Question

Je crée des tests unitaires avec DUnit.J'ai une classe qui prend beaucoup de temps à s'initialiser.

Je dérive une classe TMyTestSetup de TTestSetup et écrase sa méthode Setup.Cette méthode SetUp n'est appelée qu'une seule fois pour tous les tests de mon TTestCase.J'ai mis le processus d'initialisation dans la routine TMyTestSetup.SetUp pour augmenter les performances.

Mon problème est de savoir comment accéder à l'objet que je souhaite initialiser, qui est un champ de mon TMyTest dans la classe TestSetup?Est-ce que le seul moyen de le faire est de le déclarer globalement?

exemple court non testé:

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));
Était-ce utile?

La solution

L'astuce consiste à utiliser une variable de classe publique dans la classe TMyTestSetup.

Comme cet exemple (testé et fonctionnel, complet):

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.

Comme l'indiquent les commentaires de l'exemple, j'ai utilisé une variable privée aléatoire et une sortie de trace de débogage, pour confirmer que chaque appel de test avec la suite de tests concerne la même copie de l'objet cible, mais que nous obtenonsune copie différente de l'objet cible à chaque exécution de la suite de tests.

Autres conseils

Vous pouvez dériver une nouvelle classe de Test Suite à partir de la classe TTestSuite et remplacer ses méthodes SetUp et TearDown, puis vous pouvez ajouter vos cas de test à cette suite de tests particulière et enregistrer la suite.

De cette façon, les méthodes Setup et TearDown de votre classe de suite de tests seront appelées une fois, et les méthodes SetUp et TearDown de chaque cas de test seront appelées pour chaque méthode de test définie dans ce cas de test.

L'ordre d'exécution sera comme ceci:

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;

Le fait de n'avoir qu'une seule méthode publiée, qui à son tour appelle toutes vos autres méthodes de test, est le moyen paresseux mais plus rapide d’appeler une seule fois la procédure Setup et TearDown.

Vous ne pouvez pas initialiser les champs TTestCase pour toute une suite de tests, et voici une explication pourquoi:

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.

Si vous exécutez le test unitaire ci-dessus, vous verrez que les adresses 'Self' affichées dans les méthodes Test1 et Test2 sont différentes.Cela signifie que les instances d'objet TMyTestCase sont différentes pour les appels Test1 et Test2.

Par conséquent, tous les champs que vous pouvez déclarer dans la classe TMyTestCase sont volatils entre les appels de la méthode de test.

Pour effectuer une initialisation "globale", vous devez déclarer votre objet globalement, pas en tant que champ TMyTestCase.

En utilisant TTestSetup, vous pouvez faire quelque chose comme ceci:

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])
  ));

Cela vous semble quelque peu révoltant, mais cela fait le travail!

En fonction de votre version de Delphi, vous pouvez simplement faire du champ TMyTest.fTakes4Ever2Init un public class var pour l'initialiser à partir de la configuration de test.(Ce serait plus de style POO par rapport à une variable globale d'unité.)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top