该方法与多个关注点紧密耦合。 OpenFileDialog 是 UI 问题,File 是 IO 问题。这使得单独测试该方法的功能变得困难但并非不可能。
将这些关注点提取到它们自己的抽象中。
public interface IOpenFileDialog {
string Filter { get; set; }
bool? ShowDialog();
string FileName { get; set; }
}
public interface IFileSystem {
string ReadAllText(string path, Encoding encoding = Encoding.UTF8);
}
我还建议将该静态方法转换为服务方法
public interface ITextFileService {
Tuple<string, string> OpenTextFile();
}
它的实现将取决于其他抽象
public class TextFileService : ITextFileService {
readonly IOpenFileDialog openFileDialog;
readonly IFileSystem file;
public SUT(IOpenFileDialog openFileDialog, IFileSystem file) {
this.openFileDialog = openFileDialog;
this.file = file;
}
public Tuple<string, string> OpenTextFile() {
openFileDialog.Filter = "Text |*.txt";
bool? accept = openFileDialog.ShowDialog();
if (accept.GetValueOrDefault(false))
return Tuple.Create(file.ReadAllText(openFileDialog.FileName, Encoding.UTF8), openFileDialog.FileName);
else
return null;
}
}
依赖项的实现将包装它们各自的关注点。
这也将允许在单独测试其依赖项时模拟/替换所有抽象。
以下是基于上述建议使用 MSTest 和 Moq 测试方法的示例。
[TestMethod]
public void _OpenTextFile_Should_Return_TextContext_And_FileName() {
//Arrange
var expectedFileContent = "Hellow World";
var expectedFileName = "filename.txt";
var fileSystem = new Mock<IFileSystem>();
fileSystem.Setup(_ => _.ReadAllText(expectedFileName, It.IsAny<Encoding>()))
.Returns(expectedFileContent)
.Verifiable();
var openFileDialog = new Mock<IOpenFileDialog>();
openFileDialog.Setup(_ => _.ShowDialog()).Returns(true).Verifiable();
openFileDialog.Setup(_ => _.FileName).Returns(expectedFileName).Verifiable();
var sut = new TextFileService(openFileDialog.Object, fileSystem.Object);
//Act
var actual = sut.OpenTextFile();
//Assert
fileSystem.Verify();
openFileDialog.Verify();
Assert.AreEqual(expectedFileContent, actual.Item1);
Assert.AreEqual(expectedFileName, actual.Item2);
}