Как писать юнит-тесты к программе на Free Pascal при помощи FPTest
2012.06.07
В свете работы над моими старыми программами из КГУ понадобилось покрывать код юнит-тестами. Как выяснилось после гуглопоиска, для Free Pascal, которым я компилирую свою переработку, существует проект под названием FPTest, представляющий собой каркас для написания юнит-тестов на этом замечательном языке.
Документации по этому проекту довольно немного, и официальная вики, как и README, сильно Lazarus-ориентированы. Поэтому расскажу здесь, как подключить FPTest к существующему консольному проекту и собрать тесты, написанные с его помощью.
Допустим, у нас есть программа
Я разберу первое задание из курса «алгоритмизация и программирование» в КГУ за 2006 год.
Требуется написать программу, которая будет получать число от пользователя с консоли, вычислять значение достаточно сложной математической функции от неё и выдавать результат обратно на консоль. Как-то так:
program Lesson01;
uses
SysUtils;
var
x: real;
answer: real;
begin
writeln('Enter 0.1 =< x <= 0.6 here:');
repeat
readln(x)
until
(x>=0.1) and (x<=0.6);
answer:= MyFunction(x);
writeln('Answer is ',answer:12:3);
end.
Понятное дело, MyFunction
где-то определена. Для того, чтобы
подключить юнит-тесты, нужно осознать то, что запускать тесты будет
другая программа, которую тоже нужно компилировать. То есть, каждый
билд будет собирать две программы: одна собственно та, которая нужна и
другая, которая запускает юнит-тесты и рапортует о результатах. Поэтому
весь код, который будет покрыт юнит-тестами, выносим в отдельный .pas
файл, который будет использоваться как программой с тестами, так и
основной программой.
Итого, состав файлов конечного решения задачи выглядит так:
Lesson01.pas
— точка входа основной программы.Tests01.pas
— точка входа юнит-тестов.L01Code.pas
— код программы, проверяемый тестами.L01Tests.pas
— код самих юнит-тестов (а вы как думали? :) ).
Я здесь не буду говорить о том, как писать сами юнит-тесты, используя FPTest. Только о том, как их подключить.
Разнесём код по отдельным файлам
Приведу полные листинги всех четырёх файлов, это важно, потому что boilerplate code, нужный для нормальной работы FPTest, не особо очевиден:
Lesson01.pas
program Lesson01;
uses
SysUtils,
L01Code;
var
x: real;
answer: real;
begin
writeln('Enter 0.1 =< x <= 0.6 here:');
repeat
readln(x)
until
(x>=0.1) and (x<=0.6);
answer:= MyFunction(x);
writeln('Answer is ',answer:12:3);
end.
Tests01.pas
program Tests01;
uses
TextTestRunner,
L01Tests;
begin
L01Tests.RegisterTests;
RunRegisteredTests;
end.
L01Tests.pas
unit L01Tests;
interface
uses
TestFramework, L01Code;
type
Lesson1Tests = class(TTestCase)
published
procedure TestValidInput;
procedure TestNegativeXShouldThrowError;
procedure TestMinimumAllowedX;
procedure TestMaximumAllowedX;
end;
procedure RegisterTests;
implementation
uses
math;
const
precision = 0.0001;
procedure RegisterTests;
begin
TestFramework.RegisterTest(Lesson1Tests.Suite);
end;
procedure Lesson1Tests.TestValidInput;
begin
Check(MyFunction(0.1) - 12.4244944 < precision, 'On the left edge function should be calculable');
Check(MyFunction(0.35) - 4.8590140 < precision, 'Function should be calculable in the middle of interval');
Check(MyFunction(0.6) - 4.8087379 < precision, 'On the right edge function should be calculable');
end;
procedure Lesson1Tests.TestNegativeXShouldThrowError;
begin
StartExpectingException(EInvalidArgument);
MyFunction(-1);
StopExpectingException();
end;
procedure Lesson1Tests.TestMinimumAllowedX;
begin
StartExpectingException(EInvalidArgument);
MyFunction(0.1 - precision);
StopExpectingException();
end;
procedure Lesson1Tests.TestMaximumAllowedX;
begin
StartExpectingException(EInvalidArgument);
MyFunction(0.6 + precision);
StopExpectingException();
end;
initialization
end.
L01Code.pas
unit L01Code;
interface
function MyFunction(x: real) : real;
implementation
uses
Math;
function MyFunction(x: real) : real;
begin
if (x < 0.1) then
raise EInvalidArgument.Create('Expected x in [0.1, 0.6]');
if (x > 0.6) then
raise EInvalidArgument.Create('Expected x in [0.1, 0.6]');
MyFunction := abs(sin(power(10.5*x, 1/2))) / (power(x, 2/3) - 0.143) + 2*pi*x;
end;
initialization
end.
Как обычно, тестов больше, чем кода.
Связывание вместе
Теперь, когда у нас все эти файлы счастливо лежат в одной папке, нам
нужно собирать программу с тестами, указывая компилятору, где лежит
папка с исходниками FPTest. Я вот не знал, что для этого нужно
использовать параметр -Fu
:
$ fpc -Fu"ПУТЬ ДО FPTEST" -Mobjfpc Tests01.pas
Путь можно относительный. Обязательно использовать флаг -Mobjfcp
,
потому что FPTest у себя внутри использует объектную модель, так что без
-Mobjfpc
тесты не соберутся.
Саму программу следует собирать уж так, как заблагорассудится, в зависимости от того, что там в ней как работает. Вышеописанная программа собирается без наворотов:
$ fpc Lesson01.pas