Насмешка над конкретным классом: шаблоны и избежание условной компиляции - PullRequest
3 голосов
/ 16 апреля 2010

Я пытаюсь протестировать конкретный объект с такой структурой.

class Database {
 public:
  Database(Server server) : server_(server) {}
  int Query(const char* expression) {
    server_.Connect();
    return server_.ExecuteQuery();
  }

 private:
  Server server_;
};

т.е. у него нет виртуальных функций, не говоря уже о четко определенном интерфейсе.

Я хочу создать поддельную базу данных, которая вызывает фиктивные сервисы для тестирования. Хуже того, я хочу, чтобы один и тот же код создавался либо для реальной версии, либо для подделки, чтобы один и тот же код тестирования мог одновременно:

  • Проверка реальной реализации базы данных - для интеграционных тестов
  • Проверка фальшивой реализации, которая вызывает фиктивные сервисы

Чтобы решить эту проблему, я использую шаблонную подделку, например:

#ifndef INTEGRATION_TESTS
class FakeDatabase {
 public:
  FakeDatabase() : realDb_(mockServer_) {}
  int Query(const char* expression) {
    MOCK_EXPECT_CALL(mockServer_, Query, 3);
    return realDb_.Query();
  }
private:
  // in non-INTEGRATION_TESTS builds, Server is a mock Server with
  // extra testing methods that allows mocking
  Server mockServer_;
  Database realDb_;
};
#endif

template <class T>
class TestDatabaseContainer {
 public:
  int Query(const char* expression) {
    int result = database_.Query(expression);
    std::cout << "LOG: " << result << endl;
    return result; 
  }
private:
  T database_;
};

Редактировать: Обратите внимание, что поддельная база данных должна вызывать реальную базу данных (но с поддельным сервером).

Теперь для переключения между ними я планирую следующую тестовую среду:

class DatabaseTests {
 public:
#ifdef INTEGRATION_TESTS
  typedef TestDatabaseContainer<Database> TestDatabase ;
#else
  typedef TestDatabaseContainer<FakeDatabase> TestDatabase ;
#endif

  TestDatabase& GetDb() { return _testDatabase; }

 private: 
  TestDatabase _testDatabase;
};

class QueryTestCase : public DatabaseTests {
 public:
  void TestStep1() {
    ASSERT(GetDb().Query(static_cast<const char *>("")) == 3);
    return;
  }
};

Я не большой поклонник того переключения во время компиляции между реальным и фальшивым.

Итак, мой вопрос:

  1. Есть ли лучший способ переключения между базой данных и FakeDatabase? Например, возможно ли сделать это во время выполнения чистым способом? Мне нравится избегать # ifdefs.
  2. Кроме того, если у кого-то есть лучший способ создать поддельный класс, имитирующий конкретный класс, я был бы признателен за это.

Я не хочу иметь шаблонный код по всему фактическому тестовому коду (класс QueryTestCase).

Не стесняйтесь критиковать и сам стиль кода. Вы можете увидеть скомпилированную версию этого кода на кодовой панели .

Ответы [ 4 ]

3 голосов
/ 15 апреля 2015

Отказ от ответственности Я работаю в Typemock.

В следующем примере код построен на основе реальной версии, без изменений в производственном коде.

Под тестом:

class Server
{
    public:
    void Connect(){/* real implementation*/}
    int ExecuteQuery() { return 0; // real implementation }
};

class Database{

    public:
    Database(Server server) : server_(server) {}

    int Query(const char* expression) {
           server_.Connect();
           return server_.ExecuteQuery();
    }

    private:
           Server server_;
};

Тестовый класс:

TEST_CLASS(GlobalCMethods)
{
public:

Проверка реальной реализации базы данных - для интеграционных тестов: Тестирование реальной реализации базы данных означает использование ее так же, как в реальном коде. Следующий пример делает именно это. Тем не менее, вы можете изменить некоторую логику, используя mocking, не изменяя логику самой базы данных. Например, если выполнение запроса было условным, вы могли бы смоделировать это условие (не в этом примере) и все же выполнить реальный запрос.

     TEST_METHOD(IntegrationTest)
     {
            Server server;
            Database db(server);
            int QueryResult = db.Query("dummy expression");
            Assert::AreEqual(0, QueryResult);
     }

Тестирование фиктивной реализации, которая вызывает фиктивные сервисы: Поскольку ваша база данных использует класс Server для выполнения запросов, насмешка над сервером в основном выводит реальное соединение из игры. Я использовал Typemock Isolator ++ для насмешек. Я использовал FAKE_ALL , чтобы высмеивать экземпляр сервера, поскольку в вашем примере сервер передается по значению (не очень хорошая идея), и он создается и дублируется при передаче. При использовании FAKE_ALL вы получаете прокси (fakeServer) для объекта, который заменяет сервер в вашем коде. все поведения, установленные на прокси-сервере, применяются к объекту в вашем коде (сервер _).

     TEST_METHOD(UnitTest)
     {
            Server* fakeServer = FAKE_ALL<Server>();
            WHEN_CALLED(fakeServer->ExecuteQuery()).Return(3);
            Database db(*fakeServer);

            int QueryResult = db.Query("dummy expression");

            Assert::AreEqual(3, QueryResult);
     }
};
0 голосов
/ 12 мая 2010

Если вы хотите выбирать между реализацией базы данных во время выполнения, то, очевидно, вам нужен полиморфизм время выполнения вместо статического полиморфизма. Поэтому измените класс шаблона TestDatabaseContainer, который опирается на концепцию «База данных», на обычный класс, который опирается на интерфейс «База данных», и пусть FakeDatabase и настоящая База данных наследуют этот интерфейс:

class IDatabase
{
public:
    int Query(const char* expression) = 0;
};
class TestDatabaseContainer 
{
 public:
  TestDatabaseContainer(IDatabase& d) : database_(d) {}
  int Query(const char* expression) {
    int result = database_.Query(expression);
    std::cout << "LOG: " << result << endl;
    return result; 
  }
private:
  IDatabase& database_;
};
class DatabaseTests 
{
 public:
  TestDatabaseContainer GetDb() 
  { 
      if(IntegrationTests)
          return TestDatabaseContainer(*(new Database));
      else 
          return TestDatabaseContainer(*(new FakeDatabase)); 
  }
};

(я никоим образом не ответственен за утечку памяти :), поэтому измените ее соответствующим образом)

0 голосов
/ 27 мая 2010

Если в базе данных нет виртуальных функций:

//ancestor of database containers
class TestDatabase
{
public:
  virtual int Query(const char* expression) = 0;
}; 
//TestDatabaseContainer is the same as in the example, but
//inherits TestDatabase
template <class T>
class TestDatabaseContainer: public TestDatabase
{
//...
};
class DatabaseTests 
{
 public:
  TestDatabase* GetDb() 
  { 
      if(IntegrationTests)
          return new TestDatabaseContainer<Database>();
      else 
          return new TestDatabaseContainer<FakeDatabase>();
  }
  //...
};
0 голосов
/ 16 апреля 2010

Вы можете поместить протестированный код и макет / фейк в их собственные исходные файлы. Затем вы можете связать с любой версией, которую вы хотите использовать.

Я лично не вставляю свой тестовый код в свой производственный код. Это отдельный проект.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...