I am using DUnit to test a Delphi library. I sometimes run into cases, where i write several very similar tests to check multiple inputs to a function.
Is there a way to write (something resembling) a parameterized test in DUnit? For instance specifying an input and expected output to a suitable test procedure, then running the test suite and getting feedback on which of the multiple runs of the test failed?
(Edit: an example)
For example, suppose I had two tests like this:
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;
I might have even more of these tests that do exactly the same thing but with different inputs and expectations. I don't want to merge them into one test, because I would like them to be able to pass or fail independently.
Writing Our First Parameterized TestsAdd a new test method to our test class and ensure that this method takes a String object as a method parameter. Configure the display name of the test method. Annotate the test method with the @ParameterizedTest annotation. This annotation identifies parameterized test methods.
Jest has a built-in support for tests parameterized with data table that can be provided either by an array of arrays or as tagged template literal.
JUnit 4 has introduced a new feature called parameterized tests. Parameterized tests allow a developer to run the same test over and over again using different values. There are five steps that you need to follow to create a parameterized test. Annotate test class with @RunWith(Parameterized.
You can use DSharp to improve your DUnit tests. Especially the new unit DSharp.Testing.DUnit.pas (in Delphi 2010 and higher).
Just add it to your uses after TestFramework and you can add attributes to your test case. Then it could look like this:
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.
When you run it your test looks like this:
Since attributes in Delphi just accept constants the attributes just take the arguments as a string where the values are separated by a semicolon. But nothing prevents you from creating your own attribute classes that take multiple arguments of the correct type to prevent "magic" strings. Anyway you are limited to types that can be const.
You can also specify the Values attribute on each argument of the method and it gets called with any possible combination (as in NUnit).
Referring to the other answers personally I want to write as little code as possible when writing unit tests. Also I want to see what the tests do when I look at the interface part without digging through the implementation part (I am not going to say: "let's do BDD"). That is why I prefer the declarative way.
I think you are looking for something like this:
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.
which looks like this in the GUI test runner:
I'd be very interested to know if I have gone about this in a sub-optimal way. DUnit is so incredibly general and flexible that whenever I use it I always end up feeling that I've missed a better, simpler way to solve the problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With