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

Fre*_*oot 6 java unit-testing junit4 jersey-test-framework

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

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

public class Base {

    protected static ChildType myType = ChildType.Invalid;
    ...
}    
Run Code Online (Sandbox Code Playgroud)

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

static {
    myType = ChildType.ChildA;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

bool result = ChildA.methodTwo();
Run Code Online (Sandbox Code Playgroud)

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

细节

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

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

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

@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() );
}
Run Code Online (Sandbox Code Playgroud)

所有GET测试都可以工作,并且所有这些测试都会调用静态初始化程序.只是这个POST失败了,只有在运行JUnit测试时才会失败.

das*_*ght 5

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

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

依靠静态初始化器来设置基类中的静态字段非常脆弱,因为多个子类“竞争”基类中的单个字段。这将基类的行为“锁定”为最后运行初始化程序的子类所需的行为。除了其他不好的事情之外,它否认了将多个子类与类一起使用的可能性Base,并且使得ChildA.methodTwo()运行为ChildB.methodTwo(). 事实上,没有ChildA.methodTwo()and 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) {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

现在调用者需要改变

ChildA.method1("hello");
ChildA.method2(42, "world");
Run Code Online (Sandbox Code Playgroud)

Base.method1(ChildA.class, "hello");
Base.method2(ChildA.class, 42, "world");
Run Code Online (Sandbox Code Playgroud)

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

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
}
Run Code Online (Sandbox Code Playgroud)

并打电话

ChildA.getInstance().method1("hello");
ChildA.getInstance().method2(42, "world");
Run Code Online (Sandbox Code Playgroud)

  • @Frecklefoot 唉,Java 中没有静态继承,因此没有 OOP 或继承的优雅,只有不可靠代码的烦恼。单例方法(答案底部的方法)可能效果更好,因为现在您可以让派生类重写基类的方法。 (2认同)

Fre*_*oot 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

由于我无法重写静态方法,因此基类中的方法版本具有不同的签名(methodTwoBase()而不是methodTwo)。我将其作为常规应用程序和 JUnit 进行了尝试,它是双向的。

这是一个有趣的问题,我责怪 JUnit。感谢所有的投入!