На данный момент я создал тестовый проект, используя шаблон проекта из приложения Unit Test App Visual Studio (Winui 3), и мой код выглядит следующим образом.
namespace TestMyWinrtApp
{
TEST_CLASS(CppUnitTests)
{
public:
TEST_METHOD(CppTestOne)
{
try {
auto bp = winrt::make< winrt::MyUI::implementation::BlankPage>();
Button btn = bp.FindName(L"myButton").as<winrt::Microsoft::UI::Xaml::Controls::Button>();
winrt::hstring btnTag = btn.Tag().as<winrt::hstring>();
Assert::IsTrue(btnTag == L"MyTag");
} catch (const winrt::hresult_error& ex) {
MessageBox(
NULL,
ex.message().c_str(),
L"Error",
MB_OK | MB_ICONINFORMATION
);
}
}
};
}
Однако после запуска тестов я получаю исключение: «Приложение, вызванное интерфейсом, который был настроен для другого потока». Насколько мне известно, это исключение связано с тем, что я пытаюсь создать элемент пользовательского интерфейса winrt::make< winrt::MyUI::implementation::BlankPage>() вне потока пользовательского интерфейса. Я нашел решение для C#: добавить атрибут [UITestMethod] для тестового метода. Но как я могу выполнять подобные модульные тесты в тестовом проекте C++?
@πάνταῥεῖ нет, я хочу использовать стандарт C++17
Что ж, тогда код C# может быть сложно адаптировать для этого.
@πάνταῥεῖ вообще, меня интересует любое решение на C++
Вероятно, вам лучше поискать c++-cli, поскольку он может (должен) поддерживать это [UITestMethod] так же легко и просто, как и с C#.
@πάνταῥεῖ Я понятия не имею, как мне создать на 100% совместимое (с моим приложением WinUI3 C++) приложение C++/CLI
Либо пишите модульные тесты на C# для рабочего кода C++, либо используйте среду модульного тестирования C++, такую как Google Test, Boost Test, Catch2 или (мой любимый) doctest. Кроме того, фреймворки модульного тестирования не так уж и сложно создать самостоятельно с нуля, имея лишь достаточную реализацию, чтобы удовлетворить потребности вашего проекта.
Если проблема в том, что вы используете компоненты WinUI (я вижу Button в вашем фрагменте), которым требуется работающий поток пользовательского интерфейса, то решением может быть создание обычного приложения WinUI и выполнение тестов вручную из метода OnLaunched. В связанном примере используется C# (и NUnit), но суть вы поняли. Записи Console перенаправляются в пользовательский элемент управления ConsoleRenderer, поэтому результат аналогичен выводу dotnet test ....
Кроме того, из тестовых методов выполнение должно быть передано в поток пользовательского интерфейса с помощью DispatcherQueue.TryEnqueue. Опять же пример C# . И вот, как это можно использовать из тестового примера (обратите внимание на тело, завернутое в ExecuteTest(() => {...})).
@GyörgyKőszeg Раньше у меня была похожая идея, но мне нужно стандартное решение. Если других предложений не будет, то, возможно, сделаю это. В любом случае, спасибо :)
То, что делает реализация .NET, не является волшебством, код находится здесь github.com/microsoft/testfx/blob/… вы можете сделать то же самое на C++, но это потребует немного работы, поскольку у вас нет класса Task (и связанный с ним) объект.
@SimonMourier Проблема в том, что в модульном тесте я не могу получить доступ к потоку пользовательского интерфейса для добавления обратного вызова в очередь, потому что этот вызов winrt::Windows::ApplicationModel::Core::CoreApplication::MainView().CoreWindow() .Dispatcher().TryRunAsync(priority, callback) выдает исключение «не удалось создать новое представление, поскольку главное окно еще не создано», поэтому предложенный вами подход не работает в моем случае
В WinUI3 нет ни CoreApplication, ни CoreWindow, они предназначены только для UWP. Просто посмотрите на код: это пространство имен MicrosoftXXX, а не пространство имен WindowsXXX. Конечно, этот подход работает, это точно такая же среда, но вы просто используете разные классы.
@SimonMourier, ты прав, я уже это делаю и хочу поблагодарить тебя (если тебя интересуют подробности, см. мой ответ ниже)





Проблема решена. Как предложили @Simon Mourier и @György Kőszeg в комментариях, мы можем добавить задачу в очередь потоков пользовательского интерфейса, чтобы использовать там элементы пользовательского интерфейса. Для доступа к очереди потоков пользовательского интерфейса я использовал глобальную переменную, поскольку не нашел другого способа доступа к очереди потоков пользовательского интерфейса, кроме как через глобальную переменную, значение которой присваивается в методе OnLaunched в классе App после активации главного окна. Наконец, решение выглядит так:
#include <winrt/base.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
using namespace winrt::Microsoft::UI::Xaml::Controls;
extern winrt::Microsoft::UI::Dispatching::DispatcherQueue UIDispatcherQueue;
TEST(UITEST, first)
{
std::promise<bool> p;
std::future<bool> f = p.get_future();
bool enqueued = UIDispatcherQueue.TryEnqueue(winrt::Microsoft::UI::Dispatching::DispatcherQueuePriority::High, [&p]()
{
auto bp = winrt::make< winrt::WinUIGtest::implementation::BlankPage>();
Button btn = bp.FindName(L"myButton").as<Button>();
winrt::hstring btnTag = btn.Tag().as<winrt::hstring>();
bool isEquals = (btnTag == L"MyTag");
p.set_value(isEquals);
});
bool res = f.get();
ASSERT_EQ(res, true);
}
Как видите, я также изменил тестовую среду на Google Test. Если вы хотите это сделать, то вам нужно:
Примечание. Если вы хотите запускать тесты, которые должны ставить обратный вызов в очередь в потоке пользовательского интерфейса, вам следует запускать тесты в отдельном потоке, иначе это действие будет выглядеть как вызванная взаимоблокировка.
// allocate console to see gtest output
if (AllocConsole()) {
FILE* pStdout, * pStderr;
freopen_s(&pStdout, "CONOUT$", "w", stdout);
freopen_s(&pStderr, "CONOUT$", "w", stderr);
}
// run gtest in separate thread
std::thread([]
{
int argc = 1;
char** fakeArgv = new char* [1];
char* path = new char[1024];
strcpy_s(path, 1024, R"(C:\Users\UserName\source\repos\WinUIGtest\x64\Debug\WinUIGtest\WinUIGtest.exe)");
fakeArgv[0] = path;
::testing::InitGoogleTest(&argc, fakeArgv);
//::testing::InitGoogleMock(&argc, fakeArgv);
RUN_ALL_TESTS();
}).detach();
// disable standard tests
//winrt::Microsoft::VisualStudio::TestPlatform::TestExecutor::WinRTCore::UnitTestClient::Run(m_args);
Должен ли тест быть написан на c++-cli ?