Управляемые данными юнит-тесты с Google Test
В настоящее время я пишу модульные тесты для встраиваемого приложения, используя фреймворк для модульных тестов Google Теперь мой начальник расстроился, что данные, с которыми я тестирую (то есть значения, с которыми я вызываю методы тестируемого класса), жестко связаны в тестах. Он просит записать эти данные из файла. Его аргумент состоит в том, что, таким образом, было бы легче добавить еще один тест для углового случая, который был ранее забыт. Я не настолько опытен с юнит-тестами, но до сих пор я так не поступал. Поэтому я попытался выяснить, как лучше всего это сделать, даже если это вообще хорошая идея. Я быстро наткнулся на подход ДДТ (тестирование на основе данных).
В модуле модульных тестов Google есть функция, которую они называют " Тесты с параметризацией по значению ". После этого мой тестовый прибор становится классом шаблона, и я могу передавать параметры. Тем не менее, я вижу некоторые проблемы с этим:
- В настоящее время у меня есть приспособление для каждого тестируемого класса. Но мне нужно приспособление для каждого тестируемого метода, поскольку каждый метод требует различного набора параметров. Это будет много дополнительной работы.
- Насколько я понимаю, я могу передать только один параметр. Поскольку для моих тестов мне нужно несколько (все параметры для моего метода плюс ожидаемые результаты), мне потребуется передать что-то вроде вектора или карты. Опять же, эта конструкция и поиск звучат как большая работа.
Я бы вообразил, что что-то настолько зрелое, как тестовая среда Google, чтобы упростить его. Тем не менее, они пишут
Параметризованные по значению тесты пригодятся [когда] вы хотите протестировать свой код на различных входных данных (так называемое тестирование на основе данных). Эту функцию легко использовать, поэтому, пожалуйста, проявите здравый смысл при ее использовании!
Кроме того, существует блог TotT: ловушки, управляемые данными, который также предупреждает меня о (злоупотреблении) модульных тестах, управляемых данными.
Итак, мой вопрос сводится к:
- Стоит ли проводить модульные тесты, управляемые данными?
- Как проводить управляемые данными юнит-тесты с помощью google test framework
На самом деле я не привязан к googletest и могу свободно выбирать любые фреймворки, которые мне нравятся.
РЕДАКТИРОВАТЬ
Я нашел следующее утверждение в записи часто задаваемых вопросов о Googletest.
В Google Test пока нет хорошей поддержки [...] тестов, управляемых данными. Мы надеемся, что скоро сможем улучшить эту область.
1 ответ
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