4. Модульное тестирование
Цель работы
Проверить, что в случае непрохождения тестов автоматическая сборка завершается
со статусом failed
.
Цель работы:
Реализовать определение факта пересечения фигур.
- Easy
Окружность.
- Normal
Окружность, треугольник.
- Hard
Окружность, треугольник, полигон.
Общие сведения
Для покрытия тестами приложение декомпозируют на минимальные самодостаточные единицы. В процедурных языках программирования такая единица — функция.
Для покрытия функции тестами необходимо вызывать ее с заранее подготовленными входными параметрами и сравнить фактический результат ее работы с ожидаемым.
Код тестов должен быть написан отдельно от кода приложения. Поэтому для запуска тестов пишется отдельное приложение. Тестовое приложение обычно включает в себя автоматическую регистрацию новых тестов и формирование отчета. Для автоматизации процесса используют специальные библиотеки.
Сборка приложения и тестов
В предыдущей лабораторной работе мы разделили проект на консольное приложение и
статическую библиотеку. Артефакт сборки тестов — еще один исполняемый файл,
который линкуется со статической библиотекой библиотекой. Таким образом,
граф зависимостей для примера hello
принимает вид (диаграмма сокращена):
Из графа зависимостей мы видим, что один и тот же код как используется в приложении, так и покрыт тестами.
При сборке нужно предусмотреть возможность конфликтов имен объектных файлов.
Например, при компиляции файлов src/main.c
и test/main.c
не должен
создаваться один и тот же объектный файл obj/main.o
. Один из способов
предовращения таких конфликтов — дублирование структуры репозитория в каталоге
obj
:
obj
|-- src
| |-- parser.o
| `-- main.o
`-- test
|-- parser_test.o
`-- main.o
Библиотека ctest
Примеры доступны по адресу: https://github.com/bvdberg/ctest.
Файл test/main.c
:
#define CTEST_MAIN
#include <ctest.h>
int main(int argc, const char** argv)
{
return ctest_main(argc, argv);
}
Пример определения теста:
#include <sum.h>
#include <ctest.h>
CTEST(arithmetic_suite, simple_sum)
{
// Given
const int a = 1;
const int b = 2;
// When
const int result = sum(a, b);
// Then
const int expected = 3;
ASSERT_EQUAL(expected, result);
}
Здесь:
CTEST(<suite_name>, <test_name>)
— макрос для создания и регистрации тестовой функции. Все определенные таким образом тесты запускаются автоматически при вызовеctest_main
.ASSERT_EQUAL(<expected>, <real>)
— макрос для сравнения ожидаемого результата с фактическим.
Библиотека ctest
предоставляет следующие макросы:
CTEST(sname, tname)
— макрос для определения теста.sname
— имя набора тестов,tname
— имя теста.CTEST_LOG(const char* fmt, ...)
— запись в лог. Используется синтаксис аналогичныйprintf
.ASSERT_STR(exp, real)
— макрос для сравнения строк (``char* ``).ASSERT_EQUAL(exp, real)
— сравнение переменных типаint
.ASSERT_EQUAL_U(exp, real)
— сравнение переменных типаunsigned
.ASSERT_NOT_EQUAL(exp, real)
— проверка на неравенство переменных типаint
.ASSERT_NOT_EQUAL_U(exp, real)
— проверка на неравенство переменных типаunsigned
.ASSERT_DATA(exp, expsize, real, realsize)
— сравнение массивов.ASSERT_INTERVAL(exp1, exp2, real)
— проверка принадлежности числаreal
интервалу[exp1, exp2]
. Все аргументы типа intASSERT_DBL_NEAR_TOL(exp, real, tol)
— сравнение переменных типаdouble
с заданным допустимым отклонениемtol
.ASSERT_DBL_NEAR(exp, real)
— сравнение переменных типаdouble
сtol = 1e-4
ASSERT_NULL(real)
ASSERT_NOT_NULL(real)
ASSERT_TRUE(real)
ASSERT_FALSE(real)
Руководство
В структуру проекта, сформированную в предыдущей лабораторной работе, следует
добавить каталоги test
и thirdparty
. В результате получим:
.
|-- bin
| |-- geometry
| `-- geometry-test
|-- .clang-format
|-- .gitignore
|-- Makefile
|-- obj
|-- README.md
|-- src
|-- test
| |-- parser_test.c
| `-- main.c
`-- thirdparty
|-- .clang-format
`-- ctest.h
Для сторонних библиотек создан отдельный конфиг .clang-format
. Его
содержимое:
---
DisableFormat: true
SortIncludes: false
...
Форматирование исходного кода сторонних библиотек усложняет их поддержку и
вендоринг новых версий, а в случае с ctest
приводит к появлению
предупреждений на этапе компиляции.
Этапы работы
Каждый этап — отдельный коммит.
Добавить в репозиторий библиотеку ctest (заголовочный файл).
Добавить точку входа для запуска тестов —
test/main.c
. Настроить сборку и запуск тестов (доработатьMakefile
). Обычно цель по умолчанию используется только для компиляции приложения. Для компиляции и запуска тестов создают отдельную цель test. Таким образом, для полной сборки приложения и запуска тестов нужно выполнить команды:make make test
Написать любой простейший тест, проверить его работоспособность.
Реализовать функциональность в соответствии со своим вариантом.
Покрыть приложение тестами. Каждую группу тестов можно оформить в отдельный коммит.
Настроить запуск тестов в CI.