Мы пишем модульный тест собственными силами, без фреймворка. Это значит, что нам надо каким-то образом сымитировать работу фреймворка, но собственными силами и минимумом строчек.
Для удобства нам потребуется одна функция — и немного препроцессорной магии.
#include <iostream>
#include <cstring>
void doAssert(bool condition, int line)
{
if (condition) {
std::cout << "Line " << line << " ok" << std::endl;
} else {
std::cout << "Line " << line << " FAILED" << std::endl;
}
}
#define ASSERT(x) doAssert(x, __LINE__)
#define ASSERT_STREQ(x, y) doAssert((std::strcmp(x, y) == 0), __LINE__)
А теперь что-нибудь протестируем. Например, создание строки. Для простоты я проверю не вашу строку, а std::string.
void testStringCreation()
{
std::string s;
ASSERT(s.length() == 0);
ASSERT(s.empty());
ASSERT_STREQ("", s.c_str());
}
Проверим ещё операцию +=.
void testPlusEq()
{
std::string s1 = "Alpha";
std::string s2 = "Bravo";
s1 += s2;
ASSERT_STREQ("AlphaBravo", s1.c_str());
}
int main()
{
testStringCreation();
testPlusEq();
return 0;
}
Нужны ещё тесты — создавай новую функцию и вызывай её из main.
Если какой-то тест не проходит — уничтожаем из main все вызовы, кроме несработавшего, и начинаем отладку.
А теперь немного о том, какие должны быть модульные тесты.
1. Изолированные. Если уничтожить часть тестов или запустить их в другом порядке, ничего не должно меняться — при условии, конечно, что никто не обращается к чужой памяти.
2. Каждый модульный тест проверяет одну концепцию, которая указана в его названии. Например: строка создаётся, работает операция +=, и т.д. Будет очень много дублей кода, просто смиритесь с этим.
3. Но нет ничего зазорного, что какой-то тест полагается на концепции, испытанные в других тестах. Если мы испытываем +=, то полагаем, что конструктор копирования работает, и не будем испытывать это.
4. Модульным тестам иногда приходится иметь дело с внутренним поведением объекта. В таком случае объект должен выдавать наружу некоторую информацию — например, соответствующие функции обычно private, но по какой-то препроцессорной команде они становятся public.
5. Модульные тесты обычно имеют дело с простейшим поведением объекта. Это не нагрузочные тесты, надо с этим просто смириться.
6. Принцип работы теста таков. Создать исходные объекты, убедиться в том, что их ВНУТРЕННЕЕ (неспецифицированное, зависящее от реализации) состояние верное, провести некое действие, убедиться, что действие проведено правильно. Например, пусть строка выделяет память с запасом, и надо проверить, как она расширяется — тогда при присваивании "Alpha" мы убеждаемся, что выделено менее 10 символов, затем проводим
s1 += "Bravo"
, затем убеждаемся, что выделено, например, 16 символов.
7. Но заранее не нужно проверять, верно ли ВНЕШНЕЕ (заспецифицированное) состояние. Если мы пишем
s1 = "Alpha"
, значит, строка равна "Alpha", и точка. Все случаи, возможные в операции string = char*, разумеется, покрыты другими тестами.
8. Если вдруг в «боевом» коде обнаружится ошибка, надо сделать юнит-тест, её повторяющий, затем исправить ошибку и констатировать, что тест проходит и остальные тесты не «упали».
9. То же самое, если ошибка случилась, когда проверяли другую концепцию. Проверяем +=, а глюк в конструкторе копирования — заводи новый тест, покрывающий эту ошибку.
10. В проверках на равенство принят порядок ASSERT_EQ(что_должно_быть, что_реально_есть).