如何访问 TTestSetup 类中 TTestCase 的字段
我正在使用 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));
I am creating unit tests with DUnit. I have a class that takes quite a long time to initialize.
I derive a class TMyTestSetup from TTestSetup and override its Setup method. This SetUp method is only called once for all the tests in my TTestCase. I put the Initialization process in the TMyTestSetup.SetUp routine to increase performance.
My problem is how can I access the object I want to initialize, which is a field of my TMyTest in the TestSetup class? Is the only way to do it declaring it globally?
untested short example:
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));
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
诀窍是在 TMyTestSetup 类中使用公共类变量。
像这个(经过测试和工作,完整)示例:
正如示例中的注释所示,我使用了一个随机私有变量和一些调试跟踪输出,以确认测试套件的每个测试调用都针对相同的副本目标对象,但每次运行测试套件时我们都会获得目标对象的不同副本。
The trick is to use a public class variable in the TMyTestSetup class.
Like this (tested and working, complete) example:
As the comments in the sample indicate, I have used a randomised private variable, and some debug trace output, to confirm that each test call with the test suite is to the same copy of the target object, but that we are getting a different copy of the target object each time the test suite is run.
您可以从 TTestSuite 类派生一个新的测试套件类,并重写其 SetUp 和 TearDown 方法,然后您可以将测试用例添加到这个特定的测试套件中,并注册该套件。
这样,测试套件类的Setup和TearDown方法将被调用一次,并且每个测试用例的SetUp和TearDown方法将针对该测试用例中定义的每个测试方法被调用。
执行顺序将是这样的:
You can derive a new Test Suite class from TTestSuite class, and override its SetUp and TearDown methods, then you can add your test cases to this particular test suite, and register the suite.
This way, Setup and TearDown methods of your test suite class will be called once, and SetUp and TearDown methods of each test case will be called for every test method defined in that test case.
Execution order will be like this:
只有一个已发布的方法,该方法又调用所有其他测试方法,这就是
让Setup 和TearDown 过程仅调用一次的惰性但更快的方法。
Having just one published method, which in turn call all your other test methods is the
lazy but quicker way of having the Setup and TearDown procedure called only once.
您无法初始化整个测试套件的 TTestCase 字段,原因如下:
如果运行上述单元测试,您将看到 Test1 和 Test2 方法中显示的“Self”地址不同。这意味着 Test1 和 Test2 调用的 TMyTestCase 对象实例是不同的。
因此,您在 TMyTestCase 类中声明的任何字段在测试方法的调用之间都是可变的。
要执行“全局”初始化,您应该全局声明您的对象,而不是 TMyTestCase 字段。
You can't initialize TTestCase fields for a whole test suite, and here is an explanation why:
If you run the above unit test you will see that the 'Self' addresses shown in Test1 and Test2 methods are different. That means that TMyTestCase object instances are different for Test1 and Test2 calls.
Consequently, any fields you may declare in TMyTestCase class are volatile between test method's calls.
To perform "global" initialization you should declare your object globally, not as TMyTestCase field.
使用
TTestSetup
你可以做这样的事情:你可能会觉得有点令人反感,但它确实有效!
Using
TTestSetup
you could do something like this:It feels somewhat revolting mind you, but it does the job!
根据您的 Delphi 版本,您只需将
TMyTest.fTakes4Ever2Init
字段设置为public class var
即可从测试设置中对其进行初始化。 (与单元全局变量相比,这将更具 OOP 风格。)Depending on your Delphi version, you can simply make the
TMyTest.fTakes4Ever2Init
field apublic class var
to initialize it from the test setup. (This would be more OOP style compared to a unit-global variable.)更好的解决方案(...恕我直言)
这是一个相当老的问题,但我可以想象人们仍然会遇到这个问题。我做到了。
我对这个问题的最初解决方案也使用了类变量或全局变量。但实际上这个解决方案很糟糕,因为它使得重用 TTestSetup 派生类变得非常困难。因此我进行了一些调试以了解 DUnit 内部的工作原理。 (我在我的旗舰应用程序和库中广泛使用 DUnit)
事实证明,您实际上可以从
TTestSetup.RunTest
中访问子测试。在此方法中,您将获得包装/装饰的子测试的句柄,它实际上是一个从我的TTestCase.Suite
创建的TTestSuite
。因此,我循环遍历ITestsuite
子测试(实际上是对TtestCase
中每个已发布方法的方法调用),并检查它们是否支持我的ITestDecoratable
接口,如果是这样我调用SetupDecoration
。接下来,通过调用
继承的Runtest
来执行实际测试。最后,我们再次执行相同的循环,这次调用
TearDownDecoration
。这并没有修复嵌套的
TTestsetup
情况,因此我添加了一个检查TTestDecorator.Test
是否直接支持ITestDecoratable
,并相应地执行。就此而言,我还在我的TDecoratedTestSetup
中实现了ITestDecoratable
,因此也支持嵌套。并想出了这个解决方案。我什至为它创建了一个单元测试,一切都按预期进行。
我可以想象人们宁愿直接在
TTestCase
和TTestDecorator
中实现这些方法,但现在我已将其放在一个单独的单元中。我将向相应的 sourceforge 站点添加票证。这是我的解决方案:
单元测试
这是我为解决方案创建的单元测试。运行这个应该会带来一些启发,并希望让您了解发生了什么。
The better solution (... IMHO)
It's a pretty old question, but I can imagine people still bumping into this. I did.
My initial solution to this problem also used class vars or globals. But indeed this solution is bad as it makes it very hard to re-use
TTestSetup
derived classes. Hence I debugged a bit to find how DUnit works internally. (I use DUnit extensively on my flagship app and libs)As it turns out you actually can get access to the subtests: from within
TTestSetup.RunTest
. In this method you get a handle to the wrapped/decorated Subtest, which actually turned out to be aTTestSuite
, created from myTTestCase.Suite
. So I loop through theITestsuite
subtests (which are actually method calls for each published method in yourTtestCase
), and check if they support myITestDecoratable
interface, if so I call theSetupDecoration
.Next, the actual test is performed by calling the
inherited Runtest
.And finally we go through the same loop again, this time calling
TearDownDecoration
.This did not fix the nested
TTestsetup
case, so I added a check ifTTestDecorator.Test
supportsITestDecoratable
directly, and execute accordingly. For that matter, I alsom implemented theITestDecoratable
in myTDecoratedTestSetup
so nesting is also supported.And came up with this solution. I even created a unit test for it, and everything works as intended.
I can imagine one would rather implement these methods in
TTestCase
andTTestDecorator
directly, but for now I have put it in a separate unit. I'll add a ticket to the corresponding sourceforge site.Here's my solution:
Unit Test
And here's the unit test I created for my solution. Running this should shed some light and hopefully make you understand what's going on.