【问题标题】:How to unit test dialog interaction如何对对话交互进行单元测试
【发布时间】:2023-03-17 23:55:02
【问题描述】:

我有以下课程:

public class DirectoryFinder : IDirectoryFinder
{
    public string GetDirectory(string whereTo)
    {
        FolderBrowserDialog dialog = new FolderBrowserDialog {Description = whereTo};
        DialogResult result = dialog.ShowDialog();
        return result == DialogResult.OK ? dialog.SelectedPath : string.Empty;
    }
}

我将如何验证它是否返回了正确的数据? 例如。 string.Empty 或根据用户单击的内容在对话框中选择的任何内容?

我使用 NUnit 作为测试框架。

【问题讨论】:

  • 您不能使用纯单元测试,因为您需要对话框结果。这更具有 e2e-test 或 ui-test 的特征。您将需要一个模拟用户行为的测试框架。也许thisstackoverflow 帖子有助于找到一个。

标签: c# unit-testing dialog nunit


【解决方案1】:

一种选择是将不可测试的 UI 部分从可测试的业务逻辑中分离出来:

public string GetDirectory(string whereTo)
{
    FolderBrowserDialog dialog = new FolderBrowserDialog { Description = whereTo };
    DialogResult result = dialog.ShowDialog();

    return GetDirectory(dialog.SelectedPath, result);
}

public string GetDirectory(string selectedPath, DialogResult result)
{
    return result == DialogResult.OK ? selectedPath : string.Empty;
}

所以你只需测试第二种方法,这变得很容易。


另一种选择是使用 UI 组件的模拟/伪造。但是,FolderBrowserDialog密封,这使得这更难。

你可以做这样的事情,但这可能有点过头了

首先,为您要使用的部分定义一个接口:

public interface IFolderBrowserDialogWrapper
{
    DialogResult ShowDialog();
    string SelectedPath { get; }
}

然后将真正的FolderBrowserDialog 包裹在你的新界面中:

public class FolderBrowserDialogWrapper : IFolderBrowserDialogWrapper
{
    private readonly FolderBrowserDialog m_dialog;

    public DialogResult ShowDialog()
    {
        return m_dialog.ShowDialog();
    }

    public string SelectedPath
    {
        get { return m_dialog.SelectedPath; }
    }

    public FolderBrowserDialogWrapper(FolderBrowserDialog dialog)
    {
        m_dialog = dialog;
    }
}

并创建一个用于测试的假版本,它只返回传递给其构造函数的值:

public class FakeFolderBrowserDialogWrapper : IFolderBrowserDialogWrapper
{
    private readonly DialogResult m_result;
    private readonly string m_selectedPath;

    public DialogResult ShowDialog()
    {
        return m_result;
    }

    public string SelectedPath
    {
        get { return m_selectedPath; }
    }

    public FakeFolderBrowserDialogWrapper(string selectedPath, DialogResult result)
    {
        m_selectedPath = selectedPath;
        m_result = result;
    }
}

那么您的方法可以使用FolderBrowserDialogWrapper 进行真正的对话:

public string GetDirectory(string whereTo)
{
    var f = new FolderBrowserDialogWrapper(
        new FolderBrowserDialog { Description = whereTo });
    return GetDirectory(f);
}

public string GetDirectory(IFolderBrowserDialogWrapper dialog)
{
    DialogResult result = dialog.ShowDialog();
    return result == DialogResult.OK ? dialog.SelectedPath : string.Empty;
}

并且测试可以使用FakeFolderBrowserDialogWrapper 绕过 UI:

[Test]
public static void TestDirectoryFinderGetDirectoryWithOKExpectThePath()
{
    const string expectedPath = @"C:\temp";

    var dlg = new FakeFolderBrowserDialogWrapper(expectedPath, DialogResult.OK);

    var df = new DirectoryFinder();
    string result = df.GetDirectory(dlg);

    Assert.That(result, Is.EqualTo(expectedPath));
}

[Test]
public static void TestDirectoryFinderGetDirectoryWithCancelExpectEmptyString()
{
    const string expectedPath = @"C:\temp";

    var dlg = new FakeFolderBrowserDialogWrapper(expectedPath, DialogResult.Cancel);

    var df = new DirectoryFinder();
    string result = df.GetDirectory(dlg);

    Assert.That(result, Is.EqualTo(string.Empty));
}

但这可能有点过头了,除非您也在代码的其他地方创建了大量 FolderBrowserDialogs。

【讨论】:

    【解决方案2】:

    我要感谢 Matthew Strawbrige 的出色回答。我从他的回答开始,但我发现因为我使用的是构造函数注入(Caliburn Micro),所以我对其进行了定制以适合自己。先说界面。 (我在 SelectedPath 中添加了一个集合,以便设置起始目录)。

    public interface IFolderBrowserDialogWrapper
    {
        string SelectedPath { get; set; }
        string Description { get; set; }
        DialogResult ShowDialog();
    }
    

    然后是实现

    public class FolderBrowserDialogWrapper : IFolderBrowserDialogWrapper
    {
        private FolderBrowserDialog _dialog;
    
        public FolderBrowserDialogWrapper()
        {
            _dialog = new FolderBrowserDialog();
        }
    
        public DialogResult ShowDialog()
        {
            return _dialog.ShowDialog();
        }
    
        public string SelectedPath
        {
            get { return _dialog.SelectedPath;  }
            set { _dialog.SelectedPath = value; }
        }
    
        public string Description
        {
            get { return _dialog.Description; }
            set { _dialog.Description = value; }
        }
    }
    

    在您将在注入框架中指定的正常条件下,将使用上述类中的普通 FolderBrowseDialog 的构造函数注入 ;-)

    public class TestBrowserDialog : Caliburn.Micro.Screen
    {
        private IFolderBrowserDialogWrapper _folderBrowserDialogWrapper;
    
        public TestBrowserDialog(IFolderBrowserDialogWrapper folderBrowserDialogWrapper)
        {
            _folderBrowserDialogWrapper = folderBrowserDialogWrapper;
        }
    
        ...
        public void Browse()
        {
        ...
        }
        ...
    }
    

    然后执行命令。我正在使用直接调用该方法的 Caliburn Micro。在其他框架中,它可以是委托命令或其他东西。我的 Browse 方法链接到视图上的一个按钮,因此单击视图上的按钮会激活 Browse 命令并根据需要打开一个 BrowseFolderDialog。

        public void Browse()
        {
            _folderBrowserDialogWrapper.Description = "Your description goes here";
            DialogResult result = _folderBrowserDialogWrapper.ShowDialog();
            string path = result == DialogResult.OK ? _folderBrowserDialogWrapper.SelectedPath : string.Empty;
    
            // Process your result.
            SourceDirectory = path;
        }
    

    我的测试代码片段。在这里,我可以测试 Browse 命令的行为是否符合预期,而无需在单元测试期间调出 BrowseFolderDialog!

        [Test]
        public void TestBrowserDialog_Browse_SetsSourceDirectory()
        {
            // Arrange - This is using NSubstitute for Mocking, NUnit for testing
            IFolderBrowserDialogWrapper _folderBrowserDialogWrapper = Substitute.For<IFolderBrowserDialogWrapper>();
            _folderBrowserDialogWrapper.ShowDialog().Returns(DialogResult.OK);
            string testFileDirectory = Path.Combine(NUnit.Framework.TestContext.CurrentContext.TestDirectory, "Directory 1");
            _folderBrowserDialogWrapper.SelectedPath.Returns(testFileDirectory);
    
            // Insert your own _folderBrowserDialogWrapper for testing purposes
            TestBrowserDialog sut = new TestBrowserDialog(_folderBrowserDialogWrapper);
    
            // Action
            sut.Browse();
    
            // Assert - I'm using FluentAssertions
            sut.SourceDirectory.Should().Be(testFileDirectory);
        }
    

    我希望这能让其他人知道该怎么做。 Matthew Strawbridge 的回答肯定对我有所帮助。

    【讨论】:

      猜你喜欢
      • 2013-12-12
      • 2020-06-22
      • 2018-02-11
      • 2014-05-21
      • 2012-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多