1

我正在尝试为我在 Flex 4.5.1 中编写的一个类编写一些单元测试,分别使用 FlexUnit 4 和Mockolate作为我的测试和模拟框架。我正在为我的自定义事件使用as3 信号

我正在编写和测试的功能是ArcGIS API for Flex中QueryTask类的包装类 (QueryQueue) 。这使我能够轻松地将多个查询任务排队等待执行。我的包装器 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 创建的结果处理程序的数据。

4

1 回答 1

1

您可以使用 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.

现在对于您的特殊情况,我认为最好模拟所有三个外部依赖项的使用:和QueryQueryTaskAsyncResponder因为您正在测试的不是它们的功能,而是您的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
}

请注意,您必须将所有 mock 声明为public,并且必须在将 mock 对象传递给其 host 之前添加所有期望,否则,mockolate 无法正确配置代理对象。

于 2012-03-07T08:10:33.977 回答