我想知道如何单元测试抽象类,以及扩展抽象类的类。
我是否应该通过扩展抽象类来测试它,去掉抽象方法,然后测试所有的具体方法?然后只测试我重写的方法,并在单元测试中测试扩展抽象类的对象的抽象方法?
我是否应该有一个抽象测试用例,可以用来测试抽象类的方法,并在测试用例中为扩展抽象类的对象扩展该类?
注意,我的抽象类有一些具体的方法。
我想知道如何单元测试抽象类,以及扩展抽象类的类。
我是否应该通过扩展抽象类来测试它,去掉抽象方法,然后测试所有的具体方法?然后只测试我重写的方法,并在单元测试中测试扩展抽象类的对象的抽象方法?
我是否应该有一个抽象测试用例,可以用来测试抽象类的方法,并在测试用例中为扩展抽象类的对象扩展该类?
注意,我的抽象类有一些具体的方法。
当前回答
遵循@patrick-desjardins的回答,我实现了抽象和它的实现类@Test,如下所示:
抽象类- ABC.java
import java.util.ArrayList;
import java.util.List;
public abstract class ABC {
abstract String sayHello();
public List<String> getList() {
final List<String> defaultList = new ArrayList<>();
defaultList.add("abstract class");
return defaultList;
}
}
由于抽象类不能被实例化,但它们可以被子类化,具体类DEF.java如下:
public class DEF extends ABC {
@Override
public String sayHello() {
return "Hello!";
}
}
测试类来测试抽象方法和非抽象方法:
import org.junit.Before;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.contains;
import java.util.Collection;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;
import org.junit.Test;
public class DEFTest {
private DEF def;
@Before
public void setup() {
def = new DEF();
}
@Test
public void add(){
String result = def.sayHello();
assertThat(result, is(equalTo("Hello!")));
}
@Test
public void getList(){
List<String> result = def.getList();
assertThat((Collection<String>) result, is(not(empty())));
assertThat(result, contains("abstract class"));
}
}
其他回答
使用抽象类的主要动机之一是在应用程序中启用多态性——即:您可以在运行时替换不同的版本。事实上,这与使用接口非常相似,只是抽象类提供了一些常见的管道,通常称为模板模式。
从单元测试的角度来看,有两件事需要考虑:
抽象类与相关类的交互。对于这种情况,使用模拟测试框架是理想的,因为它表明您的抽象类可以很好地与其他类协作。 派生类的功能。如果您为派生类编写了自定义逻辑,则应该单独测试这些类。
编辑:RhinoMocks是一个很棒的模拟测试框架,它可以通过动态地从您的类派生在运行时生成模拟对象。这种方法可以为您节省大量手工编写派生类的时间。
抽象基类有两种使用方式。
您正在专门化抽象对象,但所有客户端都将通过其基接口使用派生类。 您正在使用抽象基类来排除设计中对象中的重复,而客户端通过自己的接口使用具体实现。
1策略模式的解决方案
如果您有第一种情况,那么您实际上有一个由派生类正在实现的抽象类中的虚方法定义的接口。
您应该考虑使其成为一个真实的接口,将抽象类更改为具体类,并在其构造函数中获取该接口的实例。然后派生类就成为这个新接口的实现。
这意味着您现在可以使用新接口的模拟实例测试以前的抽象类,并通过现在的公共接口测试每个新实现。一切都是简单且可测试的。
解决方案2
如果您有第二种情况,那么您的抽象类将作为辅助类工作。
看看它包含的功能。看看是否可以将其中的任何一部分推到被操纵的对象上,以最小化复制。如果您还有剩余的东西,考虑将其作为一个helper类,您的具体实现使用它的构造函数并删除它们的基类。
这再次导致了简单且易于测试的具体类。
一般来说
喜欢简单对象的复杂网络,而不是复杂对象的简单网络。
可扩展可测试代码的关键是小的构建块和独立的连接。
更新:如何处理两者的混合?
有可能有一个基类同时扮演这两个角色……即:它有一个公共接口,并有受保护的helper方法。如果是这种情况,那么您可以将辅助方法分解为一个类(场景2),并将继承树转换为策略模式。
如果您发现一些方法是基类直接实现的,而另一些方法是虚的,那么您仍然可以将继承树转换为策略模式,但我也认为这是一个很好的指标,说明职责没有正确对齐,可能需要重构。
更新2:抽象类作为垫脚石(2014/06/12)
前几天我遇到了一个使用抽象的情况,所以我想探讨一下为什么。
我们的配置文件有一个标准格式。这个特殊的工具有3个配置文件都是这种格式。我希望每个设置文件都有一个强类型类,这样通过依赖注入,类就可以请求它所关心的设置。
我通过一个抽象基类来实现这一点,它知道如何解析设置文件格式和派生类,这些派生类公开了这些相同的方法,但封装了设置文件的位置。
我本可以编写一个由3个类包装的“SettingsFileParser”,然后委托给基类以公开数据访问方法。我选择不这样做,因为这将导致3个派生类,其中的委托代码比其他任何类都多。
However... as this code evolves and the consumers of each of these settings classes become clearer. Each settings users will ask for some settings and transform them in some way (as settings are text they may wrap them in objects of convert them to numbers etc.). As this happens I will start to extract this logic into data manipulation methods and push them back onto the strongly typed settings classes. This will lead to a higher level interface for each set of settings, that is eventually no longer aware it's dealing with 'settings'.
此时,强类型设置类将不再需要公开底层“设置”实现的“getter”方法。
在这一点上,我将不再希望他们的公共接口包括设置访问器方法;所以我将改变这个类封装一个设置解析器类,而不是从它派生。
因此,对于我来说,抽象类是一种暂时避免委托代码的方法,也是代码中的一个标记,用来提醒我稍后更改设计。我可能永远也见不到它了,所以它可能会活很久……只有密码能告诉我们。
我发现这适用于任何规则……比如“没有静态方法”或者“没有私有方法”。它们表明代码中有一种气味……这很好。它让你不断寻找你错过的抽象概念……同时让你继续为你的客户提供价值。
我想象像这样的规则定义了一个景观,可维护的代码生活在山谷中。当你添加新的行为时,它就像雨水降落在你的代码上。一开始你把它放在它落地的地方。然后你重构,让优秀设计的力量推动行为,直到一切都在山谷中结束。
为了专门对抽象类进行单元测试,您应该出于测试目的派生它,test base.method()的结果和继承时预期的行为。
你通过调用一个方法来测试它,所以通过实现一个抽象类来测试它……
首先,如果抽象类包含一些具体的方法,我认为你应该这样做,考虑这个例子
public abstract class A
{
public boolean method 1
{
// concrete method which we have to test.
}
}
class B extends class A
{
@override
public boolean method 1
{
// override same method as above.
}
}
class Test_A
{
private static B b; // reference object of the class B
@Before
public void init()
{
b = new B ();
}
@Test
public void Test_method 1
{
b.method 1; // use some assertion statements.
}
}
编写一个Mock对象,并仅用于测试。它们通常是非常非常小的(继承自抽象类),而不是更多。然后,在你的单元测试中,你可以调用你想测试的抽象方法。
您应该测试包含一些逻辑的抽象类,就像您拥有的所有其他类一样。