【问题标题】:JMockit: Singleton class, Order of testJMockit:单例类,测试顺序
【发布时间】:2014-10-30 08:04:52
【问题描述】:

我有一个单例类要测试:

public class Singleton {
    private static Singleton instance;

    private List<String> list;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }

    public boolean methodOne() {
        if (list == null) {
            list = new ArrayList<String>();
            list = SomeClass.fillListOne();
        }
        return SomeClass.verifyList(list);
    }

    public boolean methodTwo() {
        if (list == null) {
            list = new ArrayList<String>();
            list = SomeClass.fillListTwo();
        }
        return SomeClass.verifyList(list);
    }
}

使用以下测试类:

@RunWith(JMockit.class)
public class SingletonTest {
    @Test
    public void testOne(final @Mocked SomeClass someClass) {
        Singleton.getInstance().methodOne();
        new Verifications() {
            {
                SomeClass.fillListOne();
            }
        };

    }

    @Test
    public void testTwo(final @Mocked SomeClass someClass) {
        Singleton.getInstance().methodTwo();
        new Verifications() {
            {
                SomeClass.fillListTwo();
            }
        };
    }
}

如果我只执行“testOne”或只执行“testTwo”,则测试通过。如果我执行所有测试,它只会通过第一个执行的方法。如何将“list”属性设置为 null,例如在 @Before 方法中?如何在没有设置器的情况下使用单例或私有成员的解封装?

【问题讨论】:

  • 仅供参考:getInstance 方法非常不安全。您不能在空检查内部进行同步并期望它是安全的。线程 A 进来并检查实例是否为空,它不是,所以它继续。上下文切换,线程 B 进来检查实例是否为空,它仍然不是,所以它继续。线程B获取锁,初始化一些Object,分配给instance(保证按照synchronized的要求分配和初始化),释放锁。上下文切换,线程 A 现在获取锁并执行相同操作,覆盖 ThreadB 的实例。
  • Java 并发开发新手的推荐读物:cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html(你犯了第一个错误,这里详述)
  • 澄清:“(保证按照synchronized的要求进行赋值和初始化)”,意思是在释放锁的时候,保证对象被赋值和初始化。根据 JMM,同步块内部发生的顺序是不确定的。为了进一步澄清,您的示例确实成为该问题的牺牲品,但也成为我最初示例的牺牲品(这与 JMM 无关,但知道 JMM 如何处理此问题将减轻您对该问题的“解决方案”,链接 I发布了为什么它不起作用的详细信息)。
  • 感谢 searchengine27,但问题不在于单例和同步。正如我在较早的评论中所写,我无法编辑原始课程。

标签: java junit singleton private-members jmockit


【解决方案1】:

该类不是特别容易受到测试的影响。如果您可以选择,我会敦促您重写它。 (使用模拟的静态方法调用进行测试是一场维护噩梦,而且我也普遍不喜欢单例的所有东西——它们似乎无处不在,不管它们是否应该使用)。

无论如何,the documentation suggests 你可以这样做(我还添加了一个额外的字段以提高可读性,尽管你不必这样做):

@RunWith(JMockit.class)
public class SingletonTest {
    private Singleton instance;

    @Before
    public void initialise() {
        Deencapsulation.setField(Singleton.class, "instance", null);
        instance = Singleton.getInstance();
    }

    @Test
    public void testOne(final @Mocked SomeClass someClass) {
        instance.methodOne();
        new Verifications() {
            {
                SomeClass.fillListOne();
            }
        };
    }
    // ...other tests...
}

【讨论】:

  • 谢谢!我编写的类是我无法编辑的更复杂类的简化。我不想将实例设置为空,只设置列表。所以我编辑你的初始化方法:instance = Singleton.getInstance();解封装.setField(instance, "list", null);
  • @borras 是的,去过那里,这就是像上面这样令人讨厌的反射东西变得有用的地方。至于重置list——如果你重置整个instance,你的测试会更加健壮,而不仅仅是其中的一部分(我认为运行资源不是问题)
【解决方案2】:

我发现现有答案有一个问题:即使 SomeClass 的方法没有被调用,测试也通过了。这违背了测试的目的:它应该通过当且仅当方法 SomeClass.fillListOne() 和 SomeClass.fillListTwo() 被调用。

这是使用 JMockit 验证调用的方法,使用依赖项 SomeClass 上的 NonStrictExpectations 返回 null。我已经使用问题中提供的相同示例对其进行了测试,当没有进行调用时它会失败并在调用时通过:

@RunWith(JMockit.class)
public class SingletonTest {
    // capturing ensures Jmockit will mock all SomeClass instances.
    @Capturing
    private SomeClass someClass;

    @Before
    public void prepareTests() {
        new NonStrictExpectations() {{
            SomeClass.fillListOne(); result = null;
            SomeClass.fillListTwo(); result = null;
        }};
    }

    @Test
    public void testOne() {
        Singleton.getInstance().methodOne();
        new Verifications() {{
            SomeClass.fillListOne();
        }};
    }

    @Test
    public void testTwo() {
        Singleton.getInstance().methodTwo();
        new Verifications() {{
            SomeClass.fillListTwo();
        }};
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-05-04
    • 1970-01-01
    • 2014-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多