В этом разделе предлагается универсальный, но достаточно примитивный метод автоматического (регрессионного) юнит-тестирования с помощью простых сценариев операционной системы ("скриптов") и программы make.
В качестве примера тестируемой программы рассмотрим класс ZZ, реализующий арифметику многократной точности.
Тесты для проверки корректности выполнения арифметических действий будут состоять из сложения и умножения положительных и отрицательных чисел:
stdin
описание решаемой задачи, вызывает тестируемую функцию с нужными
аргументами и выводит полученный ответ
в stdout
. Будем считать, что одна строчка входного
потока переводится этой программой в одну строчку выходного
потока. Например, если на вход поступает последовательность строк,
приведенная слева, то на выходе получается поток, представленный
справа:
* 2 3 + 2 5
6 7
Поскольку тестов на проверку корректности основных операций может быть много (в нашем примере есть как минимум шесть групп тестов, различающихся знаками и разрядностью чисел), а выполняются они единообразно, то для обработки всех тестов можно использовать следующую процедуру. Создадим директорию с именем UNIT-TESTS, содержащую множество файлов с входными данными для программы test-correct. Пусть эти файлы имеют имена вида название группы тестов.in. Например, файл 32bits_positive.in будет содержать тесты на выполнение действий с "короткими" положительными целыми числам. В файл с тем же именем, но расширением .out запишем ожидаемые, то есть корректные ответы. Файл 32bits_positive.out содержит правильные ответы для файла 32bits_positive.in. В этом случае выполнение тестов сводится к вызову программы test-correct для in-файла и сравнения результата работы с содержимым соответствующего out-файла. Это делает приведенный ниже сценарий оболочки bash.
#!/bin/bash
test_runner=$1
tests_directory=${2:-./UNIT-TESTS}
tmp=last_run_results.txt
nfailed=0
for testfile in `find $tests_directory -type f -name "*\.in"`
do
testname=`basename $testfile .in`
expected_answer=`dirname $testfile`/`basename $testfile .in`.out
echo -E -n $testname " "
$test_runner <$testfile 2>/dev/null >$tmp
diff -abBiq $tmp $expected_answer >/dev/null
if [ $? -ne 0 ]
then
echo -e \\033[31m \\t "FAILED" \\033[0m
((nfailed++))
else
echo -e \\033[32m \\t "PASSED" \\033[0m
fi
done
exit nfailed
chmod +x run-tests
Если программа test-correct выводит предсказуемое сообщение об ошибке в случае поступления некорректных входных данных, то для тестов этого типа можно использовать подход с in- и out- файлами. Для этого достаточно создать файлы вида:
* 2.1 3 + 1 5abc
not an integer not an integer
Для этого типа тестирования нужно написать специальную программу. Основная часть этой программы представляет собой многократно повторяющийся цикл, на каждой итерации которого делается случайный выбор операции.
ZZ x;
for(int k = 0; k < MAX_ITERATIONS; k++) {
int operation = random() % 5;
ZZ y(random());
switch(operation) {
case 0:
case 1:
x = y + x;
break;
case 2: {
ZZ temp = x*x;
temp = x*x;
x = (x*y) + temp;
break;
}
// ...
}
}
Переменная operation
на каждой итерации принимает
значение из множества {0, 1, 2}. Если это 0 или 1, то выполняется
одно действие. Если значение равно 2, то другое. И так
далее. Заметим, что operation имеет равномерное распределение, а
за счет указания нескольких меток case можно повысить вероятность
выполнения некоторых операций. На каждой итерации создается один
(y) или два (y, temp) объекта класса ZZ. Переменная x используется
для накопления значений.
Для автоматизации процесса тестирования воспользуемся возможностями команды make. Для этого добавим в Makefile цель tests, которая будет выполнять все тесты.
.PHONY: tests # Эту строчку принято писать в начале Makefile
tests: test-stability test-errors
./run-tests test-correct # Тесты операций и некорректных данных
./test-stability
echo "Все тесты пройдены!"
Здесь предполагается, что у нас есть три специально написанные
программы. Программы test-stability и test-errors выполняют длнную
последовательность операций и операции с некорректными входными
данными, соответственно. В случае успешного выполнения тестов эти
программы возвращают 0, а в случае ошибки – ненулевое
значение. Это гарантирует, что команда echo
будет
выполнена только в случае корректного завершения всех тестов.
Регрессионное тестирование подразумевает накопление тестов. В предлагаемой схеме тестирования накопление может осуществляться двумя способами.