背景
- 我厂提供的客户端SDK封装了android端对服务端/嵌入式模块端的通信接口;
- 客户根据自己的功能需求,定制嵌入式端的数据点,利用我们提供的SDK实现跟服务端/嵌入式模块的通信;
- 客户工程师希望有测试程序来检测/保证SDK与嵌入式端的通信是正常的,方便双方排查问题;
- 简单来说,该测试程序要拥有调用SDK接口给嵌入式端的发数据并检查其返回数据是否正确的能力,具体发送哪些数据根据客户定制的数据点来决定。
当那个老外工程师信誓旦旦的说他需要一些测试用例的时候,能够感觉到小伙伴们的状态是懵圈的。因为之前遇到的一般状态是,使用我们SDK开发的客户不管不顾的把功能集成到他们的应用中,最后在调试过程中不停召唤我们帮忙改BUG,最离谱的一次是同事出差到客户那发现是APK打包分包配置不当导致的问题。“测试用例”这种高端词汇,似乎是一个正规军和游击队的分界线。
实现
很遗憾到最后我们都没能提供一份测试用例,在我看来主要是没有足够的时间资源导致的。好在那位外国同行表示他已经实现了用例,现在是你们解决问题的时候了。好吧,解决问题没什么——自己写的BUG自己改掉这不算什么,关键是这种白盒测试的工作方式值得学习和借鉴,这里就对客户的实现稍作分析,权做思考和记录。
分析
非要下个定义来区分的话,从测试需求来讲,这不是单元测试,虽然可能使用的测试框架是同一个。它是一个白盒的自动化测试用例,和很久以前玩过的界面自动化测试又不大一样,通过模拟页面操作(点击、长按、拖动)来让应用按照测试程序跑起来主要是针对UI的测试,类似于用程序模拟一个测试人员。这里的需求是针对接口的自动化测试,那么可以想象的是,除了正常的功能覆盖之外还可以实现类似压力测试的场景。
按照开发的习惯,要在实现之前要尽可能的考虑到这之间会遇到什么问题。
- 站在客户使用SDK的角度而言,SDK是一个黑盒子,我们只知道一些接口和调用限制。具体一些,以发送指令为例,接口初始化实例需要context参数、SDK中必须要包含登陆过的信息、发送指令调用TuyaSmartPanel类的send函数。那么在跑用例之前,如何保证前期准备都是完善的?
- SDK提供给嵌入式端发送命令信息的接口,不言而喻一定是异步的,那么如何处理嵌入式端返回的数据断言?
- 自动化测试不像单元测试那样只依赖于被测试代码本身,由于它需要知道真正嵌入式端返回的数据是否正确,所以用例是否能够正确跑起来,还需要硬件、网络环境、测试机器等的支持。那么,如果测试用例作为SDK提供者和使用者两端沟通和认可的交付方式的话,一些必要的准备工作的说明也是不可或缺的。
类图
老外实现是这个样子的:
- SDK无论如何都需要一个context,如果是单元测试我们可以考虑mock一个假货传进去,自动化测试就不行。IntegrationService是一个Service子类,所有的SDK相关操作都被封装到这个服务中;
- IntegrationServiceTest是一个抽象类,看到熟悉的setUp和tearDown就知道,这个抽象类封装了测试用例在运行前后的操作,也就是跑用例的环境初始化和跑过之后的清理,更重要的是,它拥有绑定服务获取的实例,调用服务中的接口间接的调用SDK接口;
- 其他所有类就是真实的测试用例类,他们是都继承实现于IntegrationServiceTest,封装了针对各个数据点的测试用例;
异步接口的处理
异步接口分两步处理,第一步在服务中对接口调用的封装,增加了CountDownLatch限制来等待数据返回;第二步是测试用例定义时增加超时限制。
@Test(timeout=10000)
public void testSwitch_on() throws TimeoutException, InterruptedException {
if((Boolean)mService.getCachedDp("1")){
mService.send("1", "false");
}
dpUpdateReply = mService.send("1", "true");
Log.i(TAG, "testSwitch_on() - dpUpdate dp[1][" + dpUpdateReply.get("1") + "]");
assertEquals("testSwitch_on() - dpUpdate dp[1]", true, dpUpdateReply.get("1"));
}
服务的send函数,对于用例而言是同步的,传进去输入、拿到输出判断结果即可;在真正的执行过程如果没有返回的话,10秒超时后用例会自动报异常。
文档对接
The test suite setup assumes:
- A TuyaSmart e-mail type account is available. If you want to use another account type, you can add the login code for this in class src\androidTest\java\com\nextapp\tuyatest\IntegrationService.java .
- Exactly one radiator is already connected to the account.
外国工程师对文档的描述很详尽,也不啰嗦,总之很专业。他会把每一步该做什么、怎么做描述的很清楚。这一点十分值得学习。
一些细节
CountDownLatch 类,在这个测试场景下尤为好用,国际友人还专门提了一句:
java.util.concurrent.CountDownLatch is used for awaiting listeners being triggered and return result to test call. We avoid using Thread.sleep().
用例设置超时的方法大概有两种的样子,除了本例的注解实现意外还可以通过Rules来实现的样子:
@Rule public Timeout globalTimeout= new Timeout(10);
统一的日志关键字。代码中很少出现注释,易懂的函数/变量命名和统一整齐格式的日志。
- 针对SDK的测试用例工程开源到github上,一些敏感信息(如SDK要求的产品key、测试需要的账号/密码等)采用XML配置的方式实现;并且在开源工程中没有放置我们提供的SDK包——保证了对外公布SDK渠道的唯一性。这些信息都做成了可配置的、并且在readMe中做了说明,优雅而专业。
思考
这位外国同行的解决问题的思路显然更加程式化,更加偏向于用固有宜用的流程来思考,这就显得更加专业。代码实现上也有不少值得借鉴的地方,有此一例照猫画虎,之后针对硬件的命令传输用例的实现就非常方便,甚至可以考虑根据不同的数据点数据类型自动化生成用例代码。并且这种用例工程作为SDK的提供方和使用方的沟通工具也是不错的,能够解决一些使用问题上的误解——使用一方可以提供不通过的用例让提供方修改SDK源码/提供一方也可以通过用例证明SDK本身并无问题。