前言
公司要求做小程序的自动化,最开始让我用jest+SDK做,但后面学了两天,困难重重,第一,这种方式只支持JS语言,我以前从来没学过JS语言,JS一窍不通;第二,网上资料太少太少了,本来对JS就不懂,网上资料还少,后面去网上百度了下,minium支持python语言,我原来有一点python基础,所以最后改用了python+minium框架。
一、minium介绍
minium提供一个基于unittest封装好的测试框架,MiniTest是minium中继承自unittest.TestCase的测试基类, 你可以在testcase中使用框架实例化好的Minium/App/Native实例,也可以使用unittest中的各种断言函数,并做了以下改动:
1、加载读取测试配置
2、在合适的时机初始化minium.Minium、minium.App和minium.Native
3、根据配置打开IDE,拉起小程序项目或自动打开真机调试
4、拦截assert调用,记录检验结果
5、记录运行时数据和截图,用于测试报告生成
二、安装环境
1. 安装minium doc
(这个主要是minium框架的一些介绍,可以略过不安装直接去官网查看文档:https://minitest.weixin.qq.com/#/minium/Python/readme)
(1)该文档使用 docsify 框架,需先安装 docsify
npm i docsify-cli -g
(2)下载文档(会要求填写账号密码,指的是微信代码管理的账号和密码)
git clone https://git.weixin.qq.com/minitest/minium-doc
(3)安装依赖
cd minium-doc
npm install
(4)本地部署(浏览器能浏览http://localhost:3000/说明文档安装好了)
docsify serve .
2. 安装minium
(1)运行环境
Python 3.8及以上
微信开发者工具安全模式: 设置 -> 安全设置 -> 服务端口: 打开
确认微信公共库版本 >= 2.7.3
(2)手动安装
下载minium安装包, 解压后进入文件夹, 运行
python3 setup.py install
(3)自动安装
pip3 install https://minitest.weixin.qq.com/minium/Python/dist/minium-latest.zip
3. 启动小程序
import minium
class TestMiniprogram(minium.MiniTest):
def test_01(self):
pass
命令行运行脚本
minitest -c config.json -m tests.igtest
三、准备知识
1. 启动
minium.MiniTest类里面已经封装好了小程序的启动、关闭、调用配置、执行测试用例等一系列的方法,所以我们编写测试用例脚本的时候,定义的类在继承minium.MiniTest类之后,可以直接写测试用例,无需关注怎么启动。
2. 配置
minium框架里面默认配置的项目路径以及CLI工具路径都为None,所以会加载默认配置,如果我们的项目路径以及CLI工具路径不是用的默认路径,执行会报错找不到路径,所以我们需要在项目路径下新建一个config.json文件,将里面的project_path改为你的小程序项目路径,dev_tool_path改为你的CLI工具路径
3. 命令行运行
相关字段的说明
minitest -c config.json -m tests.igtest -g
-c 指定配置文件
-m 指定要执行的用例文件名(注意不需要.py)
-g 生成测试报告
4. 元素定位
(1)单选择器定位:一般可以使用.class或者#id去定位到元素
(2)多选择器定位:如果元素class有重名,id也有相同的,可以使用.class+#id去定位
(3)组合定位:如果有多个元素的class相同,id又是变化的,可以使用page.get_element(‘.main-menu-txt’, inner_text=’租赁合同’, text_contains=‘租赁合同’),或者使用page.get_elements(‘.main-menu-txt’)[序号]
5. 断言
常用的断言主要有三种:
(1)assertEqual(first, second, msg)
first == second时,断言成功,用例结果符合预期
first != second时,断言失败,抛出错误信息及msg
(2)assertTrue(expr, msg)
expr为True,断言成功,用例结果符合预期
expr为False,断言失败,抛出错误信息及msg
(3)assertTexts(texts, selector, msg)
texts中每个元素的值都包含在selector选择器对应的元素文本集合中,则断言成功,否则,断言失败,抛出错误信息及msg
6. 第一个测试用例
import minium
class TestIg(minium.MiniTest):
def test_01_login(self):
self.mini.page.wait_for(2) # 当前页面等待2s
self.mini.page.get_element('.login-word-input').input(user) # 输入用户名
self.mini.page.get_element('.login-pass').input(password) # 输入密码
self.mini.page.get_element('.loginBtn').click() # 点击登录按钮
self.mini.page.wait_for(3) # 当前页面等待3s
self.assertEqual('/pages/main/index', self.mini.app.current_page.path, msg='登录失败')
# 断言登录后是否跳转到首页,如果断言失败,则返回mgs"登录失败"
minitest -c config.json -tests.igtest
四、用例设计模式
上面第一个测试用例写到了登录,非常简单常见的一个场景,但像我在做的这个项目,在登录后,跳转到首页后有项目切换、项目信息、项目试算、租赁订单、租赁合同等等非常多模块的定位、元素点击、页面滑动、页面跳转以及断言等,把这么多代码全部写到一个类里面,代码多而繁杂,如果页面元素有更改,或者需要增加用例,查找起来非常不方便,更不要说整个小程序内有那么多页面,所以需要进行分层管理,我使用的是PO模式
1. 什么是PO模式
PO模式,即page object mode,页面对象模式,通过对界面元素和功能模块的封装减少冗余代码,同时在后期维护中,若元素定位或功能模块发生变化,只需要调整页面元素或功能模块封装的代码,提高测试用例的可维护性。
2. 层级关系
第一层:基础层BasePage,作用:封装一些minium的原生方法,如元素定位、框架跳转等
第二层:PO层,页面对象层,如元素定位、获得元素对象、页面操作
第三层:测试用例层,主要负责业务逻辑和数据驱动
三层之间的关系:PO层继承基础层的类,测试用例层调用PO层
3. 优点
(1)分层清晰,易读
(2)易维护,由于元素定位与业务逻辑的分层特性,元素定位变动只需要修改PO层的元素定位,不需要修改业务逻辑,容易维护
(3)可复用,由于在基础层已经对minium的原生方法进行过二次封装,所以方法具有一定的通用性,可复用(但这取决于二次封装方法的通用性有多高)
五、框架
1. 目录结构
(1)/cases/base/
/cases/base/basecase:测试用例基类,用于设置用例输出路径和清理工作,项目的测试用例都继承此类
/cases/base//basedef:页面基类,封装所有页面会用到的公用方法
/cases/base/router:小程序内各页面路径
(2)/cases/pages
页面对象模型:获得元素对象、页面操作等
(3)tests包
各页面的测试脚本
/tests/outputs
测试报告:记录各测试用例的执行情况
/tests/config.json
配置信息,包括小程序项目地址、cli工具地址等
/tests/suite.json
测试计划,配置要执行的用例及执行顺序
2. BaseCase基类
(1)复写minium.MiniTest类里面的setUpClass、tearDownClass、setUp、tearDown方法
setUpClass、tearDownClass前面一定要加@classmethod修饰器,这两个方法在整个测试计划执行期间只会执行一次,setUp、tearDown在每个测试用例case执行前后都会执行,minium.MiniTest内的setUp方法会将小程序恢复到默认页面,即小程序首页
(2)因为我写了很多页面,如果在每个页面里面的每条用例执行前都恢复到小程序首页,会大大增加用例执行时间,所以我复写的setUp、tearDown方法都直接pass了
from pathlib import Path
import minium
class BaseCase(minium.MiniTest):
"""测试用例基类"""
@classmethod
def setUpClass(cls):
super(BaseCase, cls).setUpClass()
output_dir = Path(cls.CONFIG.outputs)
if not output_dir.is_dir():
output_dir.mkdir()
@classmethod
def tearDownClass(cls):
super(BaseCase, cls).tearDownClass()
def setUp(self):
pass
def tearDown(self):
pass
3. BaseDef公共方法类
定义一些公共方法
class BaseDef:
def __init__(self, mini):
self.mini = mini
'''跳转到指定页面'''
def navigate_to_open(self, route):
self.mini.app.navigate_to