如何使用MSpec有效地测试固定长度的平面文件解析器?

Roo*_*ian 5 c# parsing unit-testing flat-file mspec

我有这个方法签名: List<ITMData> Parse(string[] lines)

ITMData 有35个房产.

你会如何有效地测试这样的解析器?

问题:

  • 我应该加载整个文件(我可以使用System.IO)吗?
  • 我应该将文件中的一行放入字符串常量吗?
  • 我应该测试一条或多条线
  • 我应该测试ITMData的每个属性还是应该测试整个对象?
  • 我的测试命名怎么样?

编辑

我将方法签名更改为 ITMData Parse(string line).

测试代码:

[Subject(typeof(ITMFileParser))]
public class When_parsing_from_index_59_to_79
{
    private const string Line = ".........";
    private static ITMFileParser _parser;
    private static ITMData _data;

    private Establish context = () => { _parser = new ITMFileParser(); };

    private Because of = () => { _data = _parser.Parse(Line); };

    private It should_get_fldName = () => _data.FldName.ShouldBeEqualIgnoringCase("HUMMELDUMM");
}
Run Code Online (Sandbox Code Playgroud)

编辑2

我仍然不确定我是否应该每班只测试一个属性.在我看来,这允许我提供更多的规范信息,即当我从索引59解析单个行到索引79时,我得到fldName.如果我测试一个类中的所有属性,我会丢失此信息.我是否过度指定我的测试?

我的测试现在看起来像这样:

[Subject(typeof(ITMFileParser))]
public class When_parsing_single_line_from_ITM_file
{
    const string Line = ""

    static ITMFileParser _parser;
    static ITMData _data;

    Establish context = () => { _parser = new ITMFileParser(); };

    private Because of = () => { _data = _parser.Parse(Line); };

    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    ...

}
Run Code Online (Sandbox Code Playgroud)

小智 2

如果我遇到这样的问题,我通常会这样做:

提前声明一个简短的免责声明:我认为我更愿意走“集成测试”或“作为一个整体测试解析器”路线,而不是测试单独的行。过去,我不止一次面临过这样的情况:大量实现细节泄漏到我的测试中,并迫使我在更改实现细节时经常更改测试。我猜是过度规范的典型案例;-/

  1. 我不会在解析器中包含文件加载。正如 @mquander 建议的那样,我宁愿使用 TextReader 或 IEnumerable 作为输入参数。这将导致测试速度更快,因为您可以在内存中指定解析器输入,而不必接触文件系统。
  2. 我不太喜欢手动滚动测试数据,因此在大多数情况下,我使用嵌入式资源和 ResourceManager 通过 assembly.GetManifestResource() 直接从规范程序集中加载测试数据。我的解决方案中通常有一堆扩展方法来简化资源的读取(例如 TextReader TextResource.Load("NAME_OF_SOME_RESOURCE"))。
  3. 关于 MSpec:我使用每个文件一个类来解析。对于解析结果中测试的每个属性,我都有一个单独的 (It) 断言。这些通常是单行代码,因此额外的编码量并不大。就文档和诊断而言,恕我直言,这是一个巨大的优势,因为当属性未正确解析时,您可以直接看到哪个断言失败,而无需查看源代码或搜索行号。它也会出现在您的 MSpec 结果文件中。此外,您不会隐藏其他失败的断言(您修复一个断言只是为了看到规范在下一个断言的下一行上失败的情况)。这当然迫使您更多地考虑在规范中使用的措辞,但对我来说这也是一个巨大的优势,因为我是语言形成思维这一观点的支持者。换句话说,如果您不知道如何命名您的断言,那么您的规范或实现可能有问题。
  4. 关于解析器的方法签名:我不会返回像 List<T> 或数组这样的具体类型,我也建议不要返回可变的 List<T> 类型。您在这里基本上说的是:“嘿,您可以在我完成后修改解析结果”,这在大多数情况下可能是您不想要的。我建议返回 IEnumerable<T> (或者 ICollection<T> 如果您确实需要之后修改它)