使用OpenFileDialog c#的单元测试文件读取方法

时间:2017-04-09 22:36:22

标签: c# unit-testing tuples openfiledialog file-read

我有一个函数,它返回文本文件路径和文件内容:

public static Tuple<string, string> OpenTextFile()
{
    OpenFileDialog openFileDialog = new OpenFileDialog();
    openFileDialog .Filter = "Text |*.txt";

    bool? accept = openFileDialog.ShowDialog();

    if (accept == true)
        return Tuple.Create(File.ReadAllText(openFileDialog.FileName, Encoding.UTF8), openFileDialog.FileName);
    else
        return null;
}

我如何单元测试文件读取?是否可以测试对话框显示?

4 个答案:

答案 0 :(得分:3)

That method is tightly coupled to multiple concerns. The OpenFileDialog is a UI concern and File is an IO concern. This makes testing the functionality of that method in isolation difficult but not impossible.

Extract those concerns into their own abstractions.

public interface IOpenFileDialog {
    string Filter { get; set; }
    bool? ShowDialog();
    string FileName { get; set; }
}


public interface IFileSystem {
    string ReadAllText(string path, Encoding encoding = Encoding.UTF8);
}

I would also suggest converting that static method into a service method

public interface ITextFileService {
    Tuple<string, string> OpenTextFile();
}

Its implementation would depend on the other abstractions

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

The implementations of the dependencies would then wrap their respective concerns.

This would also allow all the abstractions to be mocked/replaced when testing their dependents in isolation.

Here is an example of testing the method using MSTest and Moq based on the above suggestions.

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

答案 1 :(得分:2)

而不是直接测试跨越边界的UI,而不是抽象出UI。

或许: -

interface IFileSelector
{
   string Filter {get; set'}
   bool? SelectFile()
   string FileSelected
}

然后

public static Tuple<string, string> OpenTextFile(IFileSelector selector)
    {

        selector.Filter = "Text |*.txt";
        bool? accept = selector.SelectFile()
        if (accept == true)
            return Tuple.Create(File.ReadAllText(selector.FileSelected, Encoding.UTF8), selector.FileSelected);
        else
            return null;
    }

然后制作一个用户界面

public class WinformsFileSelector : IFileSelector
{
...
}

然后使用模拟框架进行测试,如Moq

答案 2 :(得分:1)

是的,您可以创建一个测试文件以在测试项目中读取。我通常把这样的东西放在一个名为Assets的文件夹中。

然后,您只需指定测试文件位置的确切路径,然后将其传递到您的函数中,并按照惯例使用断言进行验证,而不是OpenFileDialog。

但是我不认为这个功能需要单元测试。它的作用非常明确。

我觉得你用这个函数返回的元组就是你应该测试的东西,在这种情况下你的Unit测试应该只是手工创建元组,并用它来做你的逻辑。

IE你的测试可能会从以下开始:

TestFunction() {
    var TestString = "My Test data";
    var testTuple = new Tuple.Create(TestString, "Name");
    Assert.That(MyTupleLogic(testTuple), Is.Whatever());
}

你真的不需要测试微软的Tuple.Create和OpenFileDialog是否正常工作,这就是你建议的测试。

我只打扰在我输入逻辑的函数上运行单元测试

答案 3 :(得分:1)

Your method has two concerns: selecting a file in UI and reading it's content.

  • So at first you better split it in SelectTextFile and ReadAllText.
  • Then see that the second has actually no logic defined by you but just one .NET call. Not sure whether you'll have more value if you test it.
  • At second you have two boundaries here: UI and the File System, which are not under your control. If you have a lot of time and want to cover nearly 100% of code with unit tests, then, like already mentioned in other answers you abstract them away behind some interface methods: IFileDialog.SelectTextFile and IFileSystem.ReadAllText
  • Use your mocking library of choice, e.g. Moq, or direct implemented values to imitate some test cases