如何对抽象类进行单元测试:使用存根进行扩展?
我想知道如何对抽象类和扩展抽象类的类进行单元测试。
我是否应该通过扩展抽象类来测试它,将抽象方法存根,然后测试所有具体方法?然后只测试我重写的方法,并在单元测试中为扩展我的抽象类的对象测试抽象方法?
我是否应该有一个抽象测试用例,可用于测试抽象类的方法,并在测试用例中为扩展抽象类的对象扩展此类?
请注意,我的抽象类有一些具体的方法。
我想知道如何对抽象类和扩展抽象类的类进行单元测试。
我是否应该通过扩展抽象类来测试它,将抽象方法存根,然后测试所有具体方法?然后只测试我重写的方法,并在单元测试中为扩展我的抽象类的对象测试抽象方法?
我是否应该有一个抽象测试用例,可用于测试抽象类的方法,并在测试用例中为扩展抽象类的对象扩展此类?
请注意,我的抽象类有一些具体的方法。
有两种使用抽象基类的方式。
您正在专门化抽象对象,但所有客户端都将通过其基接口使用派生类。
您正在使用抽象基类来分解设计中对象中的重复,并且客户端通过自己的接口使用具体的实现。
解决方案 1 - 策略模式
如果您有第一种情况,那么您实际上有一个由派生类正在实现的抽象类中的虚方法定义的接口。
您应该考虑使其成为一个真实的接口,将抽象类更改为具体,并在其构造函数中采用此接口的实例。然后,派生类将成为此新接口的实现。
这意味着您现在可以使用新接口的模拟实例测试以前的抽象类,并通过现在的公共接口测试每个新实现。一切都很简单,可以测试。
解决方案 2
如果您有第二种情况,那么您的抽象类将用作帮助程序类。
看看它包含的功能。查看是否可以将其中的任何一个推送到正在操作的对象上,以最大程度地减少这种重复。如果您还剩下任何东西,请考虑使其成为您的具体实现在其构造函数中采用的帮助器类并删除其基类。
这再次导致简单且易于测试的具体类。
作为规则
支持简单对象的复杂网络,而不是复杂对象的简单网络。
可扩展的可测试代码的关键是小的构建块和独立的布线。
更新:如何处理两者的混合物?
可以有一个基类执行这两个角色...即:它有一个公共接口,并有受保护的帮助程序方法。如果是这种情况,则可以将帮助程序方法分解为一个类(scenario2),并将继承树转换为策略模式。
如果你发现你的基类直接实现一些方法,而其他方法都是虚拟的,那么你仍然可以将继承树转换为策略模式,但我也会把它作为一个很好的指标,表明职责没有正确对齐,可能需要重构。
更新2:抽象类作为垫脚石 (2014/06/12)
前几天我遇到了一个使用抽象的情况,所以我想探索一下为什么。
我们的配置文件有一个标准格式。此特定工具具有3个配置文件,全部采用该格式。我希望每个设置文件都有一个强类型的类,这样,通过依赖注入,一个类可以请求它关心的设置。
我通过拥有一个抽象基类来实现这一点,该基类知道如何分析公开这些相同方法的设置文件格式和派生类,但封装了设置文件的位置。
我本可以编写一个“设置文件分析器”,将 3 个类包装,然后将其委托给基类以公开数据访问方法。我选择不这样做,因为它会导致3个派生类,其中的委派代码比其他任何东西都多。
然而。。。随着此代码的发展,每个设置类的使用者变得更加清晰。每个设置用户都会要求一些设置并以某种方式转换它们(由于设置是文本,因此它们可以将它们包装在将它们转换为数字等的对象中)。当这种情况发生时,我将开始将此逻辑提取到数据操作方法中,并将它们推送回强类型设置类。这将导致每组设置都有一个更高级别的界面,最终不再意识到它正在处理“设置”。
此时,强类型设置类将不再需要公开基础“设置”实现的“getter”方法。
在这一点上,我不再希望他们的公共接口包含设置访问器方法;所以我将更改这个类来封装一个设置解析器类,而不是从它派生。
因此,Abstract类是:一种我目前避免委派代码的方法,以及代码中的一个标记,提醒我稍后更改设计。我可能永远无法到达它,所以它可能会活得好一会儿......只有代码才能分辨。
我发现这在任何规则中都是如此...如“无静态方法”或“无私有方法”。它们在代码中表示气味...这很好。它让你寻找你错过的抽象......并让您同时继续为客户提供价值。
我想象像这样的规则定义了一个景观,其中可维护的代码存在于山谷中。当你添加新的行为时,就像雨水落在你的代码上一样。最初你把它放在它落地的地方。然后你重构,让好设计的力量推动行为,直到它最终进入山谷。
编写一个 Mock 对象,并仅将其用于测试。它们通常非常非常小(从抽象类继承),而不是更多。然后,在单元测试中,可以调用要测试的抽象方法。
您应该测试包含一些逻辑的抽象类,就像您拥有的所有其他类一样。