«Вызывающий поток должен быть STA, потому что это требуется во многих компонентах пользовательского интерфейса» во время модульного тестирования. - PullRequest
0 голосов
/ 28 мая 2019

У меня есть класс с именем OptionsWindow, который наследуется от Window, который предназначен для выбора из опций в окне. И Диалог Класс, который имеет дело с этими диалогами. В моем тесте я пытаюсь высмеять выбор, выбранный в диалоге.

[TestMethod]
public async Task Test()
{
    dialog.Setup(e => e.ShowDialog(It.IsAny<Window>(), It.IsAny<IntPtr>()))
                .Returns(true)
                .Callback<Window, IntPtr>((w, ip) => {
                    if (w.DataContext != null && w.DataContext is OptionsViewModel ovm)
                        ovm.Result = -1;
                    });
    await tester.ShowWindow();
    //assert....
}

Тогда в тестируемом классе у меня есть эти методы.

public async Task ShowWindow()
{
    var res = ShowDialog();
    //do other stuff...
}

private int ShowDialog()
{
    OptionsViewModel vm = //.....
    dialog.ShowDialog(new OptionsWindow(vm));
    return vm.Result;
}

однако, я получаю сообщение об ошибке «Вызывающий поток должен быть STA, потому что это требуется для многих компонентов пользовательского интерфейса», когда он пытается установить Результат OptionsViewModel.

Во время ручного тестирования все работало нормально и не было проблем с многопоточностью, поэтому я не уверен, почему я получаю их здесь ... любая помощь великолепна. спасибо

(я использую Microsoft.VisualStudio.TestTools.UnitTesting, кстати)

1 Ответ

1 голос
/ 28 мая 2019

В моем тесте я пытаюсь смоделировать выбор, выбранный в диалоговом окне.

Обычно, если написание тестов затруднительно, это означает, что код должен быть лучше спроектирован.

В этом случае прямая зависимость от компонентов пользовательского интерфейса не является идеальной.Здесь может помочь шаблон под названием порты и адаптеры (он же Гексагональная архитектура, он же Чистая архитектура).Таким образом, вы определяете интерфейсы с точки зрения приложения, а затем небольшие объекты-адаптеры реализуют эти интерфейсы.

Таким образом, вы можете заставить приложение определять интерфейс, который обеспечивает то, что ему нужно:

public interface IUserInteraction
{
  int ModalOptionsWindow();
}

с реализацией:

public sealed class WpfUserInteraction : IUserInteraction
{
  int ModalOptionsWindow()
  {
    OptionsViewModel vm = //.....
    dialog.ShowDialog(new OptionsWindow(vm));
    return vm.Result;
  }
}

Что именно покрывает интерфейс, зависит от вас.Как правило, мне нравится хранить мои ViewModels на стороне порта приложения и иметь только представления на стороне интерфейса UI порта.

Как только у вас есть интерфейс, введите IUserInteraction и вызовите кодтот.После этого модульное тестирование упрощается.

Однако, если вы находитесь в сценарии с устаревшим кодом, где вам нужно написать тесты до рефакторинга, то вы можете код пользовательского интерфейса для модульных тестов.Это просто не легко.См. WpfContext или WindowsFormsContext в этом старом архиве , чтобы узнать, как создать поток STA и прокачать сообщения из модульного теста.

...