over 5 years ago

В свете работы над моими старыми программами из КГУ понадобилось покрывать
код юнит-тестами. Как выяснилось после гуглопоиска, для 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 файл, который будет использоваться как программой с тестами,
так и основной программой.

Итого, состав файлов конечного решения задачи выглядит так:

  1. Lesson01.pas — точка входа основной программы.
  2. Tests01.pas — точка входа юнит-тестов.
  3. L01Code.pas — код программы, проверяемый тестами.
  4. 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
 
comments powered by Disqus