如何单元测试对话框交互

时间:2015-06-28 13:42:33

标签: c# unit-testing dialog nunit

我有以下课程:

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作为测试框架。

2 个答案:

答案 0 :(得分:4)

一种选择是将不可测试的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绕过用户界面:

[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));
}

但除非你在代码中的其他位置创建了很多FolderBrowserDialog,否则这可能会超出顶部。

答案 1 :(得分: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,它直接调用该方法。在其他框架中,它可以是委托命令或其他命令。我的浏览方法链接到视图上的按钮,因此单击视图上的按钮,激活浏览命令并根据需要打开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)的回答无疑对我有所帮助。