В настоящее время я пишу модульные тесты для встроенного приложения, используя Система модульного тестирования Google. Теперь мой начальник расстроился из-за того, что данные, которые я тестирую (т. Е. Значения, с которыми я вызываю методы тестируемого класса), жестко связаны в тестах. Он просит записать эти данные из файла. Его аргумент состоит в том, что, таким образом, было бы легче добавить еще один тест для углового случая, который был ранее забыт. Я не настолько опытен с юнит-тестами, но до сих пор я так не поступал. Поэтому я попытался выяснить, как лучше всего это сделать, даже если это вообще хорошая идея. Я быстро наткнулся на подход ДДТ (тестирование на основе данных).
В фреймворке модульного тестирования Google есть функция, которую они называют «Тесты со значениями параметров«. С этим мой тестовый прибор становится классом шаблона, и я могу передавать параметры. Однако я вижу некоторые проблемы с этим:
Я бы вообразил, что что-то настолько зрелое, как тестовая среда Google, чтобы упростить его. Тем не менее, они пишут
Параметризованные по значению тесты пригодятся [когда] вы хотите протестировать свой код на различных входах (например, тестирование на основе данных). Эту функцию легко использовать, поэтому, пожалуйста, проявите здравый смысл при ее использовании!
Кроме того, существует этот блог TotT: управляемые данными ловушки, также предупреждает меня о (злоупотреблении) модульных тестах, управляемых данными.
Итак, мой вопрос сводится к:
На самом деле я не привязан к googletest и могу свободно выбирать любые фреймворки, которые мне нравятся.
РЕДАКТИРОВАТЬ
Я нашел следующее утверждение в записи часто задаваемых вопросов о Googletest.
В Google Test пока нет хорошей поддержки […] тестов, управляемых данными. Мы надеемся, что скоро сможем улучшить эту область.
GTest имеет поддержку для этого — но, возможно, они не знают …
использование testing::ValuesIn
— как в этом упрощенном примере:
class SomeTests : public TestWithParam<int>
{
public:
};
TEST_P(SomeTests, shouldBePositive)
{
ASSERT_GT(GetParam(), 0);
}
И — способ, как получить значения из входного потока:
std::ifstream inputValuesFromFile("input.txt");
INSTANTIATE_TEST_CASE_P(FromFileStream,
SomeTests,
ValuesIn(std::istream_iterator<int>(inputValuesFromFile),
std::istream_iterator<int>()));
Чтобы использовать более сложные типы, чем «int», вам нужно написать оператор >> для него — например:
struct A
{
int a;
std::vector<int> b;
}
std::istream& operator >> (std::istream& is, A& out)
{
std::size_t bSize;
if ((is >> A.a) && (is >> bSize))
{
out.b.reserve(bSize);
while (bSize-- > 0)
{
int b;
if (!(is >> b))
break;
out.b.push_back(b);
}
}
return is;
}
Конечно, в более сложных случаях рекомендуется использовать XMl-подобные форматы (json?) И некоторые более специализированные итераторы, чем std::istream_iterator<T>
,
Для XML — подобных форматов — вы можете рассмотреть такую схему (это очень гипотетический код — у меня нет такой библиотеки в моей голове):
SomeLib::File xmlData("input.xml");
class S1BasedTests : public TestWithParam<S1>
{};
TEST_P(S1BasedTests , shouldXxxx)
{
const S1& s1 = GetParam();
...
}
auto s1Entities = file.filterBy<S1>("S1");
INSTANTIATE_TEST_CASE_P(S1,
S1BasedTests,
ValuesIn(s1Entities.begin(), s1Entities .end()));
// и т. д. для любых типов S1, которые вы хотите
Если на рынке нет такой C ++ — дружественной к типу библиотеки (я искал 2 минуты и не нашел) — тогда может быть что-то вроде этого:
SomeLib::File xmlFile("input.xml");
struct S1BasedTests : public TestWithParam<SomeLib::Node*>
{
struct S1 // xml=<S1 a="1" b="2"/>
{
int a;
int b;
};
S1 readNode()
{
S1 s1{};
s1.a = GetParam()->getNode("a").getValue<int>();
s1.b = GetParam()->getNode("b").getValue<float>();
return s1;
}
};
TEST_P(S1BasedTests , shouldXxxx)
{
const S1& s1 = readNode();
...
}
INSTANTIATE_TEST_CASE_P(S1,
S1BasedTests ,
ValuesIn(xmlFile.getNode("S1").getChildren()));
// xml=<S1s> <S1.../> <S1.../> </S1>
// и т. д. для любых типов узлов, таких как S1
Других решений пока нет …