Difference between revisions of "fpcunit/pl"

From Lazarus wiki
Jump to navigationJump to search
m (→‎Testy: bold i przecinek)
m (zmiana małych liter na wielkie w SetUp i TearDown)
 
Line 23: Line 23:
 
Do skonfigurowania Twojego nowego projektu testowego, najłatwiej jest użyć Lazarusa. Poniżej znajduje się kilka opisów procedur/metod, których należy użyć w takim celu.
 
Do skonfigurowania Twojego nowego projektu testowego, najłatwiej jest użyć Lazarusa. Poniżej znajduje się kilka opisów procedur/metod, których należy użyć w takim celu.
  
===Metoda Setup===
+
===Metoda SetUp===
 
Ta procedura jest obecna we wszystkich testach FPCUnit. Konfiguruje środowisko testowe przed uruchomieniem '''każdego''' testu – innymi słowy nie tylko przed i po uruchomieniu całego zestawu testów, ale dla każdego testu”. Możesz to wykorzystać m.in. do tego, aby wypełnić bazę danych danymi testowymi.
 
Ta procedura jest obecna we wszystkich testach FPCUnit. Konfiguruje środowisko testowe przed uruchomieniem '''każdego''' testu – innymi słowy nie tylko przed i po uruchomieniu całego zestawu testów, ale dla każdego testu”. Możesz to wykorzystać m.in. do tego, aby wypełnić bazę danych danymi testowymi.
  
===Metoda Teardown===
+
===Metoda TearDown===
Ta procedura jest obecna we wszystkich testach FPCUnit i jest odwrotnością metody Setup. Czyści środowisko testowe po każdym uruchomieniu testu. Możesz to wykorzystać m.in., aby wyczyść dane testowe z bazy danych.
+
Ta procedura jest obecna we wszystkich testach FPCUnit i jest odwrotnością metody SetUp. Czyści środowisko testowe po każdym uruchomieniu testu. Możesz to wykorzystać m.in., aby wyczyść dane testowe z bazy danych.
  
===Dekorator testów: OneTimeSetup i OneTimeTearDown===
+
===Dekorator testów: OneTimeSetUp i OneTimeTearDown===
Wymienione powyżej procedury Setup i Teardown są uruchamiane jednorazowo w każdym teście. Możesz także uruchamiać te procedury raz na jedną instancję/wykonywanie każdego przebiegu testowego.
+
Wymienione powyżej procedury SetUp i TearDown są uruchamiane jednorazowo w każdym teście. Możesz także uruchamiać te procedury raz na jedną instancję/wykonywanie każdego przebiegu testowego.
  
W tym celu użyj OneTimeSetup i OneTimeTearDown w klasie dziedziczącej po „dekoratorze testów” TTestSetup i zarejestruj ją, np.:
+
W tym celu użyj OneTimeSetUp i OneTimeTearDown w klasie dziedziczącej po „dekoratorze testów” TTestSetup i zarejestruj ją, np.:
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 41: Line 41:
 
   TDBBasicsTestSetup = class(TTestSetup)
 
   TDBBasicsTestSetup = class(TTestSetup)
 
     protected
 
     protected
       procedure OneTimeSetup; override;
+
       procedure OneTimeSetUp; override;
 
       procedure OneTimeTearDown; override;
 
       procedure OneTimeTearDown; override;
 
     end;  
 
     end;  
 
...
 
...
 
initialization
 
initialization
// upewnij się, że zarejestrowałeś swój test wraz z dekoratorem, aby wiedział, jak uruchamiać Setup/Teardowns
+
// upewnij się, że zarejestrowałeś swój test wraz z dekoratorem, aby wiedział, jak uruchamiać SetUp/TearDowns
 
   RegisterTestDecorator(TDBBasicsTestSetup, TTestDBBasics);
 
   RegisterTestDecorator(TDBBasicsTestSetup, TTestDBBasics);
 
</syntaxhighlight>
 
</syntaxhighlight>

Latest revision as of 12:48, 12 May 2022

English (en) français (fr) polski (pl)

Przegląd

Fpcunit to framework do testowania modułów w stylu DUnit/JUnit/SUnit. Pozwala to na szybkie napisanie zestawu testów dla (logiki) kodu modułu (niekoniecznie takiego samego jak moduł Pascala, chociaż często tak jest).

Metodologie programowania, takie jak Test Driven Design, wykorzystują to, aby upewnić się, że najpierw kodujesz swoje oczekiwania/specyfikacje w testach jednostkowych, następnie piszesz główny kod, a następnie uruchamiasz testy i ulepszasz kod, aż wszystkie testy zakończą się pomyślnie.

fpcunit pozwala nie tylko na wizualną kontrolę przebiegów testów, ale także na systematyczne zbieranie wyników (za pomocą danych wyjściowych XML) i wykorzystanie ich do porównywania wersji m.in. błędy regresji (tzn. uruchamiasz testy regresji przy użyciu danych wyjściowych testu jednostkowego).

Zrzut ekranu programu uruchamiającego test GUI:

guitestrunner.png

Powyższy obraz pokazuje, że na 10 testów, 6 testów zakończyło się niepowodzeniem. Wyjątki EAssertionFailure wskazują, że twierdzenia testowe (patrz poniżej) nie zostały spełnione – tj. test nie powiódł się. Powiązane komunikaty wskazują wynik oczekiwany przez test i wynik rzeczywisty osiągnięty przez test.

Użycie w FPC/Lazarus

Testy FPCUnit są używane w ramach testowej bazy danych FPC: Databases#Running_FPC_database_tests

Istnieją również testy dla pakietów kompilatora/rdzenia FPC, ale przypuszczalnie poprzedzają one fpcunit i wykorzystują prostsze podejście.

Użycie

Do skonfigurowania Twojego nowego projektu testowego, najłatwiej jest użyć Lazarusa. Poniżej znajduje się kilka opisów procedur/metod, których należy użyć w takim celu.

Metoda SetUp

Ta procedura jest obecna we wszystkich testach FPCUnit. Konfiguruje środowisko testowe przed uruchomieniem każdego testu – innymi słowy nie tylko przed i po uruchomieniu całego zestawu testów, ale dla każdego testu”. Możesz to wykorzystać m.in. do tego, aby wypełnić bazę danych danymi testowymi.

Metoda TearDown

Ta procedura jest obecna we wszystkich testach FPCUnit i jest odwrotnością metody SetUp. Czyści środowisko testowe po każdym uruchomieniu testu. Możesz to wykorzystać m.in., aby wyczyść dane testowe z bazy danych.

Dekorator testów: OneTimeSetUp i OneTimeTearDown

Wymienione powyżej procedury SetUp i TearDown są uruchamiane jednorazowo w każdym teście. Możesz także uruchamiać te procedury raz na jedną instancję/wykonywanie każdego przebiegu testowego.

W tym celu użyj OneTimeSetUp i OneTimeTearDown w klasie dziedziczącej po „dekoratorze testów” TTestSetup i zarejestruj ją, np.:

uses
...
testdecorator
...
  TDBBasicsTestSetup = class(TTestSetup)
    protected
      procedure OneTimeSetUp; override;
      procedure OneTimeTearDown; override;
    end; 
...
initialization
// upewnij się, że zarejestrowałeś swój test wraz z dekoratorem, aby wiedział, jak uruchamiać SetUp/TearDowns
  RegisterTestDecorator(TDBBasicsTestSetup, TTestDBBasics);

Testy

Piszesz własne testy jako procedury w sekcji published (opublikowane) swojej klasy testowej (w sekcji private, protected lub public nie będą działać). Możesz użyć AssertEquals itp., aby określić, co powinno być testowane i dać odpowiedni komunikat, gdy test się nie powiedzie.

Jeśli chcesz „oblać test”, możesz m.in. posłużyć się tym:

if 5=0 then //śmieszny przykład, ale rozumiesz, o co mi chodzi. Możesz użyć innych procedur Assert*, aby znacznie łatwiej przetestować równość itp.
  AssertTrue('Ta część kodu nigdy nie powinna zostać wykonana w tym teście.',false);

Jeśli test się nie powiedzie, zostanie zgłoszony wyjątek EAssertionFailedError z komunikatem określonym w Assert*. W ten sposób możesz dodać serię podtestów i stwierdzić, który podtest się nie powiódł. Uwaga: program uruchamiający testy zatrzyma się po pierwszym niepowodzeniu asercji, więc kolejne podtesty nie będą wykonywane. Jeśli chcesz zawsze testować wszystko, podziel te podtesty na osobne procedury testowe.

Zamiast procedur Assert* można również użyć procedur Check* kompatybilnych z DUnit (np. CheckEquals), które dają bardziej opisowe komunikaty o błędach w wynikach testu: zawierają wartości oczekiwane i rzeczywiste.

Kolejność, w jakiej testy są uruchamiane, to kolejność, w jakiej pojawiają się one w definicji klasy testowej.

Przykładowy test

  Ttestexport1 = class(Ttestcase)
...
  published
    procedure TestOutput;
...
procedure Ttestexport1.TestOutput;
const
  OutputFilename='output.csv';
begin
  TestDataSet.Close;

  if FileExists(OutputFilename) then DeleteFile(OutputFileName);
  TestDataset.FileName:=OutputFileName;
  TestDataset.Open;
  // Wypełnij dane testowe
  TestDataset.Append;
  TestDataset.FieldByName('ID').AsInteger := 1;
  // Dane z cudzysłowami
  TestDataset.FieldByName('NAME').AsString := 'J"T"';
  TestDataset.FieldByName('BIRTHDAY').AsDateTime := ScanDateTime('yyyymmdd', '19761231', 1);
  TestDataset.Post;

  TestDataset.Last;
  TestDataset.First;
  TestDataset.First;
  AssertEquals('Liczba rekordów w testowym dataset', 1, TestDataset.RecordCount);
  TestDataset.Close;
end;

Hierarchia testów

W prostych przypadkach Ty (lub Lazarus) rejestrujesz wszystkie swoje testy za pomocą wywołań takich jak:

uses 
...
testregistry
...
initialization
  RegisterTest(Ttestexport1); //przekazujesz nazwę klasy, aby zarejestrować ją do uruchomienia

Jednak możesz także utworzyć wiele warstw, aby pogrupować testy, gdy Twój projekt stanie się duży:

initialization
  RegisterTest('WorldDominationApp.ExportCheesePlan',Ttestexport1); //Poziomy są oddzielone kropkami
  RegisterTest('WorldDominationApp.Obsolete',TtestPinkysBrain1); //inna kategoria

Niestandardowe nazwy testów

Domyślnie nazwa Twojej klasy potomnej od TTestCase jest używana jako nazwa zestawu testów, a nazwy metod są używane jako nazwy przypadków testowych. Możesz także przypisać własne nazwy, np. jeśli masz klasę, która przeprowadzi różne testy w zależności od innych ustawień.

Poniższy kod utworzy test o nazwie MyTestName (zastępując nazwę metody) w MyTestSuiteName (zastępując nazwę klasy).

interface

type
  TMyTestClass = class(TTestCase)
  protected
    // zastąp domyślną obsługę testów, uruchamiając opublikowaną metodę
    procedure RunTest; override;
    // nie potrzebujesz tutaj żadnej opublikowanej metody
  end;

implementation

procedure TMyTestClass.RunTest;
begin
  // nie wywołuj metody dziedziczonej.
  // tutaj będzie logika twojego testu ...
  AssertTrue(False);
end;

initialization
  RegisterTest('MyTestSuiteName', TMyTestClass.CreateWithName('MyTestName');
end.

Automatyczna modyfikacja pól

Jeśli dodasz jakiekolwiek pole do podklasy TTestCase, powinieneś mieć świadomość, że te pola zostaną zresetowane do wartości domyślnych przed rozpoczęciem testu. Nie uważam tego za błąd, ponieważ wyniki testów powinny być niezależne: wynik testu nie powinien zależeć od wyniku żadnego innego testu. Odtworzyłem to zachowanie zarówno w systemie Windows, jak i Linux Mint XFCE.

Jeśli naprawdę potrzebujesz wiarygodnej wartości zmiennej, użycie class var (zmiennej klasy) zamiast pól, rozwiąże problem.

Podczas dalszych testów tego modułu odkryłem, że w poniższym kodzie, jeśli zastąpię fx:double; przez class var fx:double; test już się nie powiedzie! Zadeklarowanie jednej zmiennej jako zmiennej klasowej rozwiązało problem. To sprawia, że ​​wierzę, że jest to błąd.

Zwykle błędy nie są zapisywane w dokumentacji, ale ten kosztował nas wiele godzin straconego czasu podczas testów jednostkowych. Ponadto przed zgłoszeniem jakiegokolwiek błędu należy jasno określić oczekiwane zachowanie. Ta strona wiki musi opisywać oczekiwane zachowanie. Czy błąd polega na tym, że pola są przypisane do ich domyślnych wartości 0, czy też błąd polega na tym, że po dodaniu klasy var nie są już przypisane do 0? Zgodnie z tym odniesieniem https://sergworks.wordpress.com/2012/08/31/introduction-to-unit-testing-with-lazarus/, błąd polegał na tym, że zostały one wyczyszczone do wartości domyślnych.

unit TestCase1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, fpcunit, testutils, testregistry, Dialogs;

type

  TTestCase1= class(TTestCase)
  protected
    fx:double;
    fi:integer;
    procedure SetUp; override;
    procedure TearDown; override;
  published
    procedure One;
    procedure Two;
  end;

implementation

procedure TTestCase1.One;
begin
  fx:=0.5;
  fi:=3;
  IF fi <> 3 THEN
     Fail('Musi być 3.');
end;
procedure TTestCase1.Two;
begin
  ShowMessage('fx='+fx.ToString);
  ShowMessage('fi='+fi.ToString);

  IF fi <> 3 THEN    //Nie powiedzie się, fi =0, zostało automatycznie wyczyszczone, nawet jeśli test 1 był już przeprowadzany
     Fail('Musi być 3.');
end;

procedure TTestCase1.SetUp;
begin

end;

procedure TTestCase1.TearDown;
begin

end;

initialization

  RegisterTest(TTestCase1);
end.

Wyjście

Program uruchamiający testy konsoli może wyprowadzać dane wyjściowe w formacie XML (oryginalnym formacie FPCUnit lub bardziej zaawansowanym formacie podobnym do DUnit2, jeśli używasz modułu xmltestreport), w formacie zwykłego tekstu i latex (np. nadającego się do eksportu PDF). Program uruchamiający testy GUI w razie potrzeby generuje dane wyjściowe w formacie XML (przy użyciu tego samego formatu XML xmltestreport).

Dostosowywanie wyjścia

Możesz użyć własnego „obserwatora”, który śledzi wyników testu i wyprowadza dane testowe w dowolny sposób. Utwórz T*Listener, który implementuje interfejs ITestListener. To tylko 5 wymaganych metod do wdrożenia.

W aplikacji uruchamiającej testy (np. kopii fpctestconsole), dodaj obiekt obserwatora; zarejestruj tego testowego obserwatora w środowisku testowym (np. w programie uruchamiającym testy konsoli) za pomocą wywołania TestResult.AddListener(), a wyniki testów będą do niego dostarczane w miarę ich pojawiania się.

Testdbwriter

Przykładem niestandardowego obserwatora jest program do zapisywania danych wyjściowych bazy danych dostępny pod adresem https://bitbucket.org/reiniero/testdbwriter. Ten program zapisze wszystkie wyniki testów w bazie danych, która jest zoptymalizowana pod kątem otrzymywania dużych ilości wyników testów (przydatne do używania na serwerze CI, takim jak Jenkins lub do importu/konsolidacji wyników testów). Wspomniane repozytorium zawiera przykład, który uruchamia wyniki testów frameworka db do (innej) bazy danych.

ToDo: dostosuj to; użyj nowego modułu xml

Przykładem dostępnego dodatkowego obserwatora jest TXMLResultsWriter w module xmlreporter w <fpc>\packages\fcl-fpcunit\src\xmlreporter.pas.

todo: w rzeczywistości dbtestframework wydaje się używać starej metody wyjściowej xml... Przykład dostosowanego programu uruchamiającego testy, który używa dodatkowego obserwatora, można znaleźć w <fpc>\packages\fcl-db\tests\dbtestframework.pas, który zawiera ten kod do wyprowadzenia do niestandardowych obserwatorów (program zapisujący XML i zapisujący skrót, który umieszcza wyjście w archiwum .tar, przydatne do przetwarzania zdalnego):

uses
 //...pozostałe moduły potrzebne do testowania...
 fpcunit,...
 // moduły z TXMLResultsWriter i TDigestResultsWriter
 testreport,DigestTestReport
...
Procedure LegacyOutput;

var
  FXMLResultsWriter: TXMLResultsWriter;
  FDigestResultsWriter: TDigestResultsWriter;
  testResult: TTestResult;

begin
  testResult := TTestResult.Create;
  FXMLResultsWriter := TXMLResultsWriter.Create;
  FDigestResultsWriter := TDigestResultsWriter.Create(nil);
  try
    testResult.AddListener(FXMLResultsWriter);
    testResult.AddListener(FDigestResultsWriter);
    // Ustaw niektóre właściwości specyficzne dla tego sposobu zapisywania wyników:
    FDigestResultsWriter.Comment:=dbtype;
    FDigestResultsWriter.Category:='DB';
    FDigestResultsWriter.RelSrcDir:='fcl-db';
    //WriteHeader jest specyficzny dla tego obserwatora; zapisuje nagłówek do pliku XML
    //zauważ, że nie jest wywoływany dla FDigestResultsWriter
    FXMLResultsWriter.WriteHeader;
    // To wykonuje rzeczywisty przebieg testowy, a dane wyjściowe zostaną przetworzone przez obserwatorów:
    GetTestRegistry.Run(testResult);
    // Podobnie WriteResult jest specyficzny dla tego obserwatora; zapisuje on
    FXMLResultsWriter.WriteResult(testResult);
  finally
    testResult.Free;
    FXMLResultsWriter.Free;
    FDigestResultsWriter.Free;
  end;
end;

Alternatywy

  • DUnit2 — ogromna poprawa w stosunku do oryginalnego DUnit. Pierwotnie napisany tylko dla Delphi i używany przez ogromny zestaw testowy frameworka tiOPF.
  • FPTest — rozwidlenie DUnit2, które jest dostrojone specjalnie do użytku z kompilatorem Free Pascal.

Lazarus

Lazarus posiada moduły uruchamiające testy consoletestrunner i GUI, które można zainstalować, instalując pakiet FPCUnitTestRunner. Pomoże to w tworzeniu i uruchamianiu testów jednostkowych za pomocą GUI (lub konsoli, jeśli chcesz).

Consoletestrunner jest kompatybilny z FPC, więc nie potrzebujesz Lazarusa, aby go skompilować. Wersja Lazarusa różni się nieco od tej w FPC (np. użycie wyjścia UTF8 itp.).

Program GUI jest łatwiejszy w użyciu.

W programie GUI, jeśli chcesz uruchomić wszystkie testy, obecnie musisz najpierw kliknąć element testu, zanim zostanie aktywowany przycisk Run all tests („Uruchom wszystkie testy”).

GDB błędy/funkcje

Uwaga (wrzesień 2012): błąd/nieudokumentowana funkcja w debuggerze używanym przez Lazarus/FPC (gdb) oznacza, że ​​przekazanie --all jako parametru uruchomienia nie daje żadnego efektu. Przekazanie tego parametru może być przydatne, gdy debugowanie programów uruchamiających testy fpcunit w konsoli nie przynosi żadnego efektu. Obejście: użyj -a.

Zobacz błąd [1]

Zobacz także