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));
È stato utile?

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.)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top