Если код не протестирован, то он не хорош :)
При регулярном написании тестов разработчик сталкивается с рядом специфических проблем:
Системы тестирования с той или иной успешностью решают эти и другие связанные с написанием тестов проблемы, избавляя программиста от написания кучи однотипного кода.
В рамках данного курса студентам предлагается использовать систему тестирования Google Testing Framework (далее, для краткости, GoogleTests)
Кратко перечислим базовые строительные элементы GoogleTests.
Пройдемся по ним более подробно и с примерами.
Утверждения реализованы в виде макросов (макро-функций).
Утверждения, начинающиеся с префикса ASSERT_
, порождают в случае неуспеха критические отказы. Утверждения, начинающиеся с префикса EXPECT_
— некритические.
В случае критического отказа происходит немедленный возврат из функции, в которой проверялось это утверждение. Если за этим утверждением идет какой-то очищающий память код или какие-то другие завершающие процедуры, то можно получить утечку памяти / ресурсов.
Далее приводится синтаксис для критических утверждений, для некритических синтаксис аналогичен, различается только префикс в имени макро-функции (ASSERT_
/ EXPECT_
).
Простые логические проверки
ASSERT_TRUE(выражение);
— проверка выражения на истинностьASSERT_FALSE(выражение);
— проверка выражения на ложностьstd::string s;
ASSERT_TRUE(s.empty()); // строка изначально пустая
s = "Hello, tests";
ASSERT_FALSE(s.empty()); // после инициализации строка не пустая
Проверка значений
Значения сравниваются с помощью операторов сравнения.
ASSERT_EQ(ожидаемое, реальное);
— проверка, что реальное значение точно равно ожидаемой величинеASSERT_NE(ожидаемое, реальное);
— проверка, что реальное значение не равно ожидаемой величинеASSERT_LT(ожидаемое, реальное);
— проверка, что ожидаемое значение < реальная величинаASSERT_LE(ожидаемое, реальное);
— проверка, что ожидаемое значение <= реальная величинаASSERT_GT(ожидаемое, реальное);
— проверка, что ожидаемое значение > реальная величинаASSERT_GE(ожидаемое, реальное);
— проверка, что ожидаемое значение >= реальная величинаstd::vector<int> v1 = { 1, 2, 3 }, v2 = { 2, 3 };
ASSERT_LE(v1.size(), v1.capacity()); // ёмкость вектора больше либо равна его размеру
ASSERT_LE(v2.size(), v2.capacity()); // ёмкость вектора больше либо равна его размеру
ASSERT_NE(v1, v2); // вектора не должны быть равны
Есть особый случай, который в GoogleTest обрабатывается не очевидным образом — это проверка указателя на равенство / не равенство NULL
. Обычное сравнение не вполне корректно работает, так как чаще всего указатель NULL
определён как целое число 0
, и его тип не совпадает с типом указателя.
int * p = NULL;
ASSERT_EQ(NULL, p); // работает
p = new int[100];
ASSERT_NE(NULL, p); // не компилируется
delete[] p;
Разработчики предлагают следующие обходные варианты решения:
int * p = NULL;
ASSERT_TRUE(p == NULL);
p = new int[100];
ASSERT_FALSE(p == NULL); // или ASSERT_TRUE(p != NULL);
delete[] p;
int * p = nullptr;
ASSERT_EQ(nullptr, p); // работает, C++11
p = new int[100];
ASSERT_NE(nullptr, p); // работает, C++11
delete[] p;
Сравнение строк
ASSERT_STREQ(ожидаемая, реальная);
— проверка C-строк на равенствоASSERT_STRNE(ожидаемая, реальная);
— проверка C-строк на неравенствоASSERT_STRCASEEQ(ожидаемая, реальная);
— регистронезависимая проверка C-строк на равенствоASSERT_STRCASENE(ожидаемая, реальная);
— регистронезависимая проверка C-строк на неравенствоstd::string s1 = "Hello", s2 = "world";
ASSERT_STRCASEEQ("Hello World!", (s1 + " " + s2 + '!').c_str()); // регистронезависимое сравнение C-строк
ASSERT_EQ("Hello world!", s1 + " " + s2 + '!'); // сравнение std::string объектов
Сравнение чисел с плавающей запятой
ASSERT_FLOAT_EQ(ожидаемое, реальное);
— неточная проверка двух float значений на равенствоASSERT_DOUBLE_EQ(ожидаемое, реальное);
— неточная проверка двух double значений на равенствоASSERT_NEAR(ожидаемое, реальное, абсолютная_ошибка);
— неточная проверка двух значений с плавающей запятой на равенство с заданной величиной допустимой ошибкиASSERT_FLOAT_EQ(0.33333347f, 1.0f / 3); // проверка вычислений с одинарной точностью
ASSERT_DOUBLE_EQ(0.3333333333333335, 1.0 / 3); // проверка вычислений с двойной точностью
ASSERT_NEAR(4.0, sin(3.14 / 2), 3.5); // проверка sin в военных условиях
Вызов отказа или успеха
SUCCEED();
— немедленное успешное завершение тестаFAIL();
— немедленное завершение теста с критическим отказомADD_FAILURE();
— вызвать некритический отказ и продолжить выполнение тестаВ случае отказа, выдаётся диагностическое сообщение с данными, использованными в утверждении. Кроме того, можно задать собственный комментарий:
const float war_limit = 4.0;
ASSERT_FLOAT_EQ(war_limit, sin(3.14 / 2)) << "war game is over";
d:\work\test\main.cpp(21): error: Value of: sin(3.14 / 2) Actual: 0.9999997 Expected: war_limit Which is: 4 war game is over
Для объявления и реализации теста используется макро-функция TEST()
. Этот макрос указывает набор, в который будет включён тест, и организует возвращающую void функцию, в которой можно использовать утверждения.
TEST(TestCaseName, TestName) {
// тестовый код и проверочные утверждения
}
Макро-функция TEST()
принимает 2 параметра, уникально идентифицирующие тест, — название тестового набора и название теста. В рамках одного и того же тестового набора названия тестов не должны совпадать. Разработчики GoogleTest настоятельно советуют не использовать в названиях тестовых наборов символы подчёркивания, и этому правилу стоит следовать. Если название начинается с префикса DISABLED_
, это означает, что тест (набор тестов) помечен как временно не используемый.
Пример теста.
TEST(TestCase1, TestA) {
ASSERT_FALSE(true == false) << "Это просто праздник какой-то.";
}
Как отмечалось ранее, критический отказ вызывает немедленный возврат из функции. Утверждения можно использовать не только в составе теста, но и вызывать из любой функции. Имеется лишь одно ограничение — утверждения, порождающие критические отказы не могут быть вызваны из функций возвращающих не void.
Фиксация представляет собой класс, унаследованный от ::testing::Test
, внутри которого объявлены все необходимые для тестирования объекты при этом в функции SetUp()
выполняется инициализация тестового окружения перед прогоном очередного теста, а в функции TearDown()
— обратная деинициализация после прогона этого теста.
class TestCaseFixing : public ::testing::Test {
protected:
virtual void SetUp(void) {
// настройка перед запуском очередного теста
}
virtual void TearDown(void) {
// очистка после прогона очередного теста
}
std::string settings; // объекты тестового окружения, доступные в каждом тесте
};
Тесты, в которых используются фиксации, должны быть объявлены с помощью макро-функции TEST_F()
.
TEST_F(TestCaseFixing, TestName) {
// тестовый код и проверочные утверждения
settings = "bla-bla-bla"; // использование объектов тестового окружения
}
Эта макро-функция в использовании полностью аналогична TEST()
, только в качестве первого параметра ей передаётся не название набора тестов, а название фиксации.
С помощью фиксаций, например, можно тестировать код на утечки памяти.
#include <crtdbg.h> // Windows only
class TestCase1 : public ::testing::Test {
protected:
virtual void SetUp(void) {
_CrtMemCheckpoint(&startup);
}
virtual void TearDown(void) {
_CrtMemState teardown, diff;
_CrtMemCheckpoint(&teardown);
ASSERT_EQ(0, _CrtMemDifference(&diff, &setup, &teardown)) << "Memory leaks detected";
}
_CrtMemState startup;
};
Объявив все необходимые тесты, мы можем запустить их с помощью функции RUN_ALL_TESTS()
. Функцию можно вызывать только один раз. Желательно, чтобы тестовая программа возвращала результат работы функции RUN_ALL_TESTS()
, так как некоторые автоматические средства тестирования определяют результат выполнения тестовой программы по тому, что она возвращает.
int main(int argc, char ** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Настройки для Qt
INCLUDEPATH = \
../gtest/googletest/include \
../gtest/googletest
SOURCES += \
../gtest/googletest/src/gtest-all.cc \