我有这个方法签名:List<ITMData> Parse(string[] lines)
ITMData
有35个属性。
你如何有效地测试这样的解析器?
问题:
编辑
我将方法签名更改为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");
}
编辑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(???);
...
}
答案 0 :(得分:4)
我应该加载整个文件(我可以使用System.IO)吗?
如果你这样做,它不再是单元测试 - 它就变成了集成或回归测试。如果您希望它显示单元测试不会出现的错误,则可以执行此操作。但那不太可能。
你可能最好进行单元测试,至少要开始。
我应该将文件中的一行放入字符串常量吗?
如果您打算编写多个使用相同输入行的测试,那么请确保。但就个人而言,我可能倾向于编写一堆不同的测试,每个测试都传递不同的输入字符串。那时,没有太多理由建立一个常量(除非它是一个局部常量,在测试方法中声明)。
我应该测试一条或多条线吗?
您没有指定,但我会假设您的输出是一对一的输入 - 也就是说,如果您传入三个字符串,您将获得三个ITMData
s返回。在这种情况下,对多线测试的需求将受到限制。
几乎总是值得测试退化情况,在这种情况下,它将是一个空字符串数组(零行)。并且至少有一个测试有多个行可能是值得的,这样你就可以确保迭代中没有愚蠢的错误。
但是,如果您的输出与您的输入是一对一的,那么您真的有另一种想要离开的方法 - 您应该使用ParseSingleLine
方法。那么你的Parse
只不过是迭代行并调用ParseSingleLine
。您仍然需要为Parse进行一些测试,但大多数测试都会关注ParseSingleLine
。
答案 1 :(得分:2)
如果我遇到这样的问题,我通常会这样做:
事先提出一个简短的免责声明:我认为我会更倾向于“集成测试”或“整体测试解析器”而不是测试单个行。在过去,我不止一次遇到过大量实现细节泄露到我的测试中并迫使我在更改实现细节时经常更改测试的情况。我猜是典型的过度指定的情况; - /
答案 2 :(得分:1)
我通常会尝试考虑常见的成功和失败情况,以及边缘情况。要求也有助于设置适当的用例。考虑Pex来列举各种场景。
答案 3 :(得分:0)
关于你的新问题:
我应该测试ITMData的每个属性还是应该测试整个对象?
如果您想要安全起见,您应该至少进行一次测试,检查每个属性是否匹配。
我的测试命名怎么样?
关于此主题的讨论很多,例如this one。一般规则是您在单元测试类中有多个方法,每个方法都旨在测试特定的东西。在你的情况下,它可能是这样的:
public void Check_All_Properties_Parsed_Correctly(){.....}
public void Exception_Thrown_If_Lines_Is_Null(){.....}
public void Exception_Thrown_If_Lines_Is_Wrong_Length(){.....}
因此,换句话说,测试您认为解析器“正确”的确切行为。完成此操作后,您在更改解析器代码时会感到更加放松,因为您将拥有一个全面的测试套件来检查您是否没有破坏任何内容。记得经常进行实际测试,并在进行更改时保持测试更新!在MSDN上有关于单元测试和测试驱动开发的相当好的指南。
一般来说,我认为您可以通过Google搜索找到大多数问题的答案。还有一些关于测试驱动开发的优秀书籍,它们不仅会推动TDD的如何,还会推动为什么。如果你是相对编程语言不可知的,我会推荐Kent Beck的Test Driven Development By Example,否则就像Test-Driven Development in Microsoft .NET。这些应该可以让你很快走上正轨。
编辑:
我是否过度指定我的测试?
在我看来,是的。具体来说,我不同意你的下一行:
如果我测试一个类中的所有属性,我会丢失此信息。
你究竟以什么方式丢失信息?假设有两种方法可以进行此测试,除了每次测试都有一个新类:
CheckPropertyX
,CheckPropertyY
等。运行测试时,您将确切地看到哪些字段已通过,哪些字段失败。这显然满足了你的要求,虽然我认为它仍然有点过分。我会选择选项2: Assert.AreEqual("test1", myObject.PropertyX, "Property X was incorrectly parsed");
Assert.AreEqual("test2", myObject.PropertyY, "Property Y was incorrectly parsed");
当其中一个失败时,您将知道哪一行失败。修复相关错误并重新运行测试后,您将看到是否有其他属性失败。这通常是大多数人采用的方法,因为每个属性创建一个类甚至方法会导致代码过多,并且需要做太多工作才能保持最新。