一、前言
在前端快速迭代的背景下,自动化测试变的尤为重要,它是质量保证、人力提效的关键。对于 Web 端自动化测试我们常常可见的就是单元测试、集成测试和 UI 测试等。那么对于小程序而言,是否有一整套的自动化测试玩法呢?不仅做到界面的 UI 检查、又能实现端到端完整的功能测试?答案是有的~
接下来,我们将结合当前微盟 CRM 业务场景,从 0->1为 大家介绍如何实现小程序自动化测试。
二、背景
CRM 是微盟小程序必不可少的业务,在频繁的迭代下如何保证质量更是重中之重。在仅使用人力测试遇到非常多的痛点和局限:
1、本身 CRM 涉及业务非常广泛,会员、积分、礼品卡等等,光是页面就接近50个,仅主流程回归就需要4~5day人力。
2、CRM有非常多的下游依赖,譬如 CMSSDK 涉及的授权能力,当依赖发生变更,需要机械性回归的场景非常多,纯人力去操作不仅效率低效而且会遗漏些关键场景。
3、真实的用户场景可能会遇到网络异常、服务异常等情况,而我们的测试环境很难去模拟这样的灾难测试。譬如 CRM 礼品卡,在网络异常情况下,就会出现页面表现不一致的情形(白屏/内容塌陷/一直loading加载)。
在这样背景下,我们开始了自动化测试的路程~
三、技术选型
但是对于小程序这样的载体,基于 CDP 协议的工具有以下的缺点:
真机兼容性:小程序底层内核多样化,是否能成功连接真机有很大风险。
能力受限:基于小程序安全问题,CDP相关工具只能做到 UI 测试,与我们的需求不符。
测试平台不足:测试覆盖不够,真机往往是链接的安卓调试,缺少 iOS 真机测试。
实操中,我们无法通过 CDP 的协议去控制小程序 JS 逻辑行为,只能简单的控制页面滚动、点击、跳转等。
此种方案不符合我们真实的需求,我们希望能够获取小程序的逻辑层,进行数据的修改,请求的发起等等。
miniprogram automator,可以控制小程序页面的行为以及事件。
控制小程序跳转到指定页面。
获取小程序页面数据。
获取小程序页面元素状态。
触发小程序元素绑定事件。
调用 wx 对象上任意接口。
相比于第一种方案,此方案可以获取到逻辑层初步满足业务场景的覆盖,但是它无法完成小程序的自动授权(地理位置等),这会让我们 CRM 很多业务场景受限。
云测自动化,由微信测试团队自主研发,联合 WeTest 云真机能力,共同推出的小程序自动化测试服务
丰富的自动化测试能力
全面的性能分析能力
支持持续集成,打通业务 Devops 流程
支持第三方服务商
第二、和第三种方案都是比较符合我们的期望,我们进一步进行对比这两种的优劣:
从生态、支持语言、真机配置、测试框架等角度分析,云测作为解决方案是最合适我们的场景,我们无须配置复杂的真机场景,并且可以结合 OpenAPI 去构建专属CRM 业务的自动化测试。
接下来我们具体展开如何基于云测完成我们的自动化测试能力。
四、实现方式
1、针对各自的业务情况,使用云测提供的miniTest框架去编写测试用例。
2、登录云测平台,可通过开发者工具云测插件进入即可。
3、上传我们第一步的测试用例。
4、指定计划,让云真机按测试用例执行跑测。
#!/usr/bin/env python3
import minium
class FirstTest(minium.MiniTest):
def test_auth_btn(self):
self.app.navigate_to("/pages/home")
page = self.app.get_current_page()
page.element_is_exists("#auth-btn")
整个架构分为四层:
底层依赖 Python3、MiniTest的执行环境,我们通过 pipenvfile 进行依赖的管理,为了方便大家理解,可以把它类比于 package.json(如下图),定义了源、开发环境依赖、脚本 Scripts 等信息。
# 下载源
[[source]]
url = "https://pypi.org/simple"
# 包依赖
[packages]
requests = "*"
minium = "*"
# 开发依赖
[dev-packages]
# 外部依赖
[requires]
python_version = "3.9"
# 执行脚本
[scripts]
uploadCase = "python3 ./scripts/uploadCase.py"
test = "minitest -s suite.json -c config.json -g"
配置层主要用于开发环境下,本地调试使用,通过 suite.json 进行用例执行顺序的编排,config.json 进行项目配置(项目路径、工具路径、端口等等)。
### config.json
{
# 项目路径
"project_path": "/Users/xxx/Desktop/weimobProject/marketingCloudProject/saas-fe-titan-hd/dist",
# 开发者工具路径
"dev_tool_path": "/Applications/wechatwebdevtools.app/Contents/MacOS/cli",
# 云测token
"myToken": "#####",
# 云测项目名称
"myProjectName": "####"
}
### suit.json
{
# 用例列表
"pkg_list": [
{
"case_list": [
"test_*"
],
"pkg": "case.testBuyAndSendCard"
}
]
}
测试用例分层,相比于传统的 PO 分层,我们颗粒度更细,将业务拆解出流程process(购买流程、加购流程等等),页面 Page 层,组件 Component 层,通过这几层组合成一个个用例。
最顶层就是我们的用例执行层了(回归测试、多面值加购等等),这些执行层本质上就是用例分层组合而来的。举个例子:执行层为回归测试计划(涉及购买流程、赠送流程)
// 执行层的回归测试
# 购买流程 + 赠送流程 测试用例
from case.base.baseCase import BaseCase
from case.base.basePage import BasePage
from case.process.giftingProcess import GiftingProcess
from case.process.buyGiftCardProcess import BuyGiftCardProcess
from case.base.route import route
class TestBuyAndSendCard(BaseCase, BasePage):
def __init__ (self, methodname='runTest'):
super(TestBuyAndSendCard, self).__init__(methodname)
self.giftingProcess = GiftingProcess(self)
self.buyGiftCardProcess = BuyGiftCardProcess(self)
"""
case1: 购买流程回归
"""
def test_001_testBuyGiftcard(self):
self.buyGiftCardProcess.startProcess()
"""
case2: 赠送流程回归
"""
def test_002_sendFriend(self):
self.giftingProcess.startProcess()
我们可以看到,其中依赖了两个process (giftingProcess,buyGiftCardProcess),而process中又包含了具体Page层的测试方法,我们看看buyGiftCardProcess内部长什么样子:
# 购买流程测试用例
from case.base.baseCase import BaseCase
from case.pages.home import HomePage
from case.base.basePage import BasePage
from case.base.route import route
from case.pages.templateDetail import TemplateDetail
import time
"""
礼品卡购买流程测试
礼品卡首页->礼品卡主题详情页->支付
"""
class BuyGiftCardProcess(BaseCase, BasePage):
def __init__ (self, mini):
BasePage.__init__(self,mini)
self.homePage = HomePage(mini)
self.templateDetail = TemplateDetail(mini)
self.cmsAuthPage = CMSAuth(mini)
"""
开启流程
组合相关页面方法
"""
def startProcess(self):
self.navigate_to_open(route["home"])
self.homePage.callAllPageFun()
self.templateDetail.callAllPageFun()
我们在 buyGiftCardProcess 中用到了 Page 层中的 home/templateDetail 页面的方法,同理这些page的方法也会用到下一层component暴露的能力。本质上,我们采用的就是组合模式以及命令模式将用例进行编排,这样做的好处就是复用性、拓展性更好,便于迭代和维护。
当然,作为一个完整的架构,我们还需要去约束我们用例编写的规范,举个例子,我们可以使用 Python 抽象类的方式要求每个页面必须实现校验路由、基本元素以及当前页面所有用例执行的调用,让后每个页面注入这样的抽象类进行约束。
from abc import ABCMeta, abstractmethod
# 定义页面抽象类 页面必须实现校验当前路由、校验基本元素、当前页面整体调用链路等方法
class StandardPage(metaclass=ABCMeta):
@abstractmethod
def callAllPageFun(self):
pass
@abstractmethod
def checkPath(self):
pass
@abstractmethod
def checkBasicElement(self):
pass
我们针对用例中经常出现的方法进行了统一封装,譬如高频点击事件、页面跳转事件、元素获取事件等等。同样,也提供了一些 Script 的能力,帮助编写用例人员能自动化创建用例模板、自动化去压缩并上传整个用例,下面是整个项目的文件目录:
├── Pipfile // 类似于package.json
├── case
│ ├── base
│ │ ├── baseCase.py // 测试用例的生命周期钩子
│ │ ├── basePage.py // 用例中常用的方法封装
│ │ ├── route.py // 路由文件
│ │ └── standardPage.py // 抽象类约束页面方法
│ ├── pages // 页面维度暴露测试方法
│ │ ├── components // 通用组件维度暴露测试方法
│ │ ├── exampleDetail.py
│ └── process // 流程维度暴露测试方法
│ └── exampleProcess.py
│ ├── example.py // 用例执行文件
├── config.json // 本地测试配置文件
├── debug.ipynb // 本地调试脚本
├── env // 运用在debug.ipynb 用于debug时候统一环境、统一方法
├── outputs // 本地日志输出
├── scripts // 脚本能力
├── suite.json // 本地用例编排,按这个文件执行测试用例
这样架构设计的目的是因为现代化的前端应用颗粒度非常细,涉及业务场景也很多,我们需要更合理的用例分层去应对。
相比于传统测试用例的编写,我们的优势在于以下四点:
现代化的分层设计
规范化的用例约束
统一依赖管理
本地调试能力
自动化的快捷能力
通过这样的架构设计,我们的测试团队能够快速且规范的编写用例,CRM 测试团队投入一人只需要两周时间就能够完成50个页面的基本 UI 测试。
(自动化跑测回归流程)
(自动化灾难模拟)
如上图,我们模拟了当礼品卡服务挂了之后,页面的展示情况,通过这样的模拟测试,我们很容易发现页面的一些问题,譬如白屏、一直 Loading、内容塌陷等等,这样一来我们可以针对性的对用户体验进行优化。
模拟当前服务挂了的代码也是比较简单的,这里举个简单的例子:
#!/usr/bin/env python3
import minium
class RequestTest(minium.MiniTest):
def test_mock_request(self):
self.app.restore_request() # 清空规则
mock_resp1 = {"errmsg": "服务暂不能使用", "statusCode": 500}
rule1 = ".*/SendMsg\\?.*"
url1 = "http://minitest.weixin.qq.com/SendMsg?content=test"
# 加入规则1
self.app.mock_request(rule1, fail=mock_resp1)
result = self.app.call_wx_method("request", [{"url": url1}]).get("result", {}).get("result")
self.assertDictEqual(result, mock_resp1) # 返回服务挂掉
CRM云测报告展示:
同时 mini-cli 提供了快速创建模版页面的能力,mini-cli initPage 可以读取路由文件,为我们批量生成测试用例模板,模版中已经有基础的测试方法,测试人员在对应位置写入用例即可。
* appid // 小程序appid
* projectPath // 本地项目路径(小程序dist包)
* privateKeyPath // 私钥路径 参考下方文档路径
* myToken // 自动化测试令牌
* myProjectName // 云测平台下项目英文名称
* QW_KEY // 企微webhook
* canPublishBranchList // 允许运行自动化测试的分支 默认都允许 否则string[]
* QW_reportInfo // 企微报告信息配置 planName -- 测试名称 desc -- 测试描述
* lowestPublishRate // 最低发布通过率
* taskID // 需执行的测试任务
1、QW_KEY 是用于后续将测试报告发送到企微机器人中,让开发、测试及时获取到跑测信息。
2、canPublishBranchList 用于校验当前分支是否允许发布开发版、是否允许自动化测试。
3、QW_reportInfo 允许业务方自定义一些测试信息用于测试报告通知使用。
4、lowestPublishRate 业务方可以自定义最低的测试通过率,当大于这个数字,会自动使用titan-cli发布当前包用于提测。
5、taskID 业务方云测的任务ID,若不填,会默认拉取所有测试任务列表,开发者在命令行自行选择。
我们遇到的第一个问题就是当开发者完成迭代后,如何让云测知悉要跑测当前最新的代码?
解决方案是使用 miniprogram-ci 能力去提交代码到最新的开发版,并且通过请求云测的 API 能力唤起自动化测试。这块能力集成到了mini-cli run test 中,开发人员只需执行即可,无须感知内部逻辑。
自动化测试的费时一般在20分钟左右,由于云测不提供任何回调能力,我们无法及时的拿到测试的结果,所以我们采取的方案是轮询请求,每间隔十分钟会自动请求云测的测试状态,当拿到了测试结果,mini-cli会进行组装测试数据和报告,并通过企微API的能力发送报告,及时告知开发人员测试结果。
五、总结
本文介绍了 CRM 自动化测试的架构设计,基于这样的架构下,CRM完成了50多个页面的 UI 测试,以及会员卡、礼品卡等多个业务场景的主流程回归。回归的功能点多达上百个,大大提高了测试效率,主流程的回归由原本的5day降到了1day,提升了80%的能效。另一个方面,灾难模拟测试暴露出小程序在边界情况下的问题,继而我们优化了页面的布局、不合理的 Loading 加载以及增加了骨架屏,给用户带来更好的体验。
通过 mini-cli 工具,CRM 开发人员自测阶段平均能发现1-2个问题,冒烟和提测的通过率接近100%,让 CRM 整体的研发质量得到进一步提升。
六、参考资料
1.云测平台官网
小程序云测-MiniTest(https://minitest.weixin.qq.com/#/minium/Python/readme)
2.CDP协议介绍
Chrome DevTools Protocol(https://chromedevtools.github.io/devtools-protocol/)
3.小程序自动化介绍
小程序自动化 | 微信开放文档(https://developers.weixin.qq.com/miniprogram/dev/devtools/auto/)