为什么单元测试在这个D程序中不起作用?

时间:2014-12-20 12:11:45

标签: unit-testing d visual-d

为什么单元测试适用于程序1,但不适用于下面的程序2?

计划1

import std.stdio;

unittest
{
    assert(false);
}

void main()
{
    writeln("Hello D-World!");
}

计划2

module winmain;

import core.sys.windows.windows;

unittest {
    assert(false);
}

extern (Windows)
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR
lpCmdLine, int nCmdShow)
{
    return 0;
}

这两个程序都是使用-unittest选项编译的(通过运行dmd -unittest <program.d>)。运行时,程序1显示单元测试失败,但程序2不显示。我错过了什么?

更新:重新制定的问题&amp;增加了工作实例。

更新2 :也使用dmd -debug -unittest <program.d>编译,结果相似。

1 个答案:

答案 0 :(得分:15)

答案有点简单:在程序一中,单元测试实际上是由程序二中的运行时运行的,它们不是因为单元测试函数从未被调用,因为声明自己的WinMain(甚至是extern(C) )main)绕过运行时初始化和设置,这通常是在调用D主代码之前自动完成的 - 在C main中完成代码。

打开你的dmd zip并转到dmd2 / src / druntime / src / rt / dmain2.d文件。找到函数_d_run_main()。

当启动具有常规D main的D程序时,编译器会插入一个调用_d_run_main()的C main。正如你可以看到的那样,这个函数可以看到一些东西:

  • 它将浮点硬件初始化为模式D期望
  • 它将命令行参数格式化为D字符串
  • 初始化运行时
  • 它运行单元测试&lt;&lt; ---对你来说非常重要!
  • 它运行D main,包含在try / catch块中以进行默认异常处理
  • 它终止运行时
  • 它会刷新输出并返回

是的,在第399行(我的版本,你的druntime源版本可能有点不同),你会看到以下几行:

    if (rt_init() && runModuleUnitTests()) 
        tryExec({ result = mainFunc(args); });

是的,单元测试与rt_init(也称为Runtime.initialize)分开运行。编译器-unittest开关的工作方式是它不会编译unittest函数,因此runModuleUnitTests会看到一堆空测试,它会跳过它。因此,您可以在自定义main中调用该函数,而无需担心编译器切换。

由于你有一个自定义main并且没有调用runModuleUnitTests(在core.runtime btw中定义),所以单元测试永远不会发生。它们在 D main之前调用,但仍在 c main或 Win main中。

我的建议是避免在D中使用WinMain,而是更喜欢编写常规D电源。您可以使用WinMainGetCommandLineW等API函数获取传递给GetModuleHandle的参数。 (nCmdShow很少使用,我认为hPrevInstance是16位日遗留下来的遗留物,所以我怀疑你无论如何都要关心它们!)

WinMain的存在也向链接器发出信号,表明您正在编写GUI程序,因此应该使用Windows子系统 - 您没有控制台。您也可以通过在Windows 32位上编译时将-L/SUBSYSTEM:WINDOWS:5.0传递给dmd来明确地执行此操作。 (/ SUBSYSTEM参数是optlink的开关之一。)在Windows 64上,我不确定,但它可能是相似的,如果不相同 - 检查Microsoft链接器的文档选择子系统,我确定它在那里。

在该链接器开关和两个用于获取参数的API调用之间,您不再需要WinMain,因此它可以省去重新实现运行时_d_run_main函数的功能。

如果你确实想要使用它,有两种选择:只需调用_d_run_main - 查看它所期望的签名的源代码。它需要一个指向main函数的指针,因此您可以重用所有这些。或者,您可以import core.runtime;并自己致电Runtime.initialize(); runModuleUnitTests(); your main here... Runtime.terminate();。不要忘记检查返回值并处理异常!您需要按正确的顺序执行此操作并正确处理错误,否则您将看到崩溃。

如果您正在编写自己的extern(C) main以及自己的WinMain,这一切也同样适用。

再说一次,你可能最好避开它,只是编写一个常规的D main函数,使用链接器开关来关闭gui应用程序上的控制台。