Условное моделирование исключений / возвращаемых значений при тестировании вызовов SOAP Axis 1.4 - PullRequest
2 голосов
/ 07 сентября 2011

Извиняюсь за длинный вопрос ...

Для тестирования бизнес-логики, которая вызывает веб-службы Axis 1.4 в моих модульных тестах, в настоящее время я использую прокси-сервер 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.

* 1.020 * Спасибо.

РЕДАКТИРОВАТЬ:

Я использую Spring 2.5, JUnit 3.8 и Axis 1.4

EDIT2:

Я использую этоподход к автоматизации интеграционных тестов, а не простые модульные тесты, так что любые предложения о том, как заменить решение, созданное вручную, чем-то на основе библиотеки, приветствуются.

1 Ответ

2 голосов
/ 07 сентября 2011

Типичная многоуровневая разработка должна быть организована примерно так:

Доступ к данным

Бизнес-логика

Сервисный интерфейс

Это опять-таки должно напомнить, что при разработке повторяемых независимых модульных тестов ваши тесты должны быть нацелены на логику и функциональность ТОЛЬКО на предполагаемый уровень и компонент. Таким образом, при тестировании Business Logic я вообще не хочу делать реальные сервисные вызовы, я даже не хочу делать вызовы доступа к данным, поэтому мы используем Mocking Frameworks, такие как EasyMock.

Если вам не нравится EasyMock, попробуйте посмотреть на Mockito. У него есть еще несколько функций для работы с кодом, который не идеален для насмешек.

И я нахожу странным, что вы так сильно пострадали, чтобы избежать небольшого перемонтажа. Это стоит усилий по рефакторингу.

...