Come accedere ai campi di una TtestCase in una classe TtestSetUp
-
29-10-2019 - |
Domanda
Sto creando test unitari con Dunit. Ho una lezione che richiede molto tempo per inizializzare.
Derivo una classe tmytestsetup da ttestsetup e sovrascrivo il suo metodo di configurazione. Questo metodo di configurazione viene chiamato solo una volta per tutti i test nella mia TtestCase. Ho messo il processo di inizializzazione nella routine TMyTestSetup.setup per aumentare le prestazioni.
Il mio problema è come posso accedere all'oggetto che voglio inizializzare, che è un campo del mio tmytest nella classe TestSetup? L'unico modo per farlo dichiararlo a livello globale?
Short Esempio non testato:
TMyTestSetup = class(TTestSetup)
protected
procedure SetUp; override;
end;
TMyTest = class(TTestcase)
public
fTakes4Ever2Init : TInits4Ever2Init;
published
procedure Test1;
end;
implementation
procedure TMyTestSetup.Setup;
begin
// How can I access fTakes4Ever2Init from here?
fTakes4Ever2Init.create // This is the call that takes long
end;
procedure TMyTest.Test1;
begin
fTakes4Ever2Init.DoSomething;
end;
initialization
RegisterTest(TMyTestSetup.Create(TMyTest.Suite));
Soluzione
Il trucco è utilizzare una variabile di classe pubblica nella classe TMyTestSetUp.
Come questo (testato e funzionante, completo) Esempio:
unit TestTestUnit;
interface
uses
TestFramework, TestExtensions;
type
TInits4Ever2Init = class
private
FValue: integer;
public
constructor Create;
procedure DoSomething1;
procedure DoSomething2;
procedure DoSomething3;
end;
type
TMyTestSetup = class(TTestSetup)
public class var
fTakes4Ever2Init: TInits4Ever2Init;
protected
procedure SetUp; override;
end;
TMyTest = class(TTestCase)
published
procedure Test1;
procedure Test2;
procedure Test3;
end;
implementation
uses
SysUtils, Windows;
{ TMyTestSetup }
procedure TMyTestSetup.Setup;
begin
fTakes4Ever2Init := TInits4Ever2Init.create; // This is the call that takes long
end;
{ TMyTest }
procedure TMyTest.Test1;
begin
TMyTestSetup.fTakes4Ever2Init.DoSomething1;
end;
procedure TMyTest.Test2;
begin
TMyTestSetup.fTakes4Ever2Init.DoSomething2;
end;
procedure TMyTest.Test3;
begin
TMyTestSetup.fTakes4Ever2Init.DoSomething3;
end;
{ TInits4Ever2Init }
constructor TInits4Ever2Init.Create;
begin
inherited Create;
// FValue and Format('%p, %d', [Pointer(Self), FValue])) are to confirm
// that we are talking to the same object for all the tests,
// but that the object is different each time we run the test suite.
Randomize;
FValue := Random(10000);
OutputDebugString(pAnsiChar('-- TInits4Ever2Init.Create: '
+ Format('%p, %d', [Pointer(Self), FValue])));
end;
procedure TInits4Ever2Init.DoSomething1;
begin
OutputDebugString(pAnsiChar('-- TInits4Ever2Init.DoSomething1: '
+ Format('%p, %d', [Pointer(Self), FValue])));
end;
procedure TInits4Ever2Init.DoSomething2;
begin
OutputDebugString(pAnsiChar('-- TInits4Ever2Init.DoSomething2: '
+ Format('%p, %d', [Pointer(Self), FValue])));
end;
procedure TInits4Ever2Init.DoSomething3;
begin
OutputDebugString(pAnsiChar('-- TInits4Ever2Init.DoSomething3: '
+ Format('%p, %d', [Pointer(Self), FValue])));
end;
initialization
RegisterTest(TMyTestSetup.Create(TMyTest.Suite));
end.
Come indicano i commenti nel campione, ho usato una variabile privata randomizzata e qualche output di traccia di debug, per confermare che ogni chiamata di prova con la suite di test è alla stessa copia dell'oggetto target, ma che stiamo ottenendo una copia diversa dell'oggetto target ogni volta che viene eseguita la suite di test.
Altri suggerimenti
È possibile derivare una nuova classe di test di test dalla classe TTestsuite e sovrascrivere i suoi metodi di configurazione e smontaggio, quindi è possibile aggiungere i tuoi casi di test a questa particolare suite di test e registrare la suite.
In questo modo, i metodi di configurazione e smontaggio della tua classe di test verranno chiamati una volta e i metodi di configurazione e smontaggio di ciascun caso di test saranno chiamati per ogni metodo di prova definito in quel caso di test.
L'ordine di esecuzione sarà così:
TestSuite.SetUp;
-- TestCase1.Setup;
---- TestCase1.Test1;
-- TestCase1.TearDown;
-- TestCase1.Setup;
---- TestCase1.Test2;
-- TestCase1.TearDown;
-- TestCase2.Setup;
---- TestCase2.Test1;
-- TestCase2.TearDown;
-- TestCase2.Setup;
---- TestCase2.Test2;
-- TestCase2.TearDown;
-- TestCaseN.Setup;
---- TestCaseN.Test1;
-- TestCaseN.TearDown;
-- TestCaseN.Setup;
---- TestCaseN.Test2;
-- TestCaseN.TearDown;
TestSuite.TearDown;
Avere un solo metodo pubblicato, che a sua volta chiama tutti gli altri metodi di prova è il modo pigro ma più rapido di avere la procedura di configurazione e smontaggio chiamata solo una volta.
Non puoi inizializzare i campi TtestCase per un'intera suite di test, ed ecco una spiegazione per cui:
unit Tests3;
interface
uses
TestFramework, TestExtensions, Windows, Forms, Dialogs, Controls, Classes,
SysUtils, Variants, Graphics, Messages;
type
TMyTestCase = class(TTestCase)
private
FValue: Integer;
published
procedure Test1;
procedure Test2;
end;
implementation
{ TMyTestCase }
procedure TMyTestCase.Test1;
begin
FValue:= 99;
ShowMessage(Format('%p, %d', [Pointer(Self), FValue]));
end;
procedure TMyTestCase.Test2;
begin
ShowMessage(Format('%p, %d', [Pointer(Self), FValue]));
end;
initialization
RegisterTest(TMyTestCase.Suite);
end.
Se si esegue il test dell'unità sopra, vedrai che gli indirizzi "self" mostrati nei metodi Test1 e Test2 sono diversi. Ciò significa che le istanze di oggetti TMyTestCase sono diverse per le chiamate Test1 e Test2.
Di conseguenza, tutti i campi che puoi dichiarare nella classe TMyTestCase sono volatili tra le chiamate del metodo di prova.
Per eseguire l'inizializzazione "globale" dovresti dichiarare il tuo oggetto a livello globale, non come campo TMyTestCase.
Usando TTestSetup
Potresti fare qualcosa del genere:
type
TMyTestSetup = class(TTestSetup)
private
FValue: Integer;
protected
procedure SetUp; override;
procedure TearDown; override;
end;
TMyTestCase = class(TTestCase)
published
procedure TestSomething;
end;
var
TestSetup: TMyTestSetup;
procedure TMyTestSetup.SetUp;
begin
inherited;
TestSetup := Self;
FValue := 42;
end;
procedure TMyTestSetup.TearDown;
begin
TestSetup := nil;
inherited;
end;
procedure TMyTestCase.TestSomething;
begin
CheckEquals(TestSetup.FValue, 42);
end;
initialization
TestFramework.RegisterTest(TMyTestSetup.Create(
TTestSuite.Create('My test suite', [TMyTestCase.Suite])
));
Ti sembra in qualche modo robusto, ma fa il lavoro!
A seconda della versione di Delphi, puoi semplicemente fare il TMyTest.fTakes4Ever2Init
campo a public class var
Per inizializzarlo dalla configurazione del test. (Questo sarebbe uno stile OOP più rispetto a una variabile unità-globale.)