Я использую модульное тестирование Visual Studio 2013. Мой код использует time()
функция для генерации некоторых имен объектов и, следовательно, трудно обеспечить согласованное поведение при тестировании.
Если бы это был C #, я мог бы использовать прокладки, как показано в статье http://msdn.microsoft.com/en-us/library/hh549175.aspx Раздел «Начало работы с прокладками».
Есть ли методика, как переопределить time()
звонки во время моего модульного теста C ++?
То, что вы хотите сделать, называется насмешкой (http://en.wikipedia.org/wiki/Mock_object).
Лично мне нравится использовать Hippomocks (https://github.com/dascandy/hippomocks) который позволяет в текущей версии имитировать функции C и C ++ (с заметным исключением не-виртуальных методов класса, см., например, Пересмешивать не виртуальные методы в C ++ без редактирования рабочего кода?).
Если вы не включите ctime
или же time.h
тогда вы можете написать любое определение для time()
Вы не можете «переопределить» эти вещи как таковой, но вы можете предоставить альтернативную реализацию под другим именем и сделать различие почти прозрачный в вашем реальном коде.
Как правило, вы издеваться над функцией.
CxxTest использует подход, призывающий вас T::time()
вместо std::time()
(что достаточно близко!); с трюками макросов / включений, этот вызов разрешит либо std::time()
или к любой заменяющей реализации, которую вы предоставляете.
Вот очень упрощенный, невероятно наивный пример, предназначенный только для демонстрации основного принципа:
#include "mocked-functions.h"
/**
* Takes the current time as a UNIX timestamp, and divides it by 10.
*/
inline int foo()
{
return T::time(NULL) / 10;
}
#include "foo.h"#include <iostream>
int main()
{
std::cout << foo() << std::endl;
}
#define USE_MOCKS
#include "foo.h"#include <cassert>
int main()
{
mock_time_result = 50;
assert(foo() == 5);
mock_time_result = 400;
assert(foo() == 40);
}
#include <ctime>
#ifdef USE_MOCKS
#include "mock-time.h"#endif
namespace T {
time_t time(time_t* ptr)
{
#ifdef USE_MOCKS
return Mock_time(ptr);
#else
return std::time(ptr);
#endif
}
}
#include <ctime>
time_t mock_time_result = 0;
time_t Mock_time(time_t* ptr)
{
if (ptr) *ptr = mock_time_result
return mock_time_result;
}
$ g++ main.cpp -I. -o main
$ g++ test.cpp -I. -o test
$ ./main # output is the real time div by 10
$ ./test # output is nothing; all assertions succeed
Модульное тестирование требует детерминированного кода. std::time()
по определению является недетерминированным. Это оставляет вам 2 варианта: 1) изменить способ генерации имен, чтобы быть детерминированным, или 2) макет std::time()
,
Кажется, вы сосредоточены на # 2, поэтому один из самых простых способов сделать это — заключить вызов в std::time()
позади вашей собственной функции для генерации имени.
std::string generate_name(bool use_time = true)
{
...
if (use_time)
{
// do something with time()
}
else
{
// return a value that is deterministic
}
}
Ваши юнит-тесты пройдут в false
для use_time
параметр.
Вы можете избежать в том числе <ctime>
в коде вашего юнит-теста, и напишите свою собственную версию time()
вызвать модульные тесты (это на самом деле ближе к подходу, который предпочитают большинство разработчиков TDD).
Поскольку вы не тестируете функциональность time
и, скорее, как вы относитесь к его выводу, еще лучший подход (первый вариант) состоит в том, чтобы передать вывод time
в функцию, которую вы используете для генерации имен. Таким образом, ваши юнит-тесты могут полностью контролировать вход и тестировать выход.
std::string generate_name(unsigned long long id)
{
// do something to generate the name for the id
}
...
// production code
std::string name = generate_name(static_cast<unsigned long long>(std::time(NULL)));
// your unit test code could look like this
std::string name = generate_name(1ULL);