Delphi 我能写';参数化';DUnit测试
我正在使用DUnit测试Delphi库。我有时会遇到这样的情况,我编写了几个非常类似的测试来检查函数的多个输入 有没有一种方法可以用DUnit编写(类似于)参数化测试?例如,为适当的测试过程指定输入和预期输出,然后运行测试套件并获得关于测试多次运行中哪次失败的反馈 (编辑:一个例子) 例如,假设我有两个这样的测试:Delphi 我能写';参数化';DUnit测试,delphi,delphi-xe,dunit,parameterized-unit-test,Delphi,Delphi Xe,Dunit,Parameterized Unit Test,我正在使用DUnit测试Delphi库。我有时会遇到这样的情况,我编写了几个非常类似的测试来检查函数的多个输入 有没有一种方法可以用DUnit编写(类似于)参数化测试?例如,为适当的测试过程指定输入和预期输出,然后运行测试套件并获得关于测试多次运行中哪次失败的反馈 (编辑:一个例子) 例如,假设我有两个这样的测试: procedure TestMyCode_WithInput2_Returns4(); var Sut: TMyClass; Result: Integer; begin
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;
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.
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.
我可能会有更多这样的测试,它们做完全相同的事情,但有不同的输入和期望。我不想将它们合并到一个测试中,因为我希望它们能够独立地通过或失败。如果DUnit允许编写这样的代码,即每次调用AddTestForDoStuff都会创建一个类似于您示例中的测试用例,那么这样就足够了吗
Suite.AddTestForDoStuff.With(2).Expect(4);
Suite.AddTestForDoStuff.With(3).Expect(9);
今天晚些时候,我将尝试发布一个示例,说明如何做到这一点
对于.Net来说,已经有了类似的东西:流畅的断言
以下是一个使用通用参数化测试方法的示例,该方法从您的TTestCase子代调用实际(已发布)测试方法(: 是的,存在一些重复,但主要的代码重复是从这些方法中取出,放入祖先类中的SendReceive和CheckDestinationAgainsSource方法中的:
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;
CheckDestinationAnstSource中的参数也允许此类型的使用:
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;
最后一个测试也可以使用TreepeatedTest类进行编码,但我发现该类使用起来并不直观。上面的代码在编码检查和生成可理解的故障消息方面给了我更大的灵活性。但是,它确实有一个缺点,即在第一次故障时停止测试。我想您正在寻找类似的代码这:
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;
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.
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.
在GUI测试运行程序中如下所示:
我很想知道我是否以一种次优的方式来解决这个问题。DUnit是如此的通用和灵活,以至于每当我使用它时,我总是觉得我错过了一种更好、更简单的解决问题的方法。你可以使用DSharp来改进你的DUnit测试。尤其是新的单元(在Delphi 2010及更高版本中) 只需在TestFramework之后将其添加到您的uses中,您就可以将属性添加到您的测试用例中。然后它可以如下所示:
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;
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.
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.
运行时,您的测试如下所示:
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;
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.
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.
由于Delphi中的属性只接受常量,因此属性只接受以分号分隔的字符串形式的参数。但没有任何东西阻止您创建自己的属性类,这些属性类接受多个正确类型的参数,以防止出现“神奇”字符串。无论如何,您仅限于可以是常量的类型
您还可以在方法的每个参数上指定Values属性,并使用任何可能的组合调用它(如中所示)
参考其他答案,我个人希望在编写单元测试时尽可能少地编写代码。此外,我还希望看到当我查看接口部分而不深入实现部分时,测试会做些什么(我不会说:“让我们做吧”)。这就是为什么我更喜欢声明式方法。你是说为列表中的所有输入值动态创建测试用例吗?我的(小型)测试框架包含动态创建测试用例的代码。它基于DUnit。你可以在testclass中编写一个通用参数化方法,并从一个或多个特定的(已发布的)测试类调用它测试方法。TestCase的Check(Not)Equals方法在这里也有助于保持代码简洁,并且仍然为每个测试提供特定的失败消息。@Marjan测试方法将在第一次检查(Not)后立即停止执行Equals失败-动态创建测试用例解决了这个问题,所有其他值仍然是tested@mjn:OpenCTF似乎是用于以黑盒方式测试组件和表单…这在这里似乎不适用…@Marjannema:我想这不是一个坏方法。我会尝试一下这个方法…沿着这些思路做一些事情会很好但它可能还需要将特定测试作为参数,对吗?例如
Suite.AddTest('DoStuff')。WithArgument(2)。Expected(4)
@MathiasFalkenberg:这或者至少是添加一条消息的可能性。这个答案很酷。这是我第一次看到一厢情愿的投票结果。是的,如果你能做到这一点,那不是太好了!!无论如何,如果你能生成真正符合要求的代码,+1首付就可以赚到了。提示:AddTestForDoStuff创建了一个TTestodStuff实例,使用和期望像属性设置器一样工作。这是应用于DUnit的生成器模式。+1看起来非常有用和有趣。感谢您让我们注意到它。+1确实!我一定会研究它。我想这是迄今为止建议的最轻松的方法!我也有同样的感觉。。。这就是我发布此问题的原因。虽然您的代码肯定会生成所需的输出,但我希望我的测试更具可读性。“CreateTest”方法为测试代码引入了一层复杂性,而我更希望避免这种复杂性。。。