我正在使用DUnit创建单元测试。我有一个类需要很长时间才能初始化。
我从TTestSetup派生出一个类TMyTestSetup并覆盖其Setup方法。对于我的TTestCase中的所有测试,只调用一次此SetUp方法。我将初始化过程放在TMyTestSetup.SetUp例程中以提高性能。
我的问题是如何访问我想要初始化的对象,这是TestSetup类中我的TMyTest的一个字段?唯一的方法是在全球范围内宣布它吗?
未经测试的简短例子:
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));
答案 0 :(得分:9)
诀窍是在TMyTestSetup类中使用公共类变量。
像这样(测试和工作,完整)示例:
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.
正如示例中的注释所示,我使用了一个随机私有变量和一些调试跟踪输出,以确认每个测试套件的测试调用是与目标对象的同一个副本,但我们得到了每次运行测试套件时,目标对象的不同副本。
答案 1 :(得分:4)
您可以从TTestSuite类派生一个新的Test Suite类,并覆盖其SetUp和TearDown方法,然后您可以将测试用例添加到此特定测试套件中,并注册该套件。
这样,您的测试套件类的Setup和TearDown方法将被调用一次,并且将针对该测试用例中定义的每个测试方法调用每个测试用例的SetUp和TearDown方法。
执行顺序如下:
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;
答案 2 :(得分:3)
只有一个已发布的方法,而后者又会调用所有其他测试方法 只调用一次Setup和TearDown过程的懒惰但更快的方法。
答案 3 :(得分:2)
您无法初始化整个测试套件的TTestCase字段,以下是解释原因:
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.
如果运行上述单元测试,您将看到Test1和Test2方法中显示的“Self”地址不同。这意味着TMyTestCase对象实例对于Test1和Test2调用是不同的。
因此,您在TMyTestCase类中声明的任何字段在测试方法的调用之间都是不稳定的。
要执行“全局”初始化,您应该全局声明对象,而不是TMyTestCase字段。
答案 4 :(得分:1)
使用TTestSetup
你可以这样做:
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])
));
让你感到有点反感,但是它确实起作用了!
答案 5 :(得分:1)
根据您的Delphi版本,您只需将TMyTest.fTakes4Ever2Init
字段设为public class var
即可从测试设置初始化它。 (与单位全局变量相比,这将是更多的OOP样式。)
答案 6 :(得分:0)
更好的解决方案(... 恕我直言)
这是一个很老的问题,但是我可以想象人们仍然在碰碰这个问题。我做到了。
我对这个问题的最初解决方案还使用了vars或globals类。但是,实际上,这种解决方案是不好的,因为它很难重用git reset <file>
git add <file>
派生类。因此,我进行了一些调试以查找DUnit在内部如何工作。 (我在旗舰应用程序和库中广泛使用了DUnit)
事实证明,您实际上可以从git status -s | grep "^DU"
git status -s | grep "^UD"
# or using powershell built-ins :
git status -s | sls "^DU"
git status -s | sls "^UD"
内部访问子测试。在此方法中,您将获得包裹/装饰的子测试的句柄,该子测试实际上是由我的TTestSetup
创建的TTestSetup.RunTest
。因此,我遍历了TTestSuite
子测试(实际上是TTestCase.Suite
中每个已发布方法的方法调用),并检查它们是否支持我的ITestsuite
接口,如果是,我称为{{ 1}}。
接下来,通过调用TtestCase
进行实际测试。
最后我们再次经历相同的循环,这次调用ITestDecoratable
。
这不能解决嵌套SetupDecoration
的情况,因此我添加了一个检查inherited Runtest
是否直接支持TearDownDecoration
并相应地执行。为此,我还在TTestsetup
中实现了TTestDecorator.Test
,因此也支持嵌套。
并提出了此解决方案。我什至为此创建了一个单元测试,并且一切都按预期进行。
我可以想象有人愿意直接在ITestDecoratable
和ITestDecoratable
中实现这些方法,但是现在我将其放在一个单独的单元中。我将票证添加到相应的sourceforge网站。
这是我的解决方法:
TDecoratedTestSetup
单元测试
这是我为解决方案创建的单元测试。运行此命令应该会有所启发,并希望使您了解发生了什么。
TTestCase