基于Android的C/S移动应用中访问后端数据的场景是非常多的,异步接口测试主要是在单元测试完成的基础上检查接口级访问是否正确,主要保证对外请求的组装与发送是否符合后端的约定。现在项目的异步接口访问都遵循一个特定的访问模式:前台的Activity获取到触发事件后将接受到的参数传给一个异步任务,这些任务大都是AsyncTask的实现——即启动一个新的线程访问后台接口数据,完毕后调用回调函数更新UI展示,示意图如下:
一. 测试框架
对于Android中这种异步接口的自动化测试需要解决3个问题:
1) 如何获取到异步任务执行结果;
2) 如何让上层测试代码尽量不处理任务等待;
3) 如何处理需要登录的接口。
对于问题1)每个异步任务在获取结果后就直接调用onPostExecute()方法了,测试代码获取不到结果,所以必须有一个专门的桩Activity负责异步任务的执行并将结果暴露出来;
对于问题2)尽量将等待操作交给测试基类,上层测试代码只需要执行被测逻辑而不需要关心细节;
对于问题3)采用模板模式,如果接口需要登录则先执行登录操作后再调用,整体解决方案如下:
先介绍桩Activity,它负责异步任务的实际调用,同时承担分发器的作用,如果需要登录则先分发给需要登录的流程,不需要登录则直接调用不需要登录的流程。当然它还有重要作用就是取到异步接口调用后的结果,这样Test Case获取到并比对结果,示意代码如下:
可以看到isCompleted是标识异步任务是否执行完毕的,无论异步任务返回是onSuccess、onError还是onIOException都会进行置位;result则是异步调用的返回,可以看到这里无论接口调用是成功、失败还是io异常都会将这个结果暴露出来以使测试代码能够获取到。
然后介绍InterfaceAction,它是测试代码需要实现的接口,内容如下:
前两个接口实现是为其服务的,如果访问的接口必须处于登录状态则让neddLogin()返回true同时实现login()的逻辑,不需要登录则直接让neddLogin()返回false即可。而action()是核心测试逻辑,包括发送数据的准备和实际接口的调用。
最后介绍的是测试用例都需要继承的测试基类,他主要为了减少对异步任务等待的代码以及显示对InterfaceActivity这个桩Activity的调用,示意代码如下:
这样上层test case只需要关心具体的测试逻辑而不用关心异步调用及等待处理,只需要取到返回的result进行断言。
二. 测试代码示例
首先介绍一下测试用例的组织形式,一共分为两层:action层和test case层,action层为异步调用逻辑层,所有的类都实现InterfaceAction,case层为测试用例层,主要组装各种action并断言结果,如下图所示:
为了使示例能够简单明了地表达意图,假设我们要测试Bar接口(需要登陆),BarAction是实际操作,BarTest是测试用例:
这里BarAction继承于BaseLoginAction,它默认实现了needLogin()和login()方法,这样BarAction本身就不必关心登录逻辑了。当然更好的写法是“common”和“bar”这些测试数据从params中取,这样就可以从测试用例层直接传递测试数据过来。接下来是BarTest的实现:
测试类主要是对BarAction的调用,它可以向接口传递不同的参数,同时也可传递登录信息。asyncInvoke(action)完成了接口逻辑的调用及时间的等待,测试代码只需着重关注result并进行断言。
三. 总结
异步接口的集成测试的侧重点在于Android手机端向服务器端发送的请求是否正确,以上测试Case的断言与后端数据其实是强耦合的,即后端bar这个账户的数据变化可能导致Case的fail,所以可以考虑引入hamcrest包,做一些匹配校验,主要测试正常和异常情况服务器返回的内容是否符合预期,比如上面最后一个断言可以写成:assertEquals(resp.getResult() (), greaterThan(0));当然,服务器端接口的正确性正常情况下应该由服务器端的自动化Case来保证,这样才不至于前后端测试紧耦合在一起。