Pregunta

Estoy creando pruebas unitarias con Dunit. Tengo una clase que lleva bastante tiempo inicializarse.

Dervo una clase TMyTestSetup de ttestSetup y anulación su método de configuración. Este método de configuración solo se llama una vez para todas las pruebas en mi ttestcase. Puse el proceso de inicialización en la rutina tmytestsetup.setup para aumentar el rendimiento.

Mi problema es cómo puedo acceder al objeto que quiero inicializar, que es un campo de mi tmytest en la clase TestSetup? ¿Es la única forma de hacerlo declararlo a nivel mundial?

Ejemplo corto no probado:

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));
¿Fue útil?

Solución

El truco es usar una variable de clase pública en la clase TMYTESTSETUP.

Como este (probado y funcionando, completo) Ejemplo:

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.

Como indican los comentarios en la muestra, he utilizado una variable privada aleatoria y una salida de traza de depuración, para confirmar que cada llamada de prueba con el conjunto de pruebas es la misma copia del objeto de destino, pero que estamos obteniendo una copia diferente del objeto objetivo cada vez que se ejecuta el conjunto de pruebas.

Otros consejos

Puede obtener una nueva clase de suite de prueba de la clase TTestsuite y anular sus métodos de configuración y desmontaje, luego puede agregar sus casos de prueba a este conjunto de pruebas en particular y registrar el conjunto.

De esta manera, los métodos de configuración y desmontaje de su clase de suite de prueba se llamarán una vez, y los métodos de configuración y desmontaje de cada caso de prueba serán llamados para cada método de prueba definido en ese caso de prueba.

La orden de ejecución será así:

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;

Tener solo un método publicado, que a su vez llama todos sus otros métodos de prueba es la forma perezosa pero más rápida de tener el procedimiento de configuración y desmontaje llamado solo una vez.

No puede inicializar los campos TTestcase para una suite de prueba completa, y aquí hay una explicación por qué:

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 ejecuta la prueba unitaria anterior, verá que las direcciones 'self' que se muestran en los métodos Test1 y Test2 son diferentes. Eso significa que las instancias de objetos TMYTESTCASE son diferentes para las llamadas Test1 y Test2.

En consecuencia, los campos que pueda declarar en la clase Tmytestcase es volátiles entre las llamadas del método de prueba.

Para realizar la inicialización "global", debe declarar su objeto a nivel mundial, no como campo TMytestcase.

Usando TTestSetup Podrías hacer algo como esto:

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

Se siente un tanto repugnante, pero hace el trabajo!

Dependiendo de su versión de Delphi, simplemente puede hacer el TMyTest.fTakes4Ever2Init campo public class var Para inicializarlo desde la configuración de la prueba. (Este sería más estilo OOP en comparación con una variable global unitaria).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top