如何对对话交互进行单元测试
How to unit test dialog interaction
我有以下 class:
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;
}
}
我将如何验证它 returns 是正确的数据?
例如。 string.Empty 或根据用户单击的内容在对话框中选择的任何内容?
我使用 NUnit 作为测试框架。
一种选择是将不可测试的 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 组件中的 mocking/faking。然而,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;
}
}
并创建一个用于测试的假版本,它只是 returns 传递给其构造函数的值:
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));
}
但这可能有点夸张,除非您也在代码的其他地方创建大量 FolderBrowserDialog
。
感谢 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; }
}
}
在您将在注入框架中指定的正常条件下,构造函数注入将使用上述 class 中的普通 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 的回答对我很有帮助。
我有以下 class:
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;
}
}
我将如何验证它 returns 是正确的数据? 例如。 string.Empty 或根据用户单击的内容在对话框中选择的任何内容?
我使用 NUnit 作为测试框架。
一种选择是将不可测试的 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 组件中的 mocking/faking。然而,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;
}
}
并创建一个用于测试的假版本,它只是 returns 传递给其构造函数的值:
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));
}
但这可能有点夸张,除非您也在代码的其他地方创建大量 FolderBrowserDialog
。
感谢 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; }
}
}
在您将在注入框架中指定的正常条件下,构造函数注入将使用上述 class 中的普通 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 的回答对我很有帮助。