Как я могу смоделировать AsyncResponder и результирующие функции-обработчики на смоделированном объекте, используя FlexUnit 4 и mockolate - PullRequest
1 голос
/ 07 марта 2012

Я пытаюсь написать некоторые модульные тесты для класса, который я пишу во Flex 4.5.1, используя FlexUnit 4 и Mockolate для моей среды тестирования и моделирования соответственно. Я использую as3-сигналы для своих пользовательских событий.

Функциональность, которую я пишу и тестирую, - это класс-оболочка (QueryQueue) вокруг QueryTask класса в ArcGIS API для Flex . Это позволяет мне легко ставить в очередь несколько задач для выполнения. Моя оболочка QueryQueue отправит событие completed, когда все ответы на запросы будут обработаны.

Интерфейс очень прост.

public interface IQueryQueue
{
    function get inProgress():Boolean;
    function get count():int;

    function get completed():ISignal;
    function get canceled():ISignal;

    function add(query:Query, url:String, token:Object = null):void; 
    function cancel():void;
    function execute():void;
}

Вот пример использования:

public function exampleUsage():void
{
    var queryQueue:IQueryQueue = new QueryQueue(new QueryTaskFactory());
    queryQueue.completed.add(onCompleted);
    queryQueue.canceled.add(onCanceled);

    var query1:Query = new Query();
    var query2:Query = new Query();
    // set query parameters

    queryQueue.add(query1, url1);
    queryQueue.add(query2, url2);

    queryQueue.execute();
}

public function onCompleted(sender:Object, event:QueryQueueCompletedEventArgs)
{
    // do stuff with the the processed results
}

public function onCanceled(sender:Object, event:QueryQueueCanceledEventArgs)
{
    // handle the canceled event
}

Для моих тестов я в настоящее время проверяю объекты QueryTaskFactory и QueryTask Простые тесты, такие как обеспечение того, что запросы добавляются в очередь относительно просто.

[Test(Description="Tests adding valid QueryTasks to the QueryQueue.")]
public function addsQuerys():void
{
    var queryTaskFactory:QueryTaskFactory = nice(QueryTaskFactory);
    var queryQueue:IQueryQueue = new QueryQueue(queryTaskFactory);
    assertThat(queryQueue.inProgress, isFalse());
    assertThat(queryQueue.count, equalTo(0));

    var query1:Query = new Query();
    queryQueue.add(query1, "http://gisinc.com");
    assertThat(queryQueue.inProgress, isFalse());
    assertThat(queryQueue.count, equalTo(1));

    var query2:Query = new Query();
    queryQueue.add(query2, "http://gisinc.com");
    assertThat(queryQueue.inProgress, isFalse());
    assertThat(queryQueue.count, equalTo(2));

    var query3:Query = new Query();
    queryQueue.add(query3, "http://gisinc.com");
    assertThat(queryQueue.inProgress, isFalse());
    assertThat(queryQueue.count, equalTo(3));
}

Однако я хочу иметь возможность протестировать и метод execute. Этот метод должен выполнить все запросы, добавленные в очередь. После обработки всех результатов запроса отправляется событие completed. Тест должен гарантировать, что:

  1. execute вызывается для каждого запроса один раз и только один раз
  2. inProgress = true пока результаты не обработаны
  3. inProgress = false после обработки результатов
  4. completed отправляется после обработки результатов
  5. canceled никогда не вызывается (для действительных запросов)
  6. Обработка, выполненная в очереди, корректно обрабатывает и упаковывает результаты запроса

Пока что я могу написать тесты для пунктов с 1 по 5, во многом благодаря ответу, предоставленному weltraumpirat. Мой тест выполнения теперь выглядит следующим образом.

[Test(async, description="Tests that all queryies in the queue are executed and the completed signal is fired")]
public function executesAllQueriesInQueue():void
{
    // Setup test objects and mocks
    var query:Query = new Query();
    var mockedQueryTask:QueryTask = nice(QueryTask);
    var mockedQueryTaskFactory:QueryTaskFactory = nice(QueryTaskFactory);

    // Setup expectations
    expect(mockedQueryTaskFactory.createQueryTask("http://test.com")).returns(mockedQueryTask);
    expect(mockedQueryTask.execute(query, null)).once();

    // Setup handlers for expected and not expected signals (events)
    var queryQueue:IQueryQueue = new QueryQueue(mockedQueryTaskFactory);
    handleSignal(this, queryQueue.completed, verifyOnCompleted, 500, null);
    registerFailureSignal(this, queryQueue.canceled);

    // Do it
    queryQueue.add(query, "http://test.com");
    queryQueue.execute();

    // Test that things went according to plan
    assertThat(queryQueue.inProgress, isTrue());
    verify(mockedQueryTask);
    verify(mockedQueryTaskFactory);

    function verifyOnCompleted(event:SignalAsyncEvent, passThroughData:Object):void
    {
        assertThat(queryQueue.inProgress, isFalse());
    }
}

Метод QueryQueue.execute выглядит следующим образом.

public function execute():void
{
    _inProgress = true;

    for each(var queryObject:QueryObject in _queryTasks)
    {
        var queryTask:QueryTask = _queryTaskFactory.createQueryTask(queryObject.url);
        var asyncToken:AsyncToken = queryTask.execute(queryObject.query);

        var asyncResponder:AsyncResponder = new AsyncResponder(queryTaskResultHandler, queryTaskFaultHandler, queryObject.token);
        asyncToken.addResponder(asyncResponder);
    }
}

private function queryTaskResultHandler(result:Object, token:Object = null):void
{
    // For each result collect the data and stuff it into a result collection
    // to be sent via the completed signal when all querytask responses
    // have been processed.
}

private function queryTaskFaultHandler(error:FaultEvent, token:Object = null):void
{
    // For each error collect the error and stuff it into an error collection
    // to be sent via the completed signal when all querytask responses
    // have been processed.
}

Для теста № 6, описанного выше, я хочу проверить, правильно ли обрабатываются данные, возвращаемые в queryTaskResultHandler и queryTaskFaultHandler.

То есть я не отправляю событие completed, пока не будут возвращены все ответы на запросы, включая успешные и неудачные результаты.

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

Итак, как мне высмеивать данные, передаваемые в обработчик результатов, созданный с помощью AsyncResponder с использованием FlexUnit и mockolate.

1 Ответ

1 голос
/ 07 марта 2012

Вы можете макетировать любой объект или интерфейс с помощью mockolate. По моему опыту, лучше всего установить правило и издеваться так:

[Rule]
public var rule : MockolateRule = new MockolateRule();

[Mock]
public var task : QueryTask;

Обратите внимание, что необходимо создать экземпляр правила, но не фиктивный объект.

Затем вы можете указать свои ожидания:

[Test]
public function myTest () : void {
    mock( task ).method( "execute" ); // expects that the execute method be called
}

Вы можете ожидать кучу вещей, таких как параметры:

    var responder:AsyncResponder = new AsyncResponder(resultHandler, faultHandler);
    mock( task ).method( "execute" ).args( responder ); // expects a specific argument

Или заставить объект возвращать определенные значения:

    mock( queue ).method( "execute" ).returns( myReturnValue ); // actually returns the value(!)

Отправка событий из фиктивного объекта так же проста, как и вызов dispatchEvent для него - поскольку вы насмехаетесь над исходным классом, он наследует все его функции, включая EventDispatcher.

Теперь для вашего особого случая, на мой взгляд, было бы лучше высмеивать использование всех трех внешних зависимостей: Query, QueryTask и AsyncResponder, поскольку это не их функциональность вы тестируете, но тест вашего Queue.

Поскольку вы создаете эти объекты в своей очереди, это делает их насмешливыми. На самом деле, вы не должны создавать что-либо непосредственно в каком-либо классе, если только нет внешних зависимостей! Вместо этого передайте фабрику (вы можете захотеть использовать инфраструктуру внедрения зависимостей) для каждого из объектов, которые вы должны создать - вы можете затем смоделировать эту фабрику в своем тестовом примере и заставить ее возвращать фиктивные объекты по мере необходимости:

public class QueryFactory {
    public function createQuery (...args:*) : Query {
       var query:Query = new Query();
       (...) // use args array to configure query
       return query;
    }
}

public class AsyncResponderFactory {
    public function createResponder( resultHandler:Function, faultHandler:Function ) : AsyncResponder {
        return new AsyncResponder(resultHandler, faultHandler);
    }
}

public class QueryTaskFactory {
    public function createTask (url:String) : QueryTask {
       return new QueryTask(url);
    }
}

... в очереди:

(...)
public var queryFactory:QueryFactory;
public var responderFactory : AsyncResponderFactory;
public var taskFactory:QueryTaskFactory;
(...)
var query:Query = queryFactory.createQuery ( myArgs );
var responder:AsyncResponder = responderFactory.createResponder (resultHandler, faultHandler);
var task:QueryTask = taskFactory.createTask (url);
task.execute (query, responder);
(...)

... в вашем тесте:

[Rule]
public var rule : MockolateRule = new MockolateRule();

[Mock]
public var queryFactory:QueryFactory;
public var query:Query; // no need to mock this - you are not calling any of its methods in Queue.
[Mock]
public var responderFactory:AsyncResponderFactory;
public var responder:AsyncResponder;
[Mock]
public var taskFactory:QueryTaskFactory;
[Mock]
public var task:QueryTask;

[Test]
public function myTest () : void {
    query = new Query();
    mock( queryFactory ).method( "createQuery ").args ( (...) ).returns( query ); // specify arguments instead of (...)!

    responder = new AsyncResponder ();
    mock( responderFactory ).method( "createResponder" ).args( isA(Function) , isA(Function) ).returns( responder ); // this will ensure that the handlers are really functions 
    queue.responderFactory = responderFactory;

    mock( task ).method( "execute" ).args( query, responder );
    mock( taskFactory ).method( "createTask" ).args( "http://myurl.com/" ).returns( task );
    queue.taskFactory = taskFactory; 

    queue.doStuff(); // execute whatever the queue should actually do
}

Обратите внимание, что вы должны объявить все макеты как public, и все ожидания должны быть добавлены перед передачей фиктивного объекта на его хост , в противном случае mockolate не сможет правильно настроить прокси-объекты.

...