【问题标题】:Using mock objects in multithreaded environment在多线程环境中使用模拟对象
【发布时间】:2019-01-28 15:22:53
【问题描述】:

从 jMock 2.6 开始,我可以通过

确保多个线程一致地看到我的模拟对象
final Mockery mockery = new Mockery();
mockery.setThreadingPolicy(new Synchroniser());

使用 jMock 2.5 时,我有哪些选择(我遇到间歇性测试“片状”)?

特别是,使用synchronized 包装所有模拟对象方法调用是否足够(更新:不,不适合期望)?

<T> T synchronizedMock(final T mock,
        final Class<T> clazz) {
    return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
            new Class[] {clazz, CaptureControl.class},
            (proxy, method, args) -> {
                synchronized (mock) {
                    return method.invoke(mock, args);
                }
            });
}

当使用上述方法时,我遇到任何死锁的机会有多大?

【问题讨论】:

    标签: java multithreading unit-testing concurrency jmock


    【解决方案1】:

    您是否考虑过使用 CGLib + ObjenesisHelper? CGLib 将允许您代理类和接口,而不仅仅是像java.lang.reflect.Proxy 这样的接口,并且 ObjenesisHelper 将允许您构造类的实例而无需调用构造函数。 请参阅 here for a CGLib example 和 here for a ObjenesisHelper example。

    此外,您可以解压缩 InvocationTargetException 以确保代理实例抛出模拟类定义的预期 Exception。最后,使用registerStaticCallbacks 将确保绑定的方法拦截器存在于所有调用线程中。

    public <T> T createProxy(final Class<? extends T> classToMock, final T mock) {
        final MethodInterceptor interceptor = (object, method, args, proxy) -> {
            synchronized (mock) {
                try {
                    return method.invoke(mock, args);
                } catch (final InvocationTargetException e) {
                    if (e.getCause() != null) {
                        throw e.getCause();
                    }
                    throw e;
                }
            }
        };
    
        final Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(classToMock);
        final Set<Class<?>> interfaces = new LinkedHashSet<>();
        if (classToMock.isInterface()) {
            interfaces.add(classToMock);
        }
        interfaces.addAll(Arrays.asList(classToMock.getInterfaces()));
        interfaces.add(CaptureControl.class);
        enhancer.setInterfaces(interfaces.toArray(new Class[interfaces.size()]));
        enhancer.setCallbackType(interceptor.getClass());
    
        final Class<?> proxyClass = enhancer.createClass();
        Enhancer.registerStaticCallbacks(proxyClass, new Callback[] { interceptor });
        return (T) ObjenesisHelper.newInstance(proxyClass);
    }
    

    使用上述方法时,我遇到任何死锁的机会有多大?

    我不相信您提供的解决方案,也不相信上面建议的解决方案会遇到任何死锁(假设您的代码中已经没有死锁)。使用synchronized 将确保在任何给定时间只有一个线程可以操作模拟实例。除非 jmock 将方法调用委托给单独的线程(据我所知它没有)那么代码应该正常执行而不是阻塞。如果 jmock 要求您一次锁定所有 Mockery 实例,那么您可以传入一个专用对象以进行同步,或者为所有代理拦截器提供可重入锁以共享。

    【讨论】:

    • 虽然这仍然不能解决我的期望不断失败的问题,但还是感谢您的回答。顺便说一句,我对您的代码示例进行了一些编辑:接口列表不能为空,或者根本不应该调用 enhancer.setInterfaces()。此外,CaptureControl 应该在模拟的接口列表中,否则 jMock 将拒绝设置任何期望。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-31
    • 2013-05-06
    • 2018-12-08
    • 1970-01-01
    • 2010-11-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多