apache-flex - 如何在 Flex/AS3 中使用基于 cookie 的 session 来测试服务

标签 apache-flex cookies service tdd robotlegs

第一次在这里发帖。感谢您花时间查看该问题。

正如标题所示,问题与 Flex 应用程序的服务层有关。特别是在测试用例类中。要调用服务,用户必须首先通过基于 html/CF 的页面进行身份验证。一旦完成并且进行身份验证,用户将被定向到嵌入了 Flex 应用程序的 html 页面。经过身份验证后,服务器端 CF 代码会将 cookie 写入用户计算机。然后 Flex 应用程序会读取此 Cookie,Flex 应用程序需要该 Cookie 来调用 CF 服务。

问题是:在 Flex 应用程序中的服务类的测试用例类中应该如何处理这个问题?基本上需要执行以下步骤:使用一些帐户数据来访问服务器并进行身份验证,然后需要写入 cookie(它已经会了),然后测试用例需要从 cookie 中获取 session ID 并通过它到测试用例中的服务调用。对于测试用例类来说,这一切似乎太多了。

那么,这种事情通常是如何处理的呢?据我所知,Web 服务调用通常需要这样的 session ID。

非常感谢任何想法、意见、建议等。

感谢您的宝贵时间以及您提供的任何信息。

肖恩

最佳答案

cookie 和实际的服务调用将在集成测试中进行测试。 Yoyou 认为这些位不适合单元测试是正确的。

我喜欢做的是创建一个伟大的委托(delegate)(假设您的集成点尚未表达接口(interface))并创建实现我选择的接口(interface)的测试委托(delegate)。该 cookie 将被“检索”,并且对于您的测试目的始终有效。如果您需要它为 false,请创建另一个测试委托(delegate)并将其作为依赖项提供。

Mockolate 可以大有帮助,但构建可靠的模拟是关键。

这是一个例子。它没有直接回答您的问题,但我认为它代表了一些相同的问题,因此可能会帮助您解决特定的情况。

我正在使用第三方库来处理此应用程序中的 SQL 操作。该库有一个名为 SQLRunner 的核心类。碰巧,这个类没有接口(interface)(这会让事情变得更容易一些)。我有两个选择。

  1. fork 并修改库以表达接口(interface)。
  2. 将 SQLRunner 包装在我自己的委托(delegate)类中。

事实上,我两种方法都做了,但出于多种原因更喜欢第二种方法。我能够完全定义第 3 方库的 API 和功能。这太棒了。我实际上并没有更改 API,但如果您不喜欢它的方法命名方式...不用担心,请更改它。它还允许我表达一个界面!我最初这样做是因为我想使用 Mockolate 来模拟它的用法。它对于创建我自己的测试模型也很有用,这些模型具有更强大的功能并且更清晰。所以这是服务:

public class SQLTaskService extends Actor implements ITaskService
{
    [Inject]
    public var sqlRunner:ISQLRunnerDelegate;

    [Inject]
    public var statusListModel:StatusListModel;

    [Inject]
    public var taskListModel:TaskListModel;

    public function loadAllTasks():void
    {
        statusListModel.removeAllTasks();
        sqlRunner.execute(LOAD_ALL_TASKS_SQL, null, loadAllTasksResultHandler, Task, databaseErrorHandler);
    }

    private function loadAllTasksResultHandler(result:SQLResult):void
    {
        for each(var task:Task in result.data)
        {
            var taskStatus:Status = statusListModel.getStatusFromId(task.statusId);
            statusListModel.addTaskToStatus(task, taskStatus);
            taskListModel.addTask(task);
        }
    }

    public function loadTaskById(id:int):void
    {
        sqlRunner.execute(LOAD_TASK_SQL, {taskId:id}, loadTaskResultHandler, Task);
    }

    private function loadTaskResultHandler(result:SQLResult):void
    {
        var task:Task = result.data[0] as Task;
        var taskStatus:Status = statusListModel.getStatusFromId(task.statusId);

        task = taskListModel.updateTask(task);

        statusListModel.addTaskToStatus(task, taskStatus);
    }

    public function save(task:Task):void
    {
        var params:Object = task.toParamObject();

        sqlRunner.executeModify(Vector.<QueuedStatement>(
                [new QueuedStatement(SAVE_TASK_SQL, params)]), saveTaskResultHandler, databaseErrorHandler);
    }


    private function saveTaskResultHandler(results:Vector.<SQLResult>):void
    {
        var result:SQLResult = results[0];
        if (result.rowsAffected > 0)
        {
            var id:Number = result.lastInsertRowID;
            loadTaskById(id);
        }
    }

    public function deleteTask(task:Task):void
    {
        sqlRunner.executeModify(Vector.<QueuedStatement>([new QueuedStatement(DELETE_TASK_SQL, {taskId:task.taskId})]),
                deleteTaskResult, databaseErrorHandler);
    }

    private function deleteTaskResult(results:Vector.<SQLResult>):void
    {
        //pass
    }

    private function databaseErrorHandler(error:SQLError):void
    {
        dispatch(new DatabaseErrorHandlerEvent(error.message));
    }

    [Embed(source="/assets/data/sql/tasks/SaveTask.sql", mimeType="application/octet-stream")]
    private static const SaveTaskStatementText:Class;
    public static const SAVE_TASK_SQL:String = new SaveTaskStatementText();

    [Embed(source="/assets/data/sql/tasks/DeleteTask.sql", mimeType="application/octet-stream")]
    private static const DeleteTaskStatementText:Class;
    public static const DELETE_TASK_SQL:String = new DeleteTaskStatementText();

    [Embed(source="/assets/data/sql/tasks/LoadTask.sql", mimeType="application/octet-stream")]
    private static const LoadTaskStatementText:Class;
    public static const LOAD_TASK_SQL:String = new LoadTaskStatementText();

    [Embed(source="/assets/data/sql/tasks/LoadAllTasks.sql", mimeType="application/octet-stream")]
    private static const LoadAllTasksStatementText:Class;
    public static const LOAD_ALL_TASKS_SQL:String = new LoadAllTasksStatementText();
}

你可以看到它有委托(delegate)依赖:

/**
 * This is a delegate for the SQLRunner class that allows us to utilize an interface
 * for the purposes of creating mocks. The actual SQLRunner class does not express
 * an interface. This approach also allows us to encapsulate the usage of a 3rd party
 * library into this single delegate.
 *
 * <p>An alternative would be to fork and modify the original library, which would
 * definitely be a viable option and would help others in the future.</p>
 */
public class SQLRunnerDelegate implements ISQLRunnerDelegate
{
    private var sqlRunner:SQLRunner;

    public function SQLRunnerDelegate(dataBaseFile:File, maxPoolSize:int = 5)
    {
        sqlRunner = new SQLRunner(dataBaseFile, maxPoolSize);
    }

    public function get numConnections():int
    {
        return sqlRunner.numConnections;
    }

    public function get connectionErrorHandler():Function
    {
        return sqlRunner.connectionErrorHandler;
    }

    public function set connectionErrorHandler(value:Function):void
    {
        sqlRunner.connectionErrorHandler = value;
    }

    public function execute(sql:String, parameters:Object, handler:Function, itemClass:Class = null, errorHandler:Function = null):void
    {
        sqlRunner.execute(sql, parameters, handler, itemClass, errorHandler);
    }

    public function executeModify(statementBatch:Vector.<QueuedStatement>, resultHandler:Function, errorHandler:Function, progressHandler:Function = null):void
    {
        sqlRunner.executeModify(statementBatch, resultHandler, errorHandler, progressHandler);
    }

    public function close(resultHandler:Function, errorHandler:Function = null):void
    {
        sqlRunner.close(resultHandler, errorHandler);
    }
}

效果非常好。现在我的应用程序有一个与第三方库的微小集成点。大胜利。我还可以进行强大的模拟,而不必处理对文件系统的依赖或第三方库的任何其他奇怪之处:<​​/p>

/**
 * This is a more robust mock for the SQLRunnerDelegate to test for
 * side effects that occur when methods are called on SQLTaskService
 */
public class MockTaskSQLRunnerDelegate extends MockSQLRunnerDelegateBase implements ISQLRunnerDelegate
{
    public function execute(sql:String, parameters:Object, handler:Function, itemClass:Class = null, errorHandler:Function = null):void
    {
        lastStatementExecuted = sql;
        allStatementsExecuted.push(lastStatementExecuted);
        parametersSent = parameters;

        switch (sql)
        {
            case SQLTaskService.LOAD_ALL_TASKS_SQL:
                handler.call(null, loadTask());

                break;

            case SQLTaskService.LOAD_TASK_SQL:
                handler.call(null, loadTask());
                break;
            default:
                break;
        }
    }

    private function loadTask():SQLResult
    {
        var task:Task = new Task();
        var data:Array = [task];
        var result:SQLResult = new SQLResult(data);

        task.taskId = 1;
        task.statusId = 1;

        return result;
    }

    public function executeModify(statementBatch:Vector.<QueuedStatement>, resultHandler:Function, errorHandler:Function, progressHandler:Function = null):void
    {
        lastStatementExecuted = statementBatch[0].statementText;
        allStatementsExecuted.push(lastStatementExecuted);
        parametersSent = statementBatch[0].parameters;

        switch (lastStatementExecuted)
        {
            case SQLTaskService.SAVE_TASK_SQL:
                resultHandler.call(null, saveTask());
                break;
        }
    }

    private function saveTask():Vector.<SQLResult>
    {
        var task:Task = new Task();
        var result:SQLResult = new SQLResult([task], 1, true, 1);
        var results:Vector.<SQLResult> = new Vector.<SQLResult>();

        task.taskId = task.statusId = 1;
        results.push(result);

        return results;
    }

    public function get numConnections():int
    {
        return 0;
    }

    public function get connectionErrorHandler():Function
    {
        return null;
    }

    public function set connectionErrorHandler(value:Function):void
    {
    }

    public function close(resultHandler:Function, errorHandler:Function = null):void
    {
    }
}

我从交易中得到了一个很好的测试套件:

public class SqlTaskServiceTest
{
    private var taskService:SQLTaskService;

    [Before(async)]
    public function setup():void
    {
        taskService = new SQLTaskService();
        taskService.statusListModel = new StatusListModel();
        taskService.taskListModel = new TaskListModel();
        initializeModels();
        prepareMockolates();
    }

    public function prepareMockolates():void
    {
        Async.proceedOnEvent(this, prepare(ISQLRunnerDelegate), Event.COMPLETE);
    }

    [Test]
    public function loadAllTasks_executesSqlStatement_statementEqualsLoadAll():void
    {
        var runner:MockTaskSQLRunnerDelegate = new MockTaskSQLRunnerDelegate();
        taskService.sqlRunner = runner;

        taskService.loadAllTasks();

        assertThat(runner.lastStatementExecuted, equalTo(SQLTaskService.LOAD_ALL_TASKS_SQL));
    }

    [Test]
    public function loadAllTasks_clearsTasksFromStatusListModel_lengthIsEqualToZero():void
    {
        var status:Status = new Status();
        var task:Task = new Task();

        initializeModels(status, task);
        taskService.sqlRunner = nice(ISQLRunnerDelegate);

        taskService.loadAllTasks();

        assertThat(status.tasks.length, equalTo(0))
    }

    [Test]
    public function loadAllTasks_updatesTaskListModelWithLoadedTasks_collectionLengthIsOne():void
    {
        taskService.sqlRunner = new MockTaskSQLRunnerDelegate();

        taskService.loadAllTasks();

        assertThat(taskService.taskListModel.tasks.length, equalTo(1));
    }

    [Test]
    public function loadAllTasks_updatesStatusWithTask_statusHasTasks():void
    {
        var status:Status = new Status();
        initializeModels(status);
        taskService.sqlRunner = new MockTaskSQLRunnerDelegate();

        taskService.loadAllTasks();

        assertThat(status.tasks.length, greaterThan(0));
    }

    [Test]
    public function save_executesSqlStatement_statementEqualsSave():void
    {
        var task:Task = new Task();
        var runner:MockTaskSQLRunnerDelegate = new MockTaskSQLRunnerDelegate();
        taskService.sqlRunner = runner;
        task.statusId = 1;

        taskService.save(task);

        assertThat(runner.allStatementsExecuted, hasItem(SQLTaskService.SAVE_TASK_SQL));
    }

    [Test]
    public function save_taskIsLoadedAfterSave_statementEqualsLoad():void
    {
        var task:Task = new Task();
        var runner:MockTaskSQLRunnerDelegate = new MockTaskSQLRunnerDelegate();
        taskService.sqlRunner = runner;
        task.statusId = 1;

        taskService.save(task);

        assertThat(runner.allStatementsExecuted, hasItem(SQLTaskService.LOAD_TASK_SQL));
    }

    [Test]
    public function save_taskIsAddedToModelWhenNew_tasksLengthGreaterThanZero():void
    {
        var taskListModel:TaskListModel = taskService.taskListModel;
        var task:Task = new Task();

        taskListModel.reset();
        taskService.sqlRunner = new MockTaskSQLRunnerDelegate();
        task.statusId = 1;
        task.taskId = 1;

        taskService.save(task);

        assertThat(taskListModel.tasks.length, equalTo(1));
    }

    [Test]
    public function save_existingTaskInstanceIsUpdatedAfterSave_objectsAreStrictlyEqual():void
    {
        var taskListModel:TaskListModel = taskService.taskListModel;
        var task:Task = new Task();
        var updatedTask:Task;

        taskListModel.addTask(task);
        taskService.sqlRunner = new MockTaskSQLRunnerDelegate();
        task.statusId = 1;
        task.taskId = 1;

        taskService.save(task);
        updatedTask = taskListModel.getTaskById(task.taskId);

        assertThat(updatedTask, strictlyEqualTo(task));
    }

    [Test]
    public function loadTaskById_executesLoadStatement_statementEqualsLoad():void
    {
        var runner:MockTaskSQLRunnerDelegate = new MockTaskSQLRunnerDelegate();
        taskService.sqlRunner = runner;

        taskService.loadTaskById(1);

        assertThat(runner.allStatementsExecuted, hasItem(SQLTaskService.LOAD_TASK_SQL));
    }

    [Test]
    public function deleteTasks_executesDeleteStatement_statementEqualsDelete():void
    {
        var task:Task = new Task();
        var runner:MockTaskSQLRunnerDelegate = new MockTaskSQLRunnerDelegate();
        taskService.sqlRunner = runner;

        taskService.deleteTask(task);

        assertThat(runner.allStatementsExecuted, hasItem(SQLTaskService.DELETE_TASK_SQL));
    }

    private function initializeModels(status:Status = null, task:Task = null):void
    {
        var statusListModel:StatusListModel = taskService.statusListModel;
        statusListModel.reset();

        //if nothing was passed in we need to default to new objects
        status ||= new Status();
        task ||= new Task();

        status.statusId = 1;
        task.taskId = task.statusId = 1;

        statusListModel.statuses.addItem(status);
        statusListModel.addTaskToStatus(task, status);
    }
}

旁注:我想在这里指出的是,没有一个异步测试。很少需要异步运行单元测试。在某些边缘情况下您可能确实需要这样做,但它们是异常(exception)。

关于apache-flex - 如何在 Flex/AS3 中使用基于 cookie 的 session 来测试服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6590706/

相关文章:

apache-flex - Flex 中的终端仿真

java - Apache HttpClient是否将java.net.CookieHandler设置的Cookie添加到Request中?

android - 每天创建通知

android - 如何检查服务是否在后台/前台运行 Activity ?

apache-flex - Flex 警告 : Unable to bind to property 'foo' on class 'Object' (class is not an IEventDispatcher)

ios - Flash builder - 可以使用免费的 Apple 帐户直接编译到设备吗?

java - Web 应用程序的类似电子表格的控件?

cookies - 使用curl发送cookie

java - 当我的项目部署在网络服务器 B 上时,如何检索网络服务器 A 的 cookie?

wcf - WCF 测试客户端中的 https 使用 basicHttpBinding 绑定(bind)