如何为读取文件并具有文件路径参数的方法创建单元测试

5

我知道这可能很简单,但是因为我刚开始学习单元测试,所有的可能性都让我有些困惑。

我有一个非常简单的方法,应该从文件中读取整数(文件中只包含整数,每个数字存储在新行中)。

public static IEnumerable<int> ReadIntegers(string file)
{
    using (var stream = File.OpenText(file))
    {
        string line;
        while ((line = stream.ReadLine()) != null)
        {
            yield return int.Parse(line);
        }
    }
}

在我继续之前,不要建议我重构方法,我已经找到了它,并且想学习如何为这个特定的输入参数和输出值编写单元测试。

我在另一个主题上发现有人建议在测试用例中创建一个测试文件,然后尝试将预期结果与实际结果进行比较,类似于以下内容:

 [Test]
 public void ShouldValidateReadingOfTheFile()
 {
     string path = @"E:\TestFile.txt";
     using (var sw = File.AppendText(path))
     {
         sw.WriteLine(1);
         sw.WriteLine(100);
     }
     var expected = new List<int>() { 1, 100 };
     var actual = TestProject.Merge.ReadIntegersFromFile(path);
     Assert.IsTrue(expected.SequenceEqual(actual));
 }

我想问一下,这是不是一个不好的做法?你有不同的方法吗?还有关于这个方法的不同测试,还有其他的想法吗。谢谢!


你表明你不想重构,但同时又询问这是否是一种不好的做法,是否有其他方法。你可以将文件行读取抽象为一个服务,以便在测试时进行模拟。实际实现从文件获取数据。尝试分离方法的职责,如所提供的建议。它试图做太多事情。 - Nkosi
1
在单元测试中,始终避免硬编码文件访问(最好完全避免)。另一台机器上可能没有F驱动器。有些甚至没有C驱动器或没有剩余空间。Luiso的内存流是一个很好的解决方案。当必须要使用文件时,您也可以使用部署项。使用模拟对象也是另一种解决方案。 - Stefan Steinegger
3个回答

3

我认为你可以这样进行单元测试,但我建议你也测试特殊情况。

例如:

  • null 作为文件名
  • 一个空文件
  • 有许多条目的大文件
  • 不仅在其中包含整数的文件

创建测试文件是一种常见做法。


2

您可以使用 typemock 框架对方法进行单元测试,而无需重构它,以下是您代码的示例:

[TestMethod, Isolated]
public void ShouldValidateReadingOfTheFile()
{
    var fileContents = 
@"1
100";
    StreamReader reader = new StreamReader(
        new MemoryStream(Encoding.ASCII.GetBytes(fileContents)));

    Isolate.WhenCalled(() => File.OpenText("fake")).WillReturn(reader);

    var result = Class1.ReadIntegers("fake").ToArray();

    var resList = new int[] {1, 100};
    CollectionAssert.AreEquivalent(resList, result);
}

我希望这能帮助您更好地理解单元测试。

2
我会将该方法分为两个部分。
public static IEnumerable<int> ReadIntegers(Stream file)
{
    ///... your code here
}

并且

public static IEnumerable<int> ReadIntegers(string file)
{
     var stream = File.OpenText(file);
     return ReadIntegers(stream)
}

而对于单元测试本身:

[Test]
public void ShouldValidateReadingOfTheFile()
{
    string stream = new MemoryStream();
    using (var sw = StreamWiter(stream))
    {
        sw.WriteLine(1);
        sw.WriteLine(100);
    }
    var expected = new List<int>() { 1, 100 };
    var actual = TestProject.Merge.ReadIntegersFromFile(path);
    Assert.IsTrue(expected.SequenceEqual(actual));
}

我所做的是用一个StreamWriter包装MemoryStream,这样就可以轻松地编写行并填充我想要的值。那应该就可以了。
然后你可以使用相同的策略来测试所有其他情况,并创建一个类似这样的帮助程序:
private Stream GetStream(IEnumerable<int> values)
{
    string stream = new MemoryStream();
    using (var sw = StreamWiter(stream))
    {
        foreach(int i in values)
            sw.WriteLine(i);
    }

    return stream;
}

或类似的事情。采用这种方式,您可以对执行实际工作的方法(带有 Stream 参数的方法)进行所有所需的测试,如果仍然需要测试文件打开(stringStream)代码,则可以在项目中使用测试文件进行测试,尽管我不认为这有什么意义,因为您将测试框架处理文件的能力,如果文件名来自于您的代码,则最好测试生成文件名的代码。希望这有所帮助。愉快的编码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接