Почему юнит-тестирование не работает в этой 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 - нет. Что мне не хватает?
Обновление: переформулированный вопрос и добавленный рабочий пример.
Обновление 2: также скомпилировано с dmd -debug -unittest <program.d>
с похожими результатами.
1 ответ
Ответ довольно прост: в первой программе юнит-тесты фактически выполняются, а во время выполнения во второй программе - не потому, что функция юнит-теста никогда не вызывается, поскольку объявляется ваш собственный WinMain (или даже extern(C) main) обходит инициализацию и настройку во время выполнения, которые обычно выполняются автоматически перед вызовом вашего основного кода D, выполненного в C main.
Откройте ваш dmd zip и перейдите к файлу dmd2/src/druntime/src/rt/dmain2.d. Найдите функцию _d_run_main().
Когда запускается D-программа с обычной D-главной, компилятор вставляет C-главную, которая вызывает _d_run_main(). Эта функция, как вы можете видеть, просматривая источник, делает кучу вещей:
- он инициализирует аппаратное обеспечение с плавающей запятой в ожидаемом режиме D
- он форматирует аргументы командной строки в D-строки
- он инициализирует среду выполнения
- он запускает юнит-тесты << --- очень важно для вас!
- он запускает D main, завернутый в блок try/catch для обработки исключений по умолчанию
- это завершает время выполнения
- он сбрасывает вывод и возвращает
Да, в строке 399 (у меня есть версия, которая может немного отличаться в вашей версии источника druntime), вы увидите эти строки:
if (rt_init() && runModuleUnitTests())
tryExec({ result = mainFunc(args); });
Да, модульные тесты запускаются отдельно от rt_init (также известного как Runtime.initialize). Переключатель -unittest компилятора работает так, что он просто не компилирует функции unittest, поэтому runModuleUnitTests видит кучу пустых тестов, которые он пропускает. Таким образом, вы можете вызывать функцию в своей пользовательской главной, не беспокоясь о переключателе компилятора.
Так как у вас есть пользовательский основной и не звонил runModuleUnitTests
(определено в core.runtime
кстати), модульных тестов никогда не бывает. Они вызываются перед D main, но все еще внутри c main или Win main.
Я рекомендую избегать использования WinMain
в D, вместо того, чтобы предпочитать писать обычные D сети. Вы можете получить аргументы, переданные WinMain
с функциями API, такими как GetCommandLineW
, а также GetModuleHandle
, (nCmdShow
в любом случае редко используется, и я думаю, hPrevInstance
это наследие, оставшееся от 16-ти битных дней, поэтому я сомневаюсь, что вы все равно о них позаботитесь!)
Наличие WinMain
также сообщает компоновщику, что вы пишете программу с графическим интерфейсом и, следовательно, должны использовать подсистему Windows - у вас нет консоли. Вы также можете сделать это явно, передав -L/SUBSYSTEM:WINDOWS:5.0
в dmd при компиляции в Windows 32 бит. (Аргумент /SUBSYSTEM является одним из переключателей optlink.) В Windows 64 я не уверен, но он, вероятно, похож, если не идентичен - проверьте документы компоновщика Microsoft для выбора подсистемы, я уверен, что она есть.
Между переключателем компоновщика и двумя вызовами API для извлечения аргументов вам больше не нужен WinMain, поэтому он избавляет вас от необходимости переопределять то, что во время выполнения _d_run_main
Функция выполняет сама.
Если вы все равно хотите его использовать, есть два варианта: просто позвоните _d_run_main
- посмотрите на исходный код для ожидаемой подписи. Требуется указатель на основную функцию, чтобы вы могли использовать все это. Или вы можете import core.runtime;
и позвонить Runtime.initialize(); runModuleUnitTests(); your main here... Runtime.terminate();
сам. Не забудьте проверить возвращаемые значения и обработать исключения! Вы должны сделать это в правильном порядке и правильно обрабатывать ошибки, иначе вы увидите сбои.
То же самое относится и к тому, что вы пишете самостоятельно. extern(C) main
а также ваши собственные WinMain
,
Опять же, вам, вероятно, лучше избегать этого и просто писать обычную D-главную функцию с переключателем компоновщика, чтобы отключить консоль в вашем графическом приложении.