小程序架构上分为渲染层和逻辑层,尽管各平台的运行环境十分相似,但是还是有些许的区别(如下图),比如说JavaScript 语法和 API 支持不一致,WXSS 渲染表现也有不同,所以不论是手工测试,还是UI自动化测试,都必须要在 iOS 和 Android 上分别检查小程序的真实表现。
由于生态方面的原因,目前可选择的小程序UI自动化框架较少。在框架选取过程中,我考察了Appium、Airtest和Minium三个框架,并将三者做了对比,形成了以下图表:
Appium实现微信小程序自动化测试的手段基本上还是套用针对 Hybrid App 的测试方案,通过定位H5 App资源控件,并结合屏幕坐标的方式来操控小程序的页面元素;网易推出的Airtest则是基于图像识别和Poco控件识别,之前也对此框架做过比较深入的了解,但是和Appium一样,对于小程序自动化测试来说,以上两者无法深入小程序逻辑层,只能作用于渲染层,从另外一个角度来说,这两个框架还属于黑盒自动化测试的范畴。
接下来再介绍一下今天的主角:Minium。它是微信小程序官方推出自动化框架,提供了 Python3 和 JavaScript 版本(后者目前已停止维护,后文中的minium单指Python版本),目前最新的版本为1.0.0b2。minium不仅限于 UI 自动化,它还提供了很多有用的特性,比如说支持调用和 Mock 部分 wx 对象上的接口,支持获取和设置小程序页面数据,支持直接触发小程序元素绑定事件等等。
另外,minium提供一个基于unittest封装好的测试框架,利用这个简单的框架对小程序测试也可以起到事半功倍的效果。有了以上功能,不但可以简化用例的一些前期准备工作,更可以对小程序做更针对和更全面的测试。
minium的下载安装和官方文档,可以在代码库查看。官方文档写的还算较为清晰,除此之外,以下网站在学习过程中也有帮助:
早期 GUI 自动化测试脚本,无论是Selenium还是UFT,脚本通常是由一系列的页面控件的顺序操作组成的,有点像操作级别的“流水账”,这主要体现在以下几个方面:
Page Object 就是为了解决以上问题而出现的,它是UI自动化测试项目开发实践的最佳设计模式,采用分层封装的设计思想,不同层关心不同的问题。页面对象层只关心元素定位问题,测试用例只关心测试的数据。通过对界面元素和功能模块的封装减少冗余代码,在后期维护中,若元素定位或功能模块发生变化,只需要调整页面元素或功能模块封装的代码,显著提高测试用例的可维护性。
基于PO模式,小程序UI自动化测试Demo项目的目录结构及说明如下:
cases/: 存放业务测试用例;
outputs/: Minium测试报告;
pages/:页面对象模型;
*config.json:Minium项目配置文件;
suite.json:Minium测试计划文件;
route.py:统一存放小程序页面路由;
utils.py:存放一些公共方法;
下面从具体代码入手,简单讲述一下项目的设计思路。
首先是BasePage,它是页面模型基类,用于封装所有页面公用的方法。
import abc
import time
class BasePage(abc.ABC):
def __init__(self, mini, route, title=""):
self.mini = mini
self.route = route
self.title = title
def open(self):
"""跳转到小程序目标页面"""
self._open(self.route, self.title, open_type='redirect')
def check_element(self):
"""页面元素审查
在子类中实现此方法时,建议使用Minium框架中提供的断言方法,原因如下:
调用 Minium 框架提供的断言方法,会拦截 assert 调用,记录运行时数据和截图,自动在测试报告
中生成截图 (需要在配置文件中将 assert_capture 设置为True)
但是如果直接assert或使用unittest.TestCase提供的断言,当断言失败时,无法自动生成截图
"""
raise NotImplementedError def on_page(self, route,title=None, wait_util_page_contain_keys: list = None):
"""通过对title和route断言,校验跳转进入的当前页是否符合预期"""
if wait_util_page_contain_keysis not None and isinstance(wait_util_page_contain_keys, list):
self.mini.page.wait_data_contains(wait_util_page_contain_keys)
else:
time.sleep(2)
self.mini.assertEqual(self.current_route, route,
msg="页面路由不匹配, 预期:{},实际:{}".format(route, self.current_route))
if title:
self.mini.assertEqual(self.current_title, title,
msg="页面标题不匹配, 预期:{},实际:{}".format(title, self.current_title))
@property
def current_title(self) -> str:
"""获取当前页面 head title, 具体项目具体分析,以下代码仅用于演示"""
return self.mini.page.get_element("XXXXXX").inner_text
@property
def current_route(self) -> str:
"""获取当前页面route, 具体项目具体分析, 以下代码仅用于演示"""
return self.mini.app.get_current_page().path
def _open(self, route, title, open_type=None):
"""
小程序页面跳转可以使用以下三个方法, 一些区别如下:
1.`navigate_to`: 此方法会保留当前页面,并跳转到应用内的某个页面(不能跳到tabbar页面). 小程序中页面栈最多十层, 如果超过十层时,再使用此方法
跳转页面, 会抛出以下异常:`minium.framework.exception.MiniAppError: webview count limit exceed`. 因此需要在运行用例后及时清除页面栈;
2. `redirect_to`: 关闭当前页面,重定向到应用内的某个页面,使用此方法跳转页面时,会替换页面栈,因此页面栈不会超限,但是也导致不支持页面回退;
3. `relaunch`: 关闭所有页面,清空页面栈,打开到应用内的某个页面;
"""
open_type = 'redirect' if open_type is None else open_type if open_type.lower() == "navigate":
self.mini.app.navigate_to(route)
elif open_type.lower() == "redirect":
self.mini.app.redirect_to(route)
else:
self.mini.app.relaunch(route)
self.on_page(route, title)
具体业务的页面模型对象都需要继承BasePage,以IndexPage为例,代码如下所示:
from pages.BasePage import BasePage
from route import XXXXX
class IndexPage(BasePage):
locators = {
"AAA": "view#aaa",
"BBB": "view.bbb>image"
}
def check_element(self):
self.mini.assertTrue(self.mini.page.element_is_exists(IndexPage.locators['AAA']) is True)
self.mini.assertTrue(self.mini.page.element_is_exists(IndexPage.locators['BBB']) is True)
def click_query_btn(self):
self.mini.page.get_element("view", inner_text="xxxx").click()
self.on_page(route=XXXXX.XXXX.route, title=XXXXX.XXXX.title)
BaseEntity为测试用例基类,用于统一设置用例准备和清理工作,所有项目的测试用例都继承此类:
from pathlib import Path
import minium
class BaseEntity(minium.MiniTest):
"""测试用例基类"""
@classmethod
def setUpClass(cls):
super(BaseEntity, cls).setUpClass()
output_dir = Path(cls.CONFIG.outputs)
if not output_dir.is_dir():
output_dir.mkdir()
@classmethod
def tearDownClass(cls):
super(BaseEntity, cls).tearDownClass()
cls.app.go_home()
def setUp(self):
super(BaseEntity, self).setUp()
def tearDown(self):
super(BaseEntity, self).tearDown()
cases.Moudle_1.index_test.IndexTest代码内容如下:
from cases import BaseEntity
from pages.Moudle_1.IndexPage import IndexPage
from route import XXXXX
class ParkIndexTest(BaseEntity):
def test_index_page(self):
index_page = IndexPage(self, XXXXX.INDEX.route, XXXXX.INDEX.title)
index_page.open()
index_page.check_element()
index_page.click_query_btn()
待研究方案:小程序页面对象自动生成,不用再手工维护 Page Class ,只需要提供页面路由,就会自动生成这个页面上控件的定位信息,并自动生成 Page Class;
最后: 可以在公众号:伤心的辣条 ! 自行领取一份216页软件测试工程师面试宝典文档资料【免费的】。以及相对应的视频学习教程免费分享!,其中包括了有基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等。
现在我邀请你进入我们的软件测试学习交流群:【746506216
】,备注“入群”, 大家可以一起探讨交流软件测试,共同学习软件测试技术、面试等软件测试方方面面,还会有免费直播课,收获更多测试技巧,我们一起进阶Python自动化测试/测试开发,走向高薪之路。
喜欢软件测试的小伙伴们,如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一 键三连哦!
这才是2022最精细的自动化测试自学教程,我把它刷了无数遍才上岸字节跳动,做到涨薪20K【值得自学软件测试的人刷】
接口性能测试 — 软件测试人必会618实战场景分析
软件测试工程师月薪2W以上薪资必学技能 — Python接口自动化框架封装.
美团面试真题_高级测试25K岗位面试 — 软件测试人都应该看看
测试开发之全面剖析自动化测试平台 — 软件测试人的必经之路
软件测试必会_Jmeter大厂实战 — 仅6步可实现接口自动化测试
Jmeter实战讲解案例 — 软件测试人必会