单源单元测试适用于Free Pascal和Delphi

10

有没有一种方法使得单元测试可以在Delphi和Free Pascal两个编译器下进行编译和运行?

针对两个编译器开发者需要使用不同的单元测试框架,这导致了重复工作(例如,库和框架开发者)。

因此,也许有一种方法,可以使用DUnit或FPCUnit框架,并调整测试用例源代码(或框架本身),以便它同时适用于另一个编译器。

所以基本上问题是:

  • 哪个框架(DUnit或FPCUnit)可以用最少的修改与两个编译器(Delphi和Free Pascal)都进行编译?

或者

  • 有没有第三个框架(感谢Arnaud提到的TSynTest),可以在Delphi和FPC上使用?

你特别要求在FPC中编写DUnit测试。显然这是不可能的。但这是否是你真正想问的?或者你只是想在某个单元测试框架中编写代码?我的回答是以字面意义回答问题。其他回答则假定了更宽松的解释。那么,到底是哪种情况呢? - David Heffernan
@DavidHeffernan 谢谢您指出这一点,我修改了问题并添加了 fpcunit / 单元测试 标签。 - mjn
很好,现在我可以删除不再准确的答案。问题现在更好了。 - David Heffernan
3个回答

10
请看这篇非常好的博客文章,关于FPCUnit测试的最新信息。
简而言之,据我所知,如果与DUnit进行比较:
  • 大多数Check*()方法被重命名为Assert*();
  • SetUp / TearDown方法在两个框架中都被称为per-function;
  • 其他一些内容可能会有所不同。
因此,我认为可以通过创建一个小的包装器类来让FPCUnit“模仿”DUnit,以获得与DUnit完全相同的方法。这样你就可以在两个目标之间共享代码,甚至重用现有的DUnit测试。这样的包装器类在我看来比使用{$ifdef FPC}更方便。条件编译往往会使代码难以调试、冗长、冗余,并且只应在必要时使用。
另一个潜在的解决方案是使用其他测试框架。我们的小型TSynTest类更轻便,但我目前正在将该框架转换为FPC。因此,相同的代码可以在两个编译器中使用。它具有一些功能(例如可选的细粒度日志记录和完整的堆栈跟踪失败)我会从DUnit / FPCUnit中错过。它没有GUI或Wizard,但是老实说,作为程序员,我更喜欢能够轻松地包含在我的技术发布文档中的纯文本以证明没有出现回归。

1
无论如何,Serg提到了他今天的博客文章;-)对两个人都点赞... - TLama
我建议你的 TSynTest 运行时采用非详细模式。当我运行单元测试时,我会运行文本模式的测试运行器,并期望得到像 ....... 这样的输出,每个通过的测试都显示一个点,除了失败之外,不再有其他详细输出。噪音越少越好。 - Warren P
TSynTest默认情况下不是非常详细:它将显示每个测试方法运行的一行。您还可以重定向输出。启用日志记录时,会有大量内容(几百兆字节)。当然,如果每个方法只有一个或两个断言,它将变得冗长。实际上,所使用的测试设计是为测试而编写较小的私有或公共方法,然后在发布的方法中重新组合测试,并具有明确的名称,以便进行显示。因此,您可以拥有小的粒度,但不要期望每个测试类或方法都有一个测试方法。例如,我们的mORMot运行了近1000万次测试。 - Arnaud Bouchez

7

Free Pascal 的默认单元测试框架是 FPCUnit,它与 DUnit 的设计相同,但在细节上略有不同。您可以通过 {$IFDEF FPC} 来规避差异,为 FPCUnit 和 DUnit 编写通用的单元测试。我刚刚测试了 FPCUnit,它是一个可用的框架,并且 在博客中介绍了它


4

我刚刚制作了一个示例,可以在DUnit(Delphi)和FPCUnit(最接近DUnit的Freepascal等效工具,恰好已经在Lazarus 1.0中“随箱附带”,其中包括Freepascal 2.6)中运行:

只需一点点IFDEF即可实现。

unit TestUnit1;

{$IFDEF FPC}
{$mode objfpc}{$H+}
{$ENDIF}

interface

uses
  Classes,
  {$ifdef FPC}
  fpcunit, testutils, testregistry,
  {$else}
  TestFramework,
  {$endif}
  SysUtils;

type
  TTestCase1= class(TTestCase)
  published
    procedure TestHookUp;
  end;

implementation

procedure TTestCase1.TestHookUp;
begin
   Self.Check(false,'value');
end;

initialization
  RegisterTest(TTestCase1{$ifndef FPC}.Suite{$endif});
end.

3
如果你继续深入,你会发现其他的差异,比如在DUnit中TTestCase.CheckEquals在FPCUnit中是TTestCase.AssertEquals。你需要加入一些更多的{$IFDEF} - kludg
1
我会选择使用包装类来避免在我的所有代码中添加ifdef。 - Marjan Venema
没错。从FPCUnit子类化TTestCase,并将该单元保存为TestFramework。也许可以使您需要的其他内容从TestUtils和TestRegistry正常工作。 - Warren P

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接