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

Dan*_*tor 6 unit-testing d visual-d

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

计划1

import std.stdio;

unittest
{
    assert(false);
}

void main()
{
    writeln("Hello D-World!");
}
Run Code Online (Sandbox Code Playgroud)

计划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;
}
Run Code Online (Sandbox Code Playgroud)

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

更新:重新制定的问题和增加的工作实例.

更新2:也编译dmd -debug -unittest <program.d>,具有类似的结果.

Ada*_*ppe 15

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

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

当启动具有常规D main的D程序时,编译器将插入一个调用_d_run_main()的C main.正如您可以看到的那样,通过源代码查看此函数可以执行以下操作:

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

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

    if (rt_init() && runModuleUnitTests()) 
        tryExec({ result = mainFunc(args); });
Run Code Online (Sandbox Code Playgroud)

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

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

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

存在WinMain也向链接器发出信号,表示您正在编写GUI程序,因此应该使用Windows子系统 - 您没有控制台.您也可以通过-L/SUBSYSTEM:WINDOWS:5.0在Windows 32位上编译时传递给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应用程序上的控制台.