我想知道如何对抽象类和扩展抽象类的类进行单元测试。
我应该通过扩展抽象类来测试抽象类,剔除抽象方法,然后测试所有具体方法吗?然后只测试我覆盖的方法,并在单元测试中测试扩展我的抽象类的对象的抽象方法?
我是否应该有一个可用于测试抽象类方法的抽象测试用例,并在我的测试用例中为扩展抽象类的对象扩展此类?
请注意,我的抽象类有一些具体的方法。
答案 0 :(得分:415)
答案 1 :(得分:244)
编写Mock对象并将其用于测试。它们通常非常非常小(继承自抽象类)而不是更多。然后,在单元测试中,您可以调用要测试的抽象方法。
您应该测试包含某些逻辑的抽象类,就像您拥有的所有其他类一样。
答案 2 :(得分:11)
我对抽象类和接口所做的工作如下:我编写了一个测试,它使用了具体的对象。但是在测试中没有设置X类型的变量(X是抽象类)。这个测试类没有添加到测试套件中,而是它的子类,它有一个setup-method,用于将变量设置为X的具体实现。这样我就不会复制测试代码了。如果需要,未使用测试的子类可以添加更多测试方法。
答案 3 :(得分:8)
要在抽象类上专门进行单元测试,您应该为测试目的派生它,测试base.method()结果和继承时的预期行为。
您通过调用方法测试方法,因此通过实现它来测试抽象类...
答案 4 :(得分:8)
如果您的抽象类包含具有业务价值的具体功能,那么我通常会通过创建一个存根抽象数据的测试双重来直接测试它,或者通过使用模拟框架为我做这个。我选择哪一个取决于我是否需要编写抽象方法的特定于测试的实现。
我需要这样做的最常见情况是当我使用Template Method pattern时,例如当我构建某种可由第三方使用的可扩展框架时。在这种情况下,抽象类定义了我想要测试的算法,因此测试抽象基础比使用特定实现更有意义。
但是,我认为这些测试应该只关注实际业务逻辑的具体实现;你不应该单元测试抽象类的实现细节因为你最终会进行脆弱的测试。
答案 5 :(得分:6)
一种方法是编写一个与您的抽象类对应的抽象测试用例,然后编写将抽象测试用例子类化的具体测试用例。为原始抽象类的每个具体子类执行此操作(即,您的测试用例层次结构镜像您的类层次结构)。请参阅junit收件簿中的测试界面:http://safari.informit.com/9781932394238/ch02lev1sec6。
还可以在xUnit模式中看到Testcase超类:http://xunitpatterns.com/Testcase%20Superclass.html
答案 6 :(得分:4)
我会反对“抽象”测试。我认为测试是一个具体的想法,并没有抽象。如果您有共同的元素,请将它们放在帮助方法或类中供所有人使用。
至于测试抽象测试类,请确保自问您正在测试的是什么。有几种方法,你应该找出适用于你的场景的方法。您是否尝试在子类中测试新方法?然后让您的测试仅与该方法交互。您是否正在测试基类中的方法?然后可能只为该类设置一个单独的夹具,并根据需要使用尽可能多的测试单独测试每个方法。
答案 7 :(得分:4)
这是我在设置测试抽象类的线束时经常遵循的模式:
public abstract class MyBase{
/*...*/
public abstract void VoidMethod(object param1);
public abstract object MethodWithReturn(object param1);
/*,,,*/
}
我在测试中使用的版本:
public class MyBaseHarness : MyBase{
/*...*/
public Action<object> VoidMethodFunction;
public override void VoidMethod(object param1){
VoidMethodFunction(param1);
}
public Func<object, object> MethodWithReturnFunction;
public override object MethodWithReturn(object param1){
return MethodWihtReturnFunction(param1);
}
/*,,,*/
}
如果在我不期望的情况下调用抽象方法,则测试失败。在安排测试时,我可以使用执行断言,抛出异常,返回不同值等的lambda简单地删除抽象方法。
答案 8 :(得分:3)
如果具体方法调用策略不起作用的任何抽象方法,并且您希望分别测试每个子类行为。否则,扩展它并如你所描述的那样对抽象方法进行存根应该没问题,只要抽象类具体方法与子类分离即可。
答案 9 :(得分:2)
我想你可能想要测试一个抽象类的基本功能......但是你可能最好通过扩展类而不重写任何方法,并对抽象方法进行最小化的模拟。
答案 10 :(得分:2)
使用抽象类的主要动机之一是在应用程序中启用多态 - 即:您可以在运行时替换不同的版本。实际上,这与使用接口非常相似,只是抽象类提供了一些常见的管道,通常称为模板模式。
从单元测试的角度来看,有两件事需要考虑:
您的抽象类与其相关类的交互。使用模拟测试框架是这种情况的理想选择,因为它表明您的抽象类可以很好地与其他人一起使用。
派生类的功能。如果您具有为派生类编写的自定义逻辑,则应该单独测试这些类。
编辑:RhinoMocks是一个非常棒的模拟测试框架,可以通过从您的类动态派生来在运行时生成模拟对象。这种方法可以为您节省无数小时的手工编码派生类。
答案 11 :(得分:2)
首先,如果抽象类包含一些具体的方法,我认为你应该这样做考虑这个例子
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import java.awt.Font;
public class menuFonts extends JFrame {
public menuFonts() {
setTitle("Menu Fonts");
setSize(300, 300);
JMenuBar fonts = new JMenuBar();
setJMenuBar(fonts);
JMenu menu_Face = new JMenu("Face");
JMenu menu_Size = new JMenu("Size");
JMenu menu_Color = new JMenu("Color");
fonts.add( menu_Face);
fonts.add(menu_Size);
fonts.add(menu_Color);
JMenuItem menu_Face_arial = new JMenuItem("Arial", 'A');
JMenuItem menu_Face_tahoma = new JMenuItem("Tahoma", 'T');
JMenuItem menu_Face_verdana = new JMenuItem("Verdana", 'V');
JMenuItem menu_Size_12 = new JMenuItem("12");
JMenuItem menu_Size_14= new JMenuItem("14");
JMenuItem menu_Size_16 = new JMenuItem("16");
JMenuItem menu_Color_red = new JMenuItem("Red", 'R');
JMenuItem menu_Color_blue = new JMenuItem("Blue", 'B');
JMenuItem menu_Color_green = new JMenuItem("Green", 'G');
menu_Face.add(menu_Face_arial);
menu_Face.add(menu_Face_tahoma);
menu_Face.add(menu_Face_verdana);
fonts.add(menu_Size);
menu_Size.add(menu_Size_12);
menu_Size.add(menu_Size_14);
menu_Size.add(menu_Size_16);
fonts.add(menu_Color);
menu_Color.add(menu_Color_red);
menu_Color.add(menu_Color_blue);
menu_Color.add(menu_Color_green);
}
public static void main(String[] args) {
menuFonts me = new menuFonts();
me.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
me.setVisible(true);
}
}
答案 12 :(得分:1)
根据@ 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!";
}
}
@Test 类来测试抽象方法和非抽象方法:
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"));
}
}
答案 13 :(得分:0)
如果抽象类适合您的实现,请测试(如上所述)派生的具体类。你的假设是正确的。
为了避免将来出现混淆,请注意这个具体的测试类不是模拟,而是假。
严格来说,模拟由以下特征定义: