JUnit测试方法调用之间模拟状态丢失

4

我不明白为什么test1()test2()相同却失败了。而另一个测试方法却成功了...

assertTrue(str.equals("hello"));会导致NPE。

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import junit.framework.TestCase;

class UnderTest {
    private static UnderTest instance;
    private ToMock toMock;

    public void doSomething() {
        String str = toMock.get();
        assertTrue(str.equals("hello"));
    }

    public static UnderTest getInstance() {
        if (instance == null) {
            instance = new UnderTest();
        }
        return instance;
    }

    public void set(ToMock toMock) {
        this.toMock = toMock;
    }

    public ToMock get() {
        return toMock;
    }
}

public class SomeTest extends TestCase {
    private ToMock toMock;
    private UnderTest underTest;
    private String str;

    public SomeTest() {
        toMock = mock(ToMock.class);

        doAnswer(new Answer<Void>() {
            public Void answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                str = (String) args[0];
                return null;
            }
        }).when(toMock).set((String) org.mockito.Mockito.any());

        when(toMock.get()).thenAnswer(new Answer<String>() {
            @Override
            public String answer(InvocationOnMock invocation) throws Throwable {
                return str;
            }
        });

        UnderTest.getInstance().set(toMock);
    }

    @Before
    public void setUp() throws Exception {
        //      UnderTest.getInstance().set(toMock);
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void test1() {
        toMock.set("hello");
        UnderTest.getInstance().doSomething();
    }

    @Test
    public void test2() {
        toMock.set("hello");
        UnderTest.getInstance().doSomething();
    }
}

下面的接口应该放在一个额外的文件中。否则,它将无法被Mockito模拟。
public interface ToMock {
    void set(String str);
    String get();
}

但是,一旦我取消注释:

@Before
public void setUp() throws Exception {
    //      UnderTest.getInstance().set(toMock);
}

两种方法都会成功。我不明白这个指令如何影响str字段。看起来在test1()test2()调用之间,str被设置为null。但是为什么?在我所知道的范围内,我不必调用setUp()来保留某些字段的当前值。在JUnit中,SomeTest的状态(包括str)不应该在测试方法调用之间丢失。

这可以怎样解释呢?


1
你为什么混淆JUnit版本?junit.framework.TestCaseorg.junit.* 有何不同? - Sotirios Delimanolis
@SotiriosDelimanolis 我刚刚将其限制为一个 - 具有相同结果的junit.framework.TestCase。 - ka3ak
2个回答

5
您正在混合使用JUnit 3和JUnit 4,不要这样做。请删除该

标签。

extends TestCase {

假设你的测试运行器使用JUnit 3来运行测试。
JUnit总是创建测试类的新实例来运行每个测试方法。
在JUnit 3中,运行器会在执行任何方法之前准备所有实例。在你的情况下,执行基本上像这样进行:
1. 实例化SomeTest,实例A 2. 在实例A上调用构造函数 3. 实例化ToMock,实例B,绑定到实例A,并将其注册到UnderTest单例中 4. 实例化SomeTest,实例C 5. 在实例C上调用构造函数 6. 实例化ToMock,实例D,绑定到实例C,并将其注册到UnderTest单例中,覆盖之前的内容 7. 在实例A上调用test1 8. 在ToMock实例B上调用set 9. 因为它存储在UnderTest单例中并通过getInstance检索,所以在ToMock实例D上调用doSomething
那个ToMock实例D还没有被调用set。相应的SomeTest实例C的str字段因此仍然为null。
使用JUnit 4,我们在创建实例和运行相应的测试之间交替进行。在你的测试案例中,UnderTest单例不会被错误的ToMock实例“污染”。

我已在构造函数和两个测试方法中设置了断点。控制流程与您描述的相同。但这不是很愚蠢和违反直觉吗?我的意思是,通常您期望构造函数只被调用一次。我可能一直使用JUnit 4。我从未考虑过版本之间的区别。不幸的是,项目中使用的是JUnit 3,因此我必须使用它。切换可能是可能的,但不会那么快。 - ka3ak
1
@ka3ak 这是JUnit的设计决策。请参阅Martin Fowler的文章,在这里。在您的情况下,不要在构造函数中将ToMock传递给您的单例,而是在每个测试或@Before中执行。 - Sotirios Delimanolis

1
任何对setup方法(@Before)和@After注释方法的更新都将在方法调用之间保留。
 @Before
public void executedBeforeEach() {
....
}

每次调用测试方法之前/之后都会调用此调用。如果这不足够,则考虑使用@BeforeClass注释。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接