Answers:
编写一个Mock对象,并将其仅用于测试。它们通常非常非常非常小(从abstract类继承),而没有更多。然后,在单元测试中,您可以调用要测试的抽象方法。
您应该测试包含某些逻辑的抽象类,就像您拥有的所有其他类一样。
有两种使用抽象基类的方式。
您正在专门化抽象对象,但是所有客户端都将通过其基本接口使用派生类。
您正在使用抽象基类来排除设计对象中的重复项,并且客户端通过其自己的接口使用具体的实现。
1的解决方案-策略模式
如果您遇到第一种情况,那么实际上您的派生类正在实现的抽象类中实际上有一个由虚拟方法定义的接口。
您应该考虑使其成为一个真实的接口,将抽象类更改为具体的,并在其构造函数中采用该接口的实例。然后,您的派生类将成为此新接口的实现。
这意味着您现在可以使用新接口的模拟实例并通过现在的公共接口测试每个新的实现,从而测试以前的抽象类。一切都很简单且可测试。
解决方案2
如果您遇到第二种情况,则您的抽象类将作为帮助程序类工作。
看一下它包含的功能。查看是否可以将其中任何一个推入正在操作的对象上以最大程度地减少此重复。如果还剩下什么,可以考虑将其作为帮助程序类,具体实现将这些帮助程序类纳入其构造函数并删除其基类。
这又导致了简单且易于测试的具体类。
通常
在复杂对象的简单网络上偏爱简单对象的复杂网络。
可扩展的可测试代码的关键是小的构建块和独立的布线。
更新:如何处理两者的混合?
可以让一个基类同时扮演这两个角色……即:它具有公共接口,并具有受保护的帮助程序方法。如果是这种情况,那么您可以将辅助方法分解为一个类(方案2),并将继承树转换为策略模式。
如果您发现您的基类有直接实现的某些方法而其他方法是虚拟的,那么您仍然可以将继承树转换为策略模式,但是我也将其作为很好的指示,说明职责未正确对齐,并且可能需要重构。
更新2:抽象类作为垫脚石(2014/06/12)
前几天我遇到一种情况,我使用摘要,所以我想探究原因。
我们的配置文件具有标准格式。此特定工具具有3个配置文件,所有配置文件均采用该格式。我希望为每个设置文件提供一个强类型的类,因此,通过依赖注入,一个类可以要求它关心的设置。
我通过拥有一个抽象的基类来实现此目的,该基类知道如何解析设置文件格式以及派生类,这些类公开了相同的方法,但封装了设置文件的位置。
我本可以编写3个类包装的“ SettingsFileParser”,然后委托给基类以公开数据访问方法。我选择不这样做又因为这将导致3派生类更代表团在他们的代码比什么都重要。
但是,随着代码的发展,这些设置类的使用者变得更加清晰。每个设置用户都会要求一些设置并以某种方式进行转换(由于设置是文本,因此他们可以将它们包装在将其转换为数字等的对象中)。发生这种情况时,我将开始将此逻辑提取到数据操作方法中,并将它们推回到强类型设置类中。这将导致每组设置都有一个更高级别的界面,最终不再意识到它正在处理“设置”。
此时,强类型设置类将不再需要用于公开基础“设置”实现的“获取”方法。
到那时,我不再希望它们的公共接口包含设置访问器方法。因此,我将更改此类以封装设置解析器类,而不是从其派生。
因此,Abstract类是:一种让我现在避免委托代码的方法,以及一种代码中的标记来提醒我以后更改设计。我可能永远也做不到,所以它可能会生存很长一段时间……只有代码能说明问题。
我发现这适用于任何规则,例如“没有静态方法”或“没有私有方法”。它们在代码中指示出一种气味...这很好。它使您始终寻找丢失的抽象……并使您在此期间继续为客户提供价值。
我想象像这样的规则定义了一个格局,可维护的代码生活在山谷中。当您添加新行为时,就像代码上的雨水一样。最初,您将其放置在其着陆的任何位置..然后,您进行重构以允许良好设计的力量将行为推向四周,直到一切最终落入谷底。
我对抽象类和接口所做的工作如下:我编写了一个测试,使用了具体的对象。但是在测试中未设置类型X的变量(X是抽象类)。该测试类未添加到测试套件中,而是其子类,其子类具有将变量设置为X的具体实现的设置方法。这样,我就不会重复测试代码。如果需要,未使用的测试的子类可以添加更多的测试方法。
要专门针对抽象类进行单元测试,应出于测试目的,测试base.method()结果和继承时的预期行为而派生它。
您通过调用方法来测试方法,因此通过实现它来测试抽象类...
如果您的抽象类包含具有商业价值的具体功能,那么我通常会通过创建一个存根抽象数据的测试双倍方法或通过使用模拟框架为我直接进行测试。我选择哪种方法在很大程度上取决于我是否需要编写特定于测试的抽象方法的实现。
我需要执行此操作的最常见情况是使用模板方法模式时,例如,当我构建某种可扩展的框架以供第三方使用时。在这种情况下,抽象类定义了我要测试的算法,因此测试抽象库比特定实现更有意义。
但是,我认为重要的是,这些测试应仅关注真实业务逻辑的具体实现;您不应该对抽象类的实现细节进行单元测试,因为最终会导致脆弱的测试。
一种方法是编写一个与您的抽象类相对应的抽象测试用例,然后编写子类化您的抽象测试用例的具体测试用例。对原始抽象类的每个具体子类执行此操作(即,测试用例层次结构反映了您的类层次结构)。请参阅junit收件人手册中的“测试接口”:http ://safari.informit.com/9781932394238/ch02lev1sec6 。
另请参见xUnit模式中的Testcase超类:http : //xunitpatterns.com/Testcase%20Superclass.html
我反对“抽象”测试。我认为测试是一个具体的想法,没有抽象的概念。如果您有共同的元素,请将它们放在辅助方法或类中,以供所有人使用。
至于测试抽象测试类,请确保问自己正在测试的是什么。有几种方法,您应该找出适合您的方案的方法。您是否正在尝试在子类中测试新方法?然后让您的测试仅与该方法交互。您是否正在测试基类中的方法?然后可能只为该类配备一个单独的固定装置,并根据需要进行多次测试,分别测试每个方法。
这是设置测试抽象类的工具时通常遵循的模式:
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来抽象方法。
首先,如果抽象类包含一些具体方法,我认为您应该考虑此示例
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.
}
}
按照@ patrick-desjardins的回答,我实现了abstract及其实现类@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;
}
}
由于不能实例化Abstract类,但可以将其抽象化,因此具体类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"));
}
}