测试 Axis 1.4 SOAP 调用时有条件地模拟异常/返回值

发布于 2024-12-03 08:43:55 字数 4999 浏览 2 评论 0原文

对于冗长的问题表示歉意...

为了测试在单元测试中调用 Axis 1.4 Web 服务的业务逻辑,我目前正在使用 Spring 代理,它允许我设置异常并模拟返回值,如下所示。

但是,我想知道是否有更干净的方法来执行此操作:

在我的 JUnit 应用程序上下文中,我引入以下建议:

<bean id="testProxy" class="appl.service.TestProxyImpl" />
<bean id="testAdvice" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
    <constructor-arg index="0" ref="testProxy"/>
    <constructor-arg index="1" value="appl.service.ITestProxy"/>
</bean>

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames" value="myAxisProxy"/>
    <property name="interceptorNames" value="testAdvice"/>
</bean>

TestProxyImpl 类如下(如果您需要查看接口,请告诉我):

public class TestProxyImpl 
    extends DelegatingIntroductionInterceptor 
    implements ITestProxy {

    private Map<String, Integer> calls = new HashMap<String, Integer>();
    private Map<String, Throwable[]> exceptions = new HashMap<String, Throwable[]>();
    private Map<String, Object> returnValues = new HashMap<String, Object>();
    private Map<String, Object[]> lastParams = new HashMap<String, Object[]>();

    public int getCalls(String methodName) {
        Integer noCalls = calls.get(methodName);
        if (noCalls == null) {
            return 0;
        }
        return noCalls;
    }

    public void resetCalls() {
        calls.clear();
        returnValues.clear();
        exceptions.clear();
        lastParams.clear();
    }

    public void addExceptions(String method, Throwable... exceptions) {
        this.exceptions.put(method, exceptions);
    }

    public void addReturnValue(String method, Object result) {
        returnValues.put(method, result);
    } 

    @SuppressWarnings("unchecked")
    public <T> T getLastParameter(String method, Class<? extends T> paramClass) {
        Object[] args = lastParams.get(method);
        if (args != null) {
            for (Object arg : args) {
                if (arg != null) {
                    if (paramClass.isAssignableFrom(arg.getClass())) {
                        return (T)arg;
                    }
                }
            }
        }
        return null;
    }

    @Override
    protected Object doProceed(MethodInvocation mi) throws Throwable {
        String methodName = mi.getMethod().getName();
        int noCalls;
        synchronized (calls) {
            noCalls = getCalls(methodName);
            calls.put(methodName, noCalls + 1);
        }
        Object[] args = mi.getArguments();
        synchronized (lastParams) {
            lastParams.put(methodName, args);
        }
        if (exceptions.containsKey(methodName)) {
            Throwable[] exceptionArray = exceptions.get(methodName);
            if (exceptionArray != null) {
                Throwable e = exceptionArray[noCalls % exceptionArray.length];
                if (e != null) {
                    throw e;
                }
            }
        }
        if (returnValues.containsKey(methodName)) {
            return returnValues.get(methodName);
        }
        return super.doProceed(mi);
    }
}

它允许我使用以下单元测试:

protected void onSetUp() throws Exception {
    super.onSetUp();

    // testProxy is autowired into the unit test class
    testProxy.resetCalls();
}

public void testSuccess() throws Exception {

    // myBusinessObject is autowired into the unit test class
    // myAxisProxy is injected into myBusinessObject in the spring context
    // myBusinessObject calls myAxisProxy.myMethod(MyRequestObject) internally
    myBusinessObject.doSomething();

    // check that myAxisProxy has been called the correct times
    // with the correct parameters
    assertEquals(1, testProxy.getCalls("myMethod"));
    assertEquals(
        "foo", 
        testProxy.getLastParameter(
            "myMethod", 
             MyRequestObject.class).getMyField());
 }

 public void testWithoutCallingProxy() throws Exception {

     testProxy.addReturnValue("myMethod", createTestReturnValues());

     myBusinessObject.doSomething();

     // check that the response is as expected
 }

 public void testWithException() throws Exception {
     testProxy.addException(
         "myMethod", 
         AxisFault.makeFault(new ConnectException("test")),
         null);

     // this would check that the first call results in a ConnectException
     // simulating a network error, but myBusinessObject retries
     // and goes through to the service on the second call.
     myBusinessObject.doSomething();
 }

我简要研究了 EasyMock,但不确定如何创建一个模拟对象来拦截调用并有时返回异常或指定的返回值,但使用正确的调用其他时候,所以想知道是否有人有关于如何简化这一点的想法。

请注意,理想情况下我希望避免重新连接 myBusinessObject 的属性。

谢谢。

编辑:

我正在使用 Spring 2.5、JUnit 3.8 和 Axis 1.4

编辑2:

我使用这种方法来自动化集成测试,而不是简单的单元测试,所以我对如何用基于的东西替换手工解决方案有任何建议图书馆是最受欢迎的。

Apologies for the longwinded question...

In order to test business logic which calls Axis 1.4 web services in my unit tests, I'm currently using a Spring proxy which allows me to set exceptions and simulate return values as per below.

However, I was wondering whether there was a cleaner way of doing this:

In my JUnit application context I introduce the following advice:

<bean id="testProxy" class="appl.service.TestProxyImpl" />
<bean id="testAdvice" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
    <constructor-arg index="0" ref="testProxy"/>
    <constructor-arg index="1" value="appl.service.ITestProxy"/>
</bean>

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames" value="myAxisProxy"/>
    <property name="interceptorNames" value="testAdvice"/>
</bean>

The TestProxyImpl class is as follows (let me know if you need to see the interface):

public class TestProxyImpl 
    extends DelegatingIntroductionInterceptor 
    implements ITestProxy {

    private Map<String, Integer> calls = new HashMap<String, Integer>();
    private Map<String, Throwable[]> exceptions = new HashMap<String, Throwable[]>();
    private Map<String, Object> returnValues = new HashMap<String, Object>();
    private Map<String, Object[]> lastParams = new HashMap<String, Object[]>();

    public int getCalls(String methodName) {
        Integer noCalls = calls.get(methodName);
        if (noCalls == null) {
            return 0;
        }
        return noCalls;
    }

    public void resetCalls() {
        calls.clear();
        returnValues.clear();
        exceptions.clear();
        lastParams.clear();
    }

    public void addExceptions(String method, Throwable... exceptions) {
        this.exceptions.put(method, exceptions);
    }

    public void addReturnValue(String method, Object result) {
        returnValues.put(method, result);
    } 

    @SuppressWarnings("unchecked")
    public <T> T getLastParameter(String method, Class<? extends T> paramClass) {
        Object[] args = lastParams.get(method);
        if (args != null) {
            for (Object arg : args) {
                if (arg != null) {
                    if (paramClass.isAssignableFrom(arg.getClass())) {
                        return (T)arg;
                    }
                }
            }
        }
        return null;
    }

    @Override
    protected Object doProceed(MethodInvocation mi) throws Throwable {
        String methodName = mi.getMethod().getName();
        int noCalls;
        synchronized (calls) {
            noCalls = getCalls(methodName);
            calls.put(methodName, noCalls + 1);
        }
        Object[] args = mi.getArguments();
        synchronized (lastParams) {
            lastParams.put(methodName, args);
        }
        if (exceptions.containsKey(methodName)) {
            Throwable[] exceptionArray = exceptions.get(methodName);
            if (exceptionArray != null) {
                Throwable e = exceptionArray[noCalls % exceptionArray.length];
                if (e != null) {
                    throw e;
                }
            }
        }
        if (returnValues.containsKey(methodName)) {
            return returnValues.get(methodName);
        }
        return super.doProceed(mi);
    }
}

which allows me to use the following unit tests:

protected void onSetUp() throws Exception {
    super.onSetUp();

    // testProxy is autowired into the unit test class
    testProxy.resetCalls();
}

public void testSuccess() throws Exception {

    // myBusinessObject is autowired into the unit test class
    // myAxisProxy is injected into myBusinessObject in the spring context
    // myBusinessObject calls myAxisProxy.myMethod(MyRequestObject) internally
    myBusinessObject.doSomething();

    // check that myAxisProxy has been called the correct times
    // with the correct parameters
    assertEquals(1, testProxy.getCalls("myMethod"));
    assertEquals(
        "foo", 
        testProxy.getLastParameter(
            "myMethod", 
             MyRequestObject.class).getMyField());
 }

 public void testWithoutCallingProxy() throws Exception {

     testProxy.addReturnValue("myMethod", createTestReturnValues());

     myBusinessObject.doSomething();

     // check that the response is as expected
 }

 public void testWithException() throws Exception {
     testProxy.addException(
         "myMethod", 
         AxisFault.makeFault(new ConnectException("test")),
         null);

     // this would check that the first call results in a ConnectException
     // simulating a network error, but myBusinessObject retries
     // and goes through to the service on the second call.
     myBusinessObject.doSomething();
 }

I've briefly looked into EasyMock, but was not sure how I'd be able to create a mock object that intercepts the call and returns an exception or specified return values some of the time, but uses the correct call some other times, so was wondering whether anyone had ideas on how to simplify this.

Note, ideally I'd like to avoid having to rewire the properties of myBusinessObject.

Thank you.

EDIT:

I'm using Spring 2.5, JUnit 3.8 and Axis 1.4

EDIT2:

I'm using this approach to automate integration tests, rather than simple unit tests, so I'm any suggestions on how to replace the handcrafted solution with something based on a library are most welcome.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

各自安好 2024-12-10 08:43:55

典型的分层开发应该如下组织:

数据访问

业务逻辑

服务接口

这再次提醒您,在开发可重复的独立单元测试时,您的测试应该仅针对其预期层和组件中的逻辑和功能。因此,在测试业务逻辑时,我根本不想进行实际的服务调用,我什至不想进行数据访问调用,这就是我们使用 EasyMock 等模拟框架的原因。

如果您不喜欢 EasyMock,请尝试查看 Mockito。它还有一些功能可以处理不太适合模拟的代码。

我觉得很奇怪,你会费这么大的劲来避免一点重新布线。重构的努力是非常值得的。

Typical layered development should be organized something like below:

Data Access

Business Logic

Service Interface

This again should remind that when developing repeatable independent unit tests that your tests should target logic and functionality in its intended layer and component ONLY. Thus when testing Business Logic I do not want to make actual service calls at all, I don't even want to make Data Access calls either, that is why we use Mocking frameworks like EasyMock.

If you don't like EasyMock then try looking at Mockito. It has a few more features for dealing with code that is less than ideal for mocking.

And I find it strange that you would go to so much trouble to avoid a little rewiring. It is well worth the refactoring effort.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文