[英]Is it considered bad practice to use InternalsVisibleTo for Unit Test Code?
[英]Is it bad practice to use reflection to do complex object assertion for a unit test?
我正在阅读这个主题 ,这是关于使用反射来测试私有变量...
但我的单元测试中没有这样的问题,我的代码完全可以测试。
唯一的问题是我想通了,对具有预期结果的复杂对象的每个属性进行断言时非常耗时; 特别是对于复杂对象的列表。
由于它是一个复杂的对象,除非我为每个对象实现IEquality
,否则执行正常的Assert.AreEqual
不会给我一个正确的结果。
但即使我这样做,也不会告诉我在断言期间哪个属性/字段的名称,期望值和实际值。
正确地说,我们手动将每个属性值放入一个列表并执行单个CollectionAssertion
,但这仍然非常耗时,并且当断言发生时它只告诉我元素值的索引不相等; 它不会告诉我属性名称。 这使得调试变得非常困难(我必须进入调试模式并查看集合中的元素)。
所以我想知道,如果我编写一个递归反射方法,它将对两个复杂对象进行断言,它将告诉我每个属性名称,期望值,实际值。
这是一种好的做法还是不好的做法?
我发现很多人甚至不会考虑反思,但它有它的位置。 它在性能,类型安全等方面肯定存在缺陷,正如其他海报所述,但我认为单元测试是一个很好的使用场所。 只要它成功完成。
当您不拥有在属性中使用的所有类型时,尝试在所有对象上强制执行等同实现会进入墙。 实现一百个迷你比较器类与手动写出断言一样耗时。
在过去,我编写了一个扩展方法,可以执行您所描述的操作:
我的测试在任何时候都不关心属性名称,因此重构重构并不重要。 实际上,会自动找到新属性,而忘记删除的属性。
我从来没有将它用于非常复杂的物体,但是它与我拥有的物体配合得很好,而不会减慢我的测试速度。
所以我认为在单元测试中可以随意使用Reflection。
编辑:我会尝试为你挖掘我的方法。
在我看来,使用Reflection不是一个很好的选择。 使用Reflection意味着我们在编译时失去了类型安全性。 而且,在使用Reflection之后,可能(通常)通过程序集的元数据进行不区分大小写的字符串搜索。 这导致性能下降。 考虑到这些方面,我认为拆分原始类型(如oleksii所推荐)是一种很好的方法。
另一种方法可以是使用纯访问器方法编写单独的测试,以测试单独的属性集。 这可能不适用于所有情况。 但是,在某些情况下确实如此。
例如:如果我有一个Customer类,我可以编写一个测试来检查Address-type字段; 我可以编写另一个测试来检查订单类型字段等等。
在正常情况下,您不应该需要反射来做与测试相关的任何事情 。 在回答您链接的问题时提到了这一点:
反思应该只是最后的手段
如果需要检查复杂对象是否相等, 请在单元测试中实现此类等式检查 。 纯粹用于单元测试目的的额外代码没有错:
public void ComplexObjectsAreEqual()
{
var first = // ...
var second = // ...
AssertComplexObjectsAreEqual(first, second);
}
private void AssertComplexObjectsAreEqual(ComplexObject first,
ComplexObject second)
{
Assert.That(first.Property1, Is.EqualTo(second.Property1),
"Property1 differs: {0} vs {1}", first.Property1, second.Property1);
// ...
}
您不应该将单元测试视为其他代码 。 如果需要编写某些东西以使它们更具可读性,清洁,可维护 - 写下来。 它与其他地方的代码相同。 您是否会通过生产代码中的反射来比较对象?
我想说使用反射进行简单的单元测试有很多正当理由。 引用https://github.com/kbilsted/StatePrinter
手动单元测试的问题
当我一遍又一遍地打字并重新输入时:Assert.This,Assert。那,......不禁想知道为什么计算机无法为我自动化这些东西。 所有那些不必要的打字需要时间并耗尽我的精力。
使用Stateprinter时,只要预期值和实际值不匹配,就会为您生成断言。
当代码更改时,例如通过向类添加字段,您需要在某些测试中添加断言。 但是,找到一个完全手动的过程。 在没有人对所有类进行完整概述的较大项目中,所需的更改不会在所有应该执行的地方执行。
将代码从一个分支合并到另一个分支时会出现类似的情况。 假设您将发布分支中的错误修复或功能合并到开发分支,我一遍又一遍地观察到代码被合并,所有测试都运行然后提交合并。 人们忘记重新访问并仔细检查整个测试套件,以确定开发分支上是否存在测试,而不是合并发生的分支上的测试,相应地调整这些测试。
使用Stateprinter时,会比较对象图而不是单个字段。 因此,当创建新字段时,所有相关测试都会失败。 您可以将打印调整到特定字段,但是您无法自动检测图表中的更改。
通过对测试类,测试方法和测试元素的标准命名的良好命名,您可以获得很长的成功。 但是,没有命名约定可以弥补断言创建的视觉混乱。 当索引用于从列表或词典中挑选元素时,会添加进一步的混乱。 并且在将它与for,foreach循环或LINQ表达式结合使用时,不要让我开始。
使用StatePrinter时,会比较对象图而不是单个字段。 因此,测试中不需要逻辑来挑选数据。
当我读下面的测试时。 想想这里真正重要的是什么
Assert.IsNotNull(result, "result");
Assert.IsNotNull(result.VersionData, "Version data");
CollectionAssert.IsNotEmpty(result.VersionData)
var adjustmentAccountsInfoData = result.VersionData[0].AdjustmentAccountsInfo;
Assert.IsFalse(adjustmentAccountsInfoData.IsContractAssociatedWithAScheme);
Assert.AreEqual(RiskGroupStatus.High, adjustmentAccountsInfoData.Status);
Assert.That(adjustmentAccountsInfoData.RiskGroupModel, Is.EqualTo(RiskGroupModel.Flexible));
Assert.AreEqual("b", adjustmentAccountsInfoData.PriceModel);
Assert.IsTrue(adjustmentAccountsInfoData.IsManual);
什么时候蒸馏我们想要表达的是什么
adjustmentAccountsInfoData.IsContractAssociatedWithAScheme = false
adjustmentAccountsInfoData.Status = RiskGroupStatus.High
adjustmentAccountsInfoData.RiskGroupModel = RiskGroupModel.Flexible
adjustmentAccountsInfoData.PriceModel = "b"
adjustmentAccountsInfoData.IsManual = true
当业务对象的字段数量增加时,相反的情况对于测试的可靠性来说也是如此。 是否覆盖了所有领域? 字段是否被错误地多次比较? 还是反对错误的领域? 当你必须在一个对象上做25个断言时,你就会知道痛苦,并且精心确保在正确的字段中检查正确的字段。 然后审稿人必须经历相同的练习。 为什么这不是自动化的?
使用StatePrinter时,会比较对象图而不是单个字段。 您知道所有字段都已覆盖,因为所有字段都已打印。
恕我直言,这是一个不好的做法,因为:
对我来说,这看起来好像是在试图堵塞一个洞,而不是解决问题。 为了解决这个问题,我可以建议将一个大而复杂的类分成一组较小的类。 如果您有许多属性 - 将它们分组到单个类中
这样的课
class Foo
{
T1 Prop1 {get; set;}
T2 Prop2 {get; set;}
T3 Prop3 {get; set;}
T4 Prop4 {get; set;}
}
会成为
class Foo
{
T12 Prop12 {get; set;}
T34 Prop34 {get; set;}
}
class T12
{
T1 Prop1 {get; set;}
T2 Prop2 {get; set;}
}
class T34
{
T3 Prop3 {get; set;}
T4 Prop4 {get; set;}
}
注意, Foo
现在只有一个属性(即“分组”表示)。 如果您可以以某种方式对属性进行分组,那么任何状态更改都将本地化为特定组 - 您的任务将变得更加简化。 然后,您可以断言“分组”属性等于预期状态。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.