tutorial test que makigas español delphi delphi-xe dunit parameterized-unit-test

delphi - test - ¿Puedo escribir pruebas ''parametrizadas'' en DUnit?



test en java (4)

¿Sería suficiente si DUnit permitiera escribir código como este, donde cada llamada de AddTestForDoStuff crearía un caso de prueba similar a los de su ejemplo?

Suite.AddTestForDoStuff.With(2).Expect(4); Suite.AddTestForDoStuff.With(3).Expect(9);

Trataré de publicar un ejemplo de cómo se puede hacer más tarde hoy ...

Para .Net ya hay algo similar: Afirmaciones fluidas

http://www.codeproject.com/Articles/784791/Introduction-to-Unit-Testing-with-MS-tests-NUnit-a

Estoy usando DUnit para probar una biblioteca de Delphi. A veces me topo con casos en los que escribo varias pruebas muy similares para verificar entradas múltiples a una función.

¿Hay alguna manera de escribir (algo parecido) una prueba parametrizada en DUnit? Por ejemplo, especificar una entrada y un resultado esperado para un procedimiento de prueba adecuado, luego ejecutar el conjunto de pruebas y obtener retroalimentación sobre cuál de las múltiples ejecuciones de la prueba falló.

(Editar: un ejemplo)

Por ejemplo, supongamos que tengo dos pruebas como esta:

procedure TestMyCode_WithInput2_Returns4(); var Sut: TMyClass; Result: Integer; begin // Arrange: Sut := TMyClass.Create; // Act: Result := sut.DoStuff(2); // Assert CheckEquals(4, Result); end; procedure TestMyCode_WithInput3_Returns9(); var Sut: TMyClass; Result: Integer; begin // Arrange: Sut := TMyClass.Create; // Act: Result := sut.DoStuff(3); // Assert CheckEquals(9, Result); end;

Podría tener aún más de estas pruebas que hacen exactamente lo mismo pero con diferentes entradas y expectativas. No quiero fusionarlos en una sola prueba, porque me gustaría que pasen o fallen de forma independiente.


Aquí hay un ejemplo del uso de un método de prueba parametrizado general llamado desde los métodos de prueba reales (publicados) de los descendientes de TTestCase (:

procedure TTester.CreatedWithoutDisplayFactorAndDisplayString; begin MySource := TMyClass.Create(cfSum); SendAndReceive; CheckDestinationAgainstSource; end; procedure TTester.CreatedWithDisplayFactorWithoutDisplayString; begin MySource := TMyClass.Create(cfSubtract, 10); SendAndReceive; CheckDestinationAgainstSource; end;

Sí, hay algo de duplicación, pero la duplicación principal del código se tomó de estos métodos en los métodos SendAndReceive y CheckDestinationAgainstSource en una clase antepasado:

procedure TCustomTester.SendAndReceive; begin MySourceBroker.CalculationObject := MySource; MySourceBroker.SendToProtocol(MyProtocol); Check(MyStream.Size > 0, ''Stream does not contain xml data''); MyStream.Position := 0; MyDestinationBroker.CalculationObject := MyDestination; MyDestinationBroker.ReceiveFromProtocol(MyProtocol); end; procedure TCustomTester.CheckDestinationAgainstSource(const aCodedFunction: string = ''''); var ok: Boolean; msg: string; begin if aCodedFunction = '''' then msg := ''Calculation does not match: '' else msg := ''Calculation does not match. Testing CodedFunction '' + aCodedFunction + '': ''; ok := MyDestination.IsEqual(MySource, MyErrors); Check(Ok, msg + MyErrors.Text); end;

El parámetro en CheckDestinationAgainstSource también permite este tipo de uso:

procedure TAllTester.AllFunctions; var CF: TCodedFunction; begin for CF := Low(TCodedFunction) to High(TCodedFunction) do begin TearDown; SetUp; MySource := TMyClass.Create(CF); SendAndReceive; CheckDestinationAgainstSource(ConfiguredFunctionToString(CF)); end; end;

Esta última prueba también podría codificarse usando la clase TRepeatedTest, pero creo que esa clase no es muy intuitiva de usar. El código anterior me da una mayor flexibilidad para codificar verificaciones y producir mensajes de falla inteligibles. Sin embargo, tiene el inconveniente de detener la prueba en la primera falla.


Creo que estás buscando algo como esto:

unit TestCases; interface uses SysUtils, TestFramework, TestExtensions; implementation type TArithmeticTest = class(TTestCase) private FOp1, FOp2, FSum: Integer; constructor Create(const MethodName: string; Op1, Op2, Sum: Integer); public class function CreateTest(Op1, Op2, Sum: Integer): ITestSuite; published procedure TestAddition; procedure TestSubtraction; end; { TArithmeticTest } class function TArithmeticTest.CreateTest(Op1, Op2, Sum: Integer): ITestSuite; var i: Integer; Test: TArithmeticTest; MethodEnumerator: TMethodEnumerator; MethodName: string; begin Result := TTestSuite.Create(Format(''%d + %d = %d'', [Op1, Op2, Sum])); MethodEnumerator := TMethodEnumerator.Create(Self); Try for i := 0 to MethodEnumerator.MethodCount-1 do begin MethodName := MethodEnumerator.NameOfMethod[i]; Test := TArithmeticTest.Create(MethodName, Op1, Op2, Sum); Result.addTest(Test as ITest); end; Finally MethodEnumerator.Free; End; end; constructor TArithmeticTest.Create(const MethodName: string; Op1, Op2, Sum: Integer); begin inherited Create(MethodName); FOp1 := Op1; FOp2 := Op2; FSum := Sum; end; procedure TArithmeticTest.TestAddition; begin CheckEquals(FOp1+FOp2, FSum); CheckEquals(FOp2+FOp1, FSum); end; procedure TArithmeticTest.TestSubtraction; begin CheckEquals(FSum-FOp1, FOp2); CheckEquals(FSum-FOp2, FOp1); end; function UnitTests: ITestSuite; begin Result := TTestSuite.Create(''Addition/subtraction tests''); Result.AddTest(TArithmeticTest.CreateTest(1, 2, 3)); Result.AddTest(TArithmeticTest.CreateTest(6, 9, 15)); Result.AddTest(TArithmeticTest.CreateTest(-3, 12, 9)); Result.AddTest(TArithmeticTest.CreateTest(4, -9, -5)); end; initialization RegisterTest(''My Test cases'', UnitTests); end.

que se ve así en el corredor de prueba GUI:

Estaría muy interesado en saber si he hecho esto de una manera subóptima. DUnit es tan increíblemente general y flexible que siempre que lo uso, siempre termino sintiendo que me he perdido una manera mejor y más simple de resolver el problema.


Puede usar DSharp para mejorar sus pruebas de DUnit. Especialmente la nueva unidad DSharp.Testing.DUnit.pas (en Delphi 2010 y superior).

Solo agréguela a sus usos después de TestFramework y puede agregar atributos a su caso de prueba. Entonces podría verse así:

unit MyClassTests; interface uses MyClass, TestFramework, DSharp.Testing.DUnit; type TMyClassTest = class(TTestCase) private FSut: TMyClass; protected procedure SetUp; override; procedure TearDown; override; published [TestCase(''2;4'')] [TestCase(''3;9'')] procedure TestDoStuff(Input, Output: Integer); end; implementation procedure TMyClassTest.SetUp; begin inherited; FSut := TMyClass.Create; end; procedure TMyClassTest.TearDown; begin inherited; FSut.Free; end; procedure TMyClassTest.TestDoStuff(Input, Output: Integer); begin CheckEquals(Output, FSut.DoStuff(Input)); end; initialization RegisterTest(TMyClassTest.Suite); end.

Cuando lo ejecutas, tu prueba se ve así:

Como los atributos en Delphi solo aceptan constantes, los atributos simplemente toman los argumentos como una cadena donde los valores están separados por un punto y coma. Pero nada le impide crear sus propias clases de atributos que toman múltiples argumentos del tipo correcto para evitar cadenas "mágicas". De todos modos, estás limitado a tipos que pueden ser const.

También puede especificar el atributo de Valores en cada argumento del método y se llama con cualquier combinación posible (como en NUnit ).

Al referirme a las otras respuestas personalmente, quiero escribir la menor cantidad de código posible al escribir pruebas unitarias. También quiero ver lo que hacen las pruebas cuando miro la parte de la interfaz sin profundizar en la parte de implementación (no voy a decir: "hagamos BDD "). Es por eso que prefiero la forma declarativa.