静态初始化程序在JUnit测试期间不运行

时间:2017-10-24 15:26:10

标签: java unit-testing junit4 jersey-test-framework

我在这里有一个有趣的JUnit问题(JUnit 4.12)。我有一个只有静态方法的基类。它们必须是静态的,因为它们的使用方式。我从基类继承了其他类。因此,如果基类为Base,则我们有ChildAChildB

大多数方法都包含在基类中,但它必须知道它实际上是哪个子项(只是调用方法,因为基类是无效的)。这是通过基类中的静态数据成员完成的:

public class Base {

    protected static ChildType myType = ChildType.Invalid;
    ...
}    

每个子项都通过静态初始化程序设置数据成员,因此:

static {
    myType = ChildType.ChildA;
}

然后在调用方法时,基类知道它是什么类型并加载适当的配置(类型实际上是配置名称)。

这在运行应用程序时非常有效。在调试器和日志消息中逐步执行它,我可以看到设置了适当的类型,并且方法根据子类型加载适当的配置。

使用JUnit时出现问题。我们有一些JUnit测试来测试每个基类方法。由于仅在基类上调用方法无效,因此我们在子类上调用方法,因此:

bool result = ChildA.methodTwo();

这个'总是失败''为什么?永远不会调用静态初始化程序。将代码作为应用程序运行时,它会被调用,每个人都很高兴。当我将其作为JUnit测试运行时,将跳过静态初始化程序,并且方法具有无效数据。什么是JUnit跳过静态初始化程序?有办法吗?

详情

实际上,我们并未像上面发布的那样调用该方法。我只想让这个例子尽可能清楚。实际上,我们有一个用Jersey框架编写的Web服务。调用的方法是REST端点之一。

@POST
@Produces(MediaType.TEXT_PLAIN)
public String methodPost() {
    ...
    return new String( itWorked ? "success" : "fail" );
}

我们这样称呼它(对于丑陋的语法感到抱歉,这只是它的工作方式):

@Test
public void testThePost() throws Exception {

    javax.ws.rs.core.Response response = target("restapi/").request().post(Entity.entity(null, MediaType.TEXT_PLAIN));

    assertEquals( 200, response.getStatus() );
}

所有GET测试都有效,并且所有这些测试都会调用静态初始化程序。只是这个POST失败了,只有在运行JUnit测试时才会发生。

3 个答案:

答案 0 :(得分:4)

您正在尝试为静态方法实现多态行为,静态方法是其他编程语言中存在的语言功能,但在Java中缺失。

  

[myType]是基类的受保护成员

依赖静态初始化器来设置基类中的静态字段非常脆弱,因为多个子类“竞争”基类中的单个字段。这会将基类的行为“锁定”到初始化程序最后运行的子类所需的行为中。除了其他不好的事情之外,它还拒绝使用多个子类和Base类,并使ChildA.methodTwo()能够运行为ChildB.methodTwo()设计的功能。实际上,没有ChildA.methodTwo()ChildB.methodTwo(),只有Base.methodTwo()依赖于静态初始化序列为其准备的信息。

这个问题有几种解决方案。一种可能性是将Class<Child###>对象传递给基类的方法:

class Base {
    public static void method1(Class childConfig, String arg) {
        ...
    }
    public static void method2(Class childConfig, int arg1, String arg2) {
        ...
    }
}

现在呼叫者需要更改

ChildA.method1("hello");
ChildA.method2(42, "world");

Base.method1(ChildA.class, "hello");
Base.method2(ChildA.class, 42, "world");

另一个解决方案是用非静态替换静态实现,并将“常规”多态行为与派生类中创建的单例结合使用:

class Base {
    protected Base(Class childConfig) {
        ...
    }
    public void method1(String arg) {
        ...
    }
    public void method2(int arg1, String arg2) {
        ...
    }
}
class ChildA extends Base {
    private static final Base inst = new ChildA();
    private ChildA() {
        super(ChildA.class);
    }
    public static Base getInstance() {
        return inst;
    }
    ... // Override methods as needed
}
class ChildB extends Base {
    private static final Base inst = new ChildB();
    private ChildB() {
        super(ChildB.class);
    }
    public static Base getInstance() {
        return inst;
    }
    ... // Override methods as needed
}

并致电

ChildA.getInstance().method1("hello");
ChildA.getInstance().method2(42, "world");

答案 1 :(得分:2)

所有访问者之间只共享一个Base.myType字段:BaseChildAChildB。以下事件序列可能导致您看到的失败:

  • JUnit测试调用ChildA.methodOne()开始执行,导致JVM类加载器加载ChildA.class并执行其static初始化程序块,将Base.myType设置为ChildType.ChildA,< / LI>
  • JUnit测试调用ChildB.methodOne()开始执行,导致JVM类加载器加载ClassB.class并执行 static初始化程序块,将Base.myType设置为ChildType.ChildB,然后
  • JUnit测试调用ChildA.methodTwo()开始执行,而不是首先执行ChildA static初始化程序块,因为ChildA已经由JVM类加载器加载,导致JUnit测试失败,因为Base.myType(因此ChildA.myType)目前等于ChildType.ChildB

基本设计问题是,部分代码要求子类型拥有myType字段,但该字段实际上是所有子类型共享的。

请提供运行JUnit测试的顺序,以验证上述理论。谢谢!

附录:感谢您在评论中澄清您只有一个仅调用ChildA.methodTwo()的JUnit测试,该测试仅在Base中定义,而不是ChildA。发生的事情可能是JVM决定ChildA只是为了调用其父Base类的methodTwo()方法而不需要初始化。 @ShyJ为https://stackoverflow.com/a/13475305/1840078的父级和子级static 字段访问提供了非常好的解释。我相信你的JUnit测试中会发生类似的事情。

附录2 :下面是我的代码建模并在JUnit测试期间将myType所述问题的值ChildType.Invalid再现为当前最佳理解:

public enum ChildType {
    Invalid, ChildA
}

public class Base {
    protected static ChildType myType = ChildType.Invalid;

    public static boolean methodTwo() {
        return true;
    }
}

public class ChildA extends Base {
    static {
        myType = ChildType.ChildA;
    }
}

public class ChildATest {
    @org.junit.Test
    public void test() {
        boolean result = ChildA.methodTwo();
        System.out.println("result: " + result);
        System.out.println("Base.myType: " + Base.myType);
    }
}

执行ChildATest.test()的输出:

result: true
Base.myType: Invalid

答案 2 :(得分:0)

我决定尝试@Arkdiy建议并在子类中使用传递方法。

让我重申一下:代码,就像我拥有它一样,在作为应用程序运行时效果很好。只有通过JUnit运行才会失败

所以现在我有类似下面的内容:

public class BaseClass {

    protected static ChildType myType = ChildType.Invalid;

    ...

    public static boolean methodTwoBase() {
        ...
    }
}

public class ChildA extends BaseClass {

    public static boolean methodOne() {
        ...
    }

    public static boolean methodTwo() {

        myType = ChildType.ChildA;
        return methodTwoBase();
    }
}

public class ChildB extends BaseClass {

    public static boolean methodOne() {
        ...
    }

    public static boolean methodTwo() {

        myType = ChildType.ChildB;
        return methodTwoBase();
    }
}

由于我无法覆盖静态方法,因此基类中方法的版本具有不同的签名(methodTwoBase()而不是methodTwo)。我尝试将它作为常规应用程序和JUnit,并且它可以双向工作。

有点问题,我责怪JUnit。感谢所有的投入!