结构平等以获得更好的测试

2021-05-05 20:58:44

如果你' ve读了一点关于单元测试,测试驱动的开发或其他类型的开发人员测试,你可能会遇到这样的短语:它常常认为是行为的意思 - 驱动的发展(BDD),而且'肯定是一个解释。我没有问题。我自己的Pluralsight课程在测试驱动的发展中显示出类似的技术。

然而,它是一个逻辑谬误,从而得出结论,你只能在大的情况下应用理想,但不是小。它&#39才能在系统的边界处用粗粒测试,但没有单位测试。

在单位级别可能更难以做,因为写入单元测试时,'重新靠近实施,所以说话。但是,在实施之前写测试可以帮助

[理论] [inlinedata("家庭")] [inlinedata("日历")] [inlinedata("预留和#34;)] public void withcontrollanduffix(字符串名称){ var sut = new urlbuilder(); var实际= sut.withcontroller(名称+"控制器"); var预期= sut.withcontroller(name);断言(预期,实际);}

它测试了ASP.NET核心URL Builder;特别是如何处理我遇到去年的控制器后缀问题。

它描述了初始URLBuilder对象(SUT)的两个单独投影之间的平等关系。

首先,使用可变的流畅生成器,测试会产生假阴性,因为别名会使断言是一个重叠的主张。然而,使用不可变的流利的建筑商,优雅地躲避了这个弹子:预期和实际是两个单独的物体。

[理论] [inlinedata("家庭")] [inlinedata("日历")] [inlinedata("预留和#34;)] public void withcontrollanduffix(字符串名称){ var sut = new urlbuilder(); var实际= sut.withcontroller(名称+"控制器"); var预期= sut.withcontroller(name); assert.equal(预期.Controller,实际.Controller);}

此变更而不是比较两个整个对象,而不是比较两个对象的控制器属性值。为了使这是为了编译,您必须公开一个实现细节:该类具有级别字段(此处公开为自动属性),可跟踪控制器名称。

我认为大多数面向对象的程序员'默认习惯是编写比较属性或类字段的断言,因为在C#和Java中,默认情况下对象仅具有参考平等。这导致原始的痴迷,这次在测试断言的背景下。

另一方面,结构平等使得简化和有意义的断言更容易。只是使用实际比较。

公共覆盖Bool等于(对象?obj){return obj是UrlBuilder Builder&& action == builder.action&& Controller == Builder.Controller&&平等偏差者<对象?> .default.equals(值,builder.values);}公共覆盖int gethashcode(){return hashcode.com净(动作,控制器,值);}

你可能会认为它是一个奇怪的选择,给出流利的建筑物结构平等,但为什么不呢?自It' s不可变,它完全安全,它使测试更容易。

我很少看到人们这样做。甚至具有功能编程的程序员甚至似乎似乎似乎将结构相等分类为完全与代数数据类型(ADTS)相关的东西。另一方面,UrlBuilder类没有看起来像ADT。毕竟,其公共API仅暴露行为,但没有数据:

公共密封类URLBuilder {公共URLBuilder()公共URLBuilder ()}

另一方面,当我给予不可变类结构平等时,我的门槛是单调的。结构平等只是让事情变得更容易。上述测试只是一个示例。结构平等使您可以测试行为而不是实现细节。在该示例中,行为可以表示为两个不同输入之间的平等关系。

虽然它看起来很奇怪或令人惊讶地提供流利的建造者结构平等,它真正同性恋到配备有几个基因族的简单记录类型。 (毕竟,我们已经知道生成器模式对子宫内膜的同构。)让' s明确使用f#。

键入URLBuilder = private {action:string选项;控制器:字符串选项;值:obj选项}

虽然其定义是私人的,但它仍然是代数数据类型。 F#中的记录自动具有结构平等,也是如此。

自IT' s私有,客户端代码可以' t使用普通语言构造来创建实例。相反,定义类型的模块必须提供客户端代码可以使用的API:

让extucturlbuilder = {action = none;控制器=无; value = none}让Firection action UB = {UB With Action = Some Action}允许使用控制器(Controller:String)UB = Let index = Controller.LastIndexOf("控制器" stringcomparison.ordinalignoreCase)Let NewController =如果0< index然后controller.remove(索引)else controller {ub withcler =一些newController}让假设值UB = {UB具有值=某些值}

[<理论>] [< inlinedata("家庭")>] [< inlinedata("日历")>] [< inlinedata("保留和#34;)>]让``withcontroller处理后缀withcontroller(名称+"控制器")让预期= sut |> withcontroller名称预计=!实际的

尽管URLBuilder没有外部可见数据,但它会自动具有结构平等。功能规划,实际上,比面向对象的编程更具测试友好。

您可以安全地给出不可变的物体结构平等。除其他优点外,它可以更轻松地编写测试。通过结构平等,您可以使用高级语言表达预期和实际结果之间的关系。

这些天,我不太关心问题是'正确的'代数数据类型。如果它'我不可变,我不必在提供结构平等之前思考它。