假设你有一个具有一些任意属性的类:
class Data {
String a = '';
int b = 0;
bool c = false;
SomeObject d = SomeObject();
}
让我们也说某个地方你有一个函数要重置大多数但不是所有这个Data
对象的属性与那些与对象构造函数的默认值无关的属性。
Data data = Data();
...
void resetData() {
data = data
..a='reset'
..b=42
..c=true;
// We want to retain [d]'s state, for whatever reason.
}
你如何进行单元测试这种行为?
您可以进行单元测试,将Data
的每个属性设置为与重置的默认值完全不同的内容,并验证所有相关字段是否发生变化,但是1)非常脆弱2)违背了单元测试的目的对于。如果您添加了另一个应该重置的对象e
,但是您忘了将其添加到resetData
函数中,您几乎肯定忘记将其添加到单元测试中。然后,单元测试将不提供任何值,因为行为将被破坏,但您不会收到警报。
通过dart:mirrors
使用反射/内省是一个选项,通过测试每个对象的变量确实是不同的(除了d
),但dart:mirrors
不适用于AngularDart,因此用户保持高和干。
我也找不到任何可以通过用垃圾值播种对象来“模糊”对象的库,所以不完全确定如何继续(或者如果我甚至应该在这个看似愚蠢的单元测试中浪费我的时间)。
你的问题与问题的方向类似,是否应该对吸气剂和制定者进行单元测试(参见Should unit tests be written for getter and setters?)。从某种意义上说,方法resetData
的代码示例比setter更简单,因为属性是分配常量而不是参数值。
并且,测试相应属性的正确设置的单元测试将仅从代码中复制该值。此类测试发现错误的可能性很低。并且,让第二个开发人员查看测试并不比让第二个开发人员审查代码本身更好 - 代码审查甚至可以更好地利用开发时间。测试可能具有一些作为回归测试的价值,但同样,在大多数情况下,如果对代码进行了更改,则必须维护测试以便遵循代码更改。
因此,类似于getter和setter以及琐碎的构造函数,我建议不要为resetData
编写特定的测试,而是尝试通过测试resetData
对后续计算的影响来使resetData
成为某些(略微)更大的测试场景的一部分:
// Setup:
Data data = Data(); // Construction
... // some calculation on data (optional)
// Exercise:
resetData()
... // some calculation on data
// Verify:
...
毕竟,从用户的角度来看,应该有一个理由为什么resetData
会将这些属性赋予其特定值。这些以用户为中心的场景有助于进行有用的测试。 (名称resetData
,顺便说一句,违反了最少惊喜的原则,因为人们会认为它会将值重置为初始值。)
您描述的另一个问题(如果添加了新属性,单元测试不会告诉您没有更新resetData
)表明您对单元测试期望太多:这种现象不仅限于简单的功能。如果你添加一个属性而忘记更新一些复杂的函数来使用它,加上你按原样保留测试,那么测试也将继续通过。
您可以想到巧妙的技巧,比如,保持和比较当前编写方法时已知的属性列表与当前属性列表(使用内省获得),但这对我来说似乎有些过分 - 除非您正在开发安全关键代码,我认为dart
可能不适合。