1

我有一个名为 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;
}

但是,当它尝试设置 OptionsViewModel 的结果时,我收到错误“调用线程必须是 STA,因为许多 UI 组件需要这个”。

在手动测试期间,一切正常,没有线程问题,所以我不知道为什么我在这里得到这些......任何帮助都很棒。谢谢

(我使用 Microsoft.VisualStudio.TestTools.UnitTesting 顺便说一句)

4

2 回答 2

2

在我的测试中,我试图模拟从对话框中选择的选项。

通常,如果编写测试很困难,则表明代码应该设计得更好。

在这种情况下,直接依赖 UI 组件并不理想。有一种称为端口和适配器(又名六边形架构,又名清洁架构)的模式在这里会有所帮助。总之,您从应用程序的角度定义接口,然后让小型适配器对象实现这些接口。

所以你可以让应用程序定义一个接口来提供它需要的东西:

public interface IUserInteraction
{
  int ModalOptionsWindow();
}

有一个实现:

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

界面涵盖的具体内容取决于您。通常,我喜欢将我的 ViewModel 保留在端口的应用程序端,并且只在端口的 UI 端有视图。

一旦你有一个接口,注入IUserInteraction并让你的代码调用它。之后,简化了单元测试。


但是,如果您处于遗留代码场景中,需要在重构之前编写测试,那么您可以对 UI 代码进行单元测试。这并不容易。请参阅WpfContextWindowsFormsContext在此旧存档中查看创建 STA 线程并从单元测试中抽取消息的方法。

于 2019-05-28T20:19:25.040 回答
0

我的用例类似,对我有用的是在测试方法中创建一个 STA 线程。通过这种方式,我能够使用在其他情况下无法运行的 Winforms UI。

[TestMethod("JUST THE BASIC: Microsoft.VisualStudio.TestTools.UnitTesting")]
public async Task TestMethod1()
{
    // Use a semaphore to prevent the [TestMethod] from returning prematurely.
    SemaphoreSlim ss = new SemaphoreSlim(1);
    await ss.WaitAsync();
    Thread thread = new Thread(() =>
    {
        // Verify
        Assert.IsTrue(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA);

        // Log a message to the Unit Test
        Console.WriteLine($"Thread State is {Thread.CurrentThread.GetApartmentState()}.");

        // I personally needed to test a Winforms UI and
        // the DragDrop COM wouldn't register without STA.
        var myUI = new System.Windows.Forms.Form();
        myUI.HandleCreated += (sender, e) =>
        {
            AutomateMyUI(myUI);
        };
        System.Windows.Forms.Application.Run(myUI);

        // Signal that the [TestMethod] can return now.
        ss.Release();
    });
    // Just make sure to set the apartment state BEFORE starting the thread:
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();           
    await ss.WaitAsync();

    Console.WriteLine("All done!");
}

然后为了证明这个概念,我做了一个循环颜色的简短程序。

/// <summary>
/// DEMO: Sweeps though some UI colors before closing UI.
/// </summary>
private async void AutomateMyUI(System.Windows.Forms.Form myUI)
{
    await Task.Delay(1000);
    myUI.BackColor = Color.LightSalmon;
    await Task.Delay(1000);
    myUI.BackColor = Color.LightGreen;
    await Task.Delay(1000);
    myUI.BackColor = Color.LightYellow;
    await Task.Delay(1000);
    myUI.BackColor = Color.LightBlue;

    myUI.Close();
}

测试资源管理器输出

于 2022-02-05T17:44:23.797 回答