我想知道如何对抽象类进行单元测试,以及扩展抽象类的类.
我应该通过扩展它来测试抽象类,删除抽象方法,然后测试所有具体方法吗?然后只测试我覆盖的方法,并在单元测试中测试扩展我的抽象类的对象的抽象方法?
我是否应该有一个抽象的测试用例,可以用来测试抽象类的方法,并在我的测试用例中为扩展抽象类的对象扩展这个类?
请注意,我的抽象类有一些具体的方法.
有两种方法可以使用抽象基类.
您正在专门化您的抽象对象,但所有客户端都将通过其基本接口使用派生类.
您正在使用抽象基类来分解设计中对象内的重复,并且客户端通过自己的接口使用具体实现.
解决方案1 - 战略模式
如果您有第一种情况,那么您实际上有一个由派生类正在实现的抽象类中的虚方法定义的接口.
您应该考虑将其设置为一个真正的接口,将您的抽象类更改为具体,并在其构造函数中获取此接口的实例.然后,派生类将成为此新接口的实现.
这意味着您现在可以使用新接口的模拟实例以及通过现在公共接口的每个新实现来测试您之前的抽象类.一切都很简单,可以测试.
解决方案2
如果你有第二种情况,那么你的抽象类就是一个帮助类.
看看它包含的功能.查看是否可以将其中任何一个推送到正在操作的对象上以最小化此重复.如果你还有任何东西,那么看看它是否是一个帮助类,你的具体实现将在它们的构造函数中删除它们的基类.
这再次导致简单且易于测试的具体类.
作为规则
在简单的复杂对象网络上支持复杂的简单对象网络.
可扩展可测试代码的关键是小型构建块和独立布线.
更新:如何处理两者的混合物?
可以让基类执行这两种角色...即:它具有公共接口,并具有受保护的辅助方法.如果是这种情况,那么您可以将辅助方法分解为一个类(scenario2),并将继承树转换为策略模式.
如果您发现基类直接实现某些方法而其他方法是虚拟的,那么您仍然可以将继承树转换为策略模式,但我也会将其视为责任未正确对齐的良好指标,并且可能需要重构.
更新2:抽象类作为垫脚石(2014/06/12)
前几天我使用抽象的情况,所以我想探究原因.
我们的配置文件有标准格式.这个特殊工具有3个配置文件,全部采用该格式.我想为每个设置文件设置一个强类型类,因此,通过依赖注入,类可以请求它关心的设置.
我通过一个抽象基类来实现这一点,该基类知道如何解析设置文件格式和暴露相同方法的派生类,但封装了设置文件的位置.
我本可以编写一个"SettingsFileParser",将3个类包装起来,然后委托给基类来公开数据访问方法.我选择不这样做又因为这将导致3派生类更代表团在他们的代码比什么都重要.
但是......随着此代码的发展,每个设置类的使用者都变得更加清晰.每个设置用户都会要求一些设置并以某种方式对它们进行转换(因为设置是文本,它们可能将它们包装在将它们转换为数字的对象中等).在这种情况下,我将开始将此逻辑提取到数据操作方法中,并将它们推回到强类型设置类.这将为每组设置带来更高级别的界面,最终不再意识到它正在处理"设置".
此时,强类型设置类将不再需要暴露底层"设置"实现的"getter"方法.
那时我不再希望他们的公共接口包含设置访问器方法; 所以我将更改此类以封装设置解析器类而不是从它派生.
因此,Abstract类是:我现在可以避免委托代码的方法,以及代码中的标记,以提醒我稍后更改设计.我可能永远不会达到它,所以它可能活得很好......只有代码才能说明.
我发现任何规则都是如此......比如"没有静态方法"或"没有私有方法".它们表明代码中有异味......这很好.它让您一直在寻找您错过的抽象......并让您在同一时间继续为您的客户提供价值.
我想象这样的规则定义了一个景观,可维护的代码存在于山谷中.当您添加新行为时,就像降雨登陆您的代码一样.最初你把它放在任何地方......然后你重构以允许良好设计的力量推动行为,直到它们最终都在山谷中.
编写一个Mock对象并将其用于测试.它们通常非常非常小(继承自抽象类)而不是更多.然后,在单元测试中,您可以调用要测试的抽象方法.
您应该测试包含某些逻辑的抽象类,就像您拥有的所有其他类一样.
我为抽象类和接口做的是:我编写一个测试,它使用对象,因为它是具体的.但是在测试中没有设置X类型的变量(X是抽象类).这个测试类没有添加到测试套件中,而是它的子类,它有一个setup-method,用于将变量设置为X的具体实现.这样我就不会复制测试代码了.如果需要,未使用的测试的子类可以添加更多测试方法.
要在抽象类上专门进行单元测试,您应该为测试目的派生它,测试base.method()结果和继承时的预期行为.
您通过调用方法测试方法,因此通过实现它来测试抽象类...
如果您的抽象类包含具有业务价值的具体功能,那么我通常会通过创建一个用于存根抽象数据的测试双重来直接测试它,或者通过使用模拟框架为我执行此操作.我选择哪一个取决于我是否需要编写抽象方法的特定于测试的实现.
我需要执行此操作的最常见方案是当我使用模板方法模式时,例如当我构建某种可由第三方使用的可扩展框架时.在这种情况下,抽象类定义了我想要测试的算法,因此测试抽象基础比使用特定实现更有意义.
但是,我认为这些测试应该只关注实际业务逻辑的具体实现,这一点很重要; 你不应该单元测试抽象类的实现细节,因为你最终会进行脆弱的测试.
一种方法是编写一个与您的抽象类对应的抽象测试用例,然后编写子类您的抽象测试用例的具体测试用例.为原始抽象类的每个具体子类执行此操作(即,您的测试用例层次结构反映了您的类层次结构).请参阅junit recipies book中的测试界面:http://safari.informit.com/9781932394238/ch02lev1sec6 .
还可以在xUnit模式中看到Testcase Superclass:http://xunitpatterns.com/Testcase%20Superclass.html