Cómo acceder a los campos de una ttestcase en una clase de ttestsetup
-
29-10-2019 - |
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));
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).