微信小程序自动化测试框架 微信小程序自动化测试框架 Minium——PO 模式测试用例
本文主要介绍 PO 模式的测试用例,PO 模式优点及层级间的关系,相关配置及运行
minitest 的测试小程序和测试 case:minitest-demo
- 
miniprogram-demo:测试小程序
- 
testcase:测试 case,同时也包含文档的测试 case
- 
testcase-PO:Page Object(PO) 模式的测试 case
PO 简介
PO 模式是自动化测试项目开发实践的最佳设计模式之一。通过对界面元素的封装减少冗余代码,同时在后期维护中,若元素定位发生变化,只需要调整页面元素封装的代码,提高测试用例的可维护性、可读性。
优点:
- 减少代码冗余
- 业务和实现分离
- 降低维护成本
PO 用例层级
- 基础层 base,初始化、封装 Minium 的原生方法
- BaseCase:初始化 Minium 实例,继承 MiniTest 类
- BaseDef:封装 Minium 的页面跳转、元素定位等方法
 
- 操作层 pages,页面对象层,各页面的元素定位,操作等
- BasePage:封装公用页面基础操作方法,继承 BaseDef 类
- HomePage:首页相关元素定位及相关操作等,继承 BaseDef 类
 
- 测试用例层,主要负责业务逻辑和数据驱动(继承 BaseCase 类,初始化 BasePage、HomePage 类)
- AppTest:测试 hook 用例
- ElementTest:测试操作不同组件用例
- LoginTest:测试登录授权用例
- NativeTest:测试处理原生控件用例
- PageTest:测试页面元素定位用例
 
BaseCase 基类
测试用例基类,继承 MiniTest 类。复写 minium.MiniTest 类里面的 setUpClass、tearDownClass、setUp、tearDown 方法
class BaseCase(minium.MiniTest):
    """
    初始化Minium实例,测试用例基类
    """
    @classmethod
    def setUpClass(cls):
        super(BaseCase, cls).setUpClass()
    @classmethod
    def tearDownClass(cls):
        super(BaseCase, cls).tearDownClass()
    def setUp(self):
        pass
    def tearDown(self):
        pass
BaseDef 公用方法类
封装 Minium 的页面跳转、元素定位等方法
class BaseDef:
    """
    封装Minium的页面跳转、元素定位等方法
    """
    def __init__(self, mini):
        """
        基类初始化 Minium 实例
        """
        self.mini = mini
    def navigate_to_page(self, route):
        """
        跳转到指定页面
        """
        self.mini.app.navigate_to(route)
        ret = self.wait_page(route)
        return ret
    def redirect_to_page(self, route):
        """
        跳转到指定页面并关闭当前页面
        """
        self.mini.app.redirect_to(route)
        ret = self.wait_page(route)
        return ret
    ## ...
/pages 页面对象类
页面对象层,各页面的元素定位,操作等
BasePage 基础页面对象类
封装公用页面基础操作方法,继承 BaseDef 类。例如 hook wx API 接口,点击元素,获取回调;hook 原生控件弹窗,点击元素,处理弹窗,获取回调等
class BasePage(base_def.BaseDef):
    """
    封装公用页面基础操作方法
    """
   def hook_wx_method(self, method, selector):
        """
        封装hook wx API接口,获取回调
        :param method: API接口
        :param selector: 触发元素选择器
        :return: 信号量,回调信息
        """
        callback = Callback()
        # hook wx API接口,获取回调后执行callback
        self.mini.app.hook_wx_method(method, callback=callback.callback)
        # 点击元素
        self.mini.page.get_element(selector).tap()
        is_called = callback.wait_called(timeout=10)
        callback_args = callback.get_callback_result(10)
        # 释放hook
        self.mini.app.release_hook_wx_method(method)
        return is_called, callback_args
    ## ...
HomePage 首页页面对象类
首页相关元素定位及相关操作等,继承 BaseDef 类

class HomePage(base_def.BaseDef):
    """
    首页相关元素定位及相关操作等
    """
    locator = {
        'app': '/page/view/navigator[1]/button',  # App接口测试
        'page': '/page/view/navigator[2]/button',  # Page接口测试
        'element': '/page/view/navigator[3]/button',  # Element接口测试
        'native': '/page/view/navigator[4]/button',  # Native接口测试
    }
    # 进入不同接口测试
    def interface_page(self, key):
        ele = self.mini.page.get_element(self.locator[key])
        ele.tap()
        ret = False
        if key == 'app':
            ret = self.wait_page(router.appPage)
        elif key == 'page':
            ret = self.wait_page(router.pagePage)
        elif key == 'element':
            ret = self.wait_page(router.elementPage)
        elif key == 'native':
            ret = self.wait_page(router.nativePage)
        return ret
/test 测试用例类
主要负责业务逻辑和数据驱动
AppTest
app 页面测试用例,测试 hook 调用
class AppTest(BaseCase):
    def setUp(self) -> None:
        super().setUp()
        # 页面跳转
        self.HomePage.interface_page("app")
    def __init__(self, methodName='runTest'):
        """
        初始化AppTest类,初始化基础页、首页类
        """
        super(AppTest, self).__init__(methodName)
        self.HomePage = home_page.HomePage(self)
        self.BasePage = base_page.BasePage(self)
    def test_hook_wx_method(self):
        """
        hook 小程序方法 getSystemInfo
        :return:
        """
        is_called, callback_args = self.BasePage.hook_wx_method("getSystemInfo", "#testhook1")
        self.assertTrue(is_called, "callback called")
        self.assertDictContainsSubset(
            {"errMsg": "getSystemInfo:ok"}, callback_args, "getSystemInfo"
        )
ElementTest
element 页面测试用例,测试操作不同组件用例
class ElementTest(BaseCase):
    def setUp(self) -> None:
        super().setUp()
        # 页面跳转
        self.HomePage.interface_page("element")
    def __init__(self, methodName='runTest'):
        """
        初始化ElementTest类,初始化基础页、首页类
        """
        super(ElementTest, self).__init__(methodName)
        self.HomePage = home_page.HomePage(self)
        self.BasePage = base_page.BasePage(self)
    def test_move(self):
        """
        movable-view 容器拖拽滑动
        """
        ele = self.BasePage.get_element_selector("movable-view")
        # reset 重置
        ele.move_to(0, 0)  # 把movable-view复位
        time.sleep(2)
        rect = ele.rect  # 记录初始位置
        self.logger.warn(rect)
        ele.move_to(20, 100)  # 移动到坐标为20, 100的地方
        time.sleep(2)
        self.assertDictContainsSubset(
            {
                "left": 20 + rect.x,
                "top": 100 + rect.y,
            },
            ele.rect,
        )
    ## ...
LoginTest
login 页面测试用例,测试登录授权用例
class LoginTest(BaseCase):
    @classmethod
    def setUpClass(cls) -> None:
        super().setUpClass()
        try:
            # 清空授权
            cls.mini.clear_auth()
        except:
            pass
    def setUp(self) -> None:
        super().setUp()
        # 页面跳转
        self.HomePage.switch_to_tabbar("/pages/mine/mine")
    def __init__(self, methodName='runTest'):
        """
        初始化登录类,初始化基础页、首页类
        """
        super(LoginTest, self).__init__(methodName)
        self.HomePage = home_page.HomePage(self)
        self.BasePage = base_page.BasePage(self)
    def test_wechat_login(self):
        """
        微信登录
        """
        self.BasePage.get_element_xpath("/page/view/navigator[1]/button").tap()
        time.sleep(2)
        ret = self.BasePage.wait_page(router.loginPage)
        self.assertTrue(ret, "页面跳转")
        # hook 页面方法,并获取回调信息
        is_called, callback_args = self.BasePage.hook_native_method(
            method="getUserProfile",
            selector="/page/view/button",
            attr_method="allow_get_user_info")
        self.assertTrue(is_called, "callback called")
        self.assertDictContainsSubset(
            {"errMsg": "getUserProfile:ok"}, callback_args, "授权用户信息")
    ## ...
NativeTest
native 页面测试用例,测试处理原生控件用例
class NativeTest(BaseCase):
    @classmethod
    def setUpClass(cls) -> None:
        super().setUpClass()
        try:
            # 清空授权
            cls.mini.clear_auth()
        except:
            pass
    def setUp(self) -> None:
        super().setUp()
        # 页面跳转
        self.HomePage.interface_page("native")
    def __init__(self, methodName='runTest'):
        """
        初始化NativeTest类,初始化基础页、首页类
        """
        super(NativeTest, self).__init__(methodName)
        self.HomePage = home_page.HomePage(self)
        self.BasePage = base_page.BasePage(self)
    def test_01_show_modal(self):
        """
        showModal弹框授权
        :return:
        """
        is_called, callback_args = self.BasePage.hook_native_method(
            method="showModal",
            selector="#testModal",
            attr_method="handle_modal")
        self.assertTrue(is_called, "callback called")
        # 小程序API showModal方法回调信息断言
        self.assertDictContainsSubset(
            {"errMsg": "showModal:ok"}, callback_args
        )
    ## ...  
PageTest
page 页面测试用例,测试页面元素定位用例
class PageTest(BaseCase):
    def setUp(self) -> None:
        super().setUp()
        # 页面跳转
        self.HomePage.interface_page("page")
    def __init__(self, methodName='runTest'):
        """
        初始化PageTest类,初始化基础页、首页类
        """
        super(PageTest, self).__init__(methodName)
        self.HomePage = home_page.HomePage(self)
        self.BasePage = base_page.BasePage(self)
    def test_custom_element(self):
        """
        自定义组件定位
        """
        ele = self.BasePage.get_element_custom("mytest>>>test2>>>text")
        self.assertEqual("this is test2", ele.inner_text, "元素定位")
    ## ...      
config.json 配置
{
  "project_path":"D:\\Program Files\\workspace\\miniApplet\\miniprogram-demo",
  "dev_tool_path":"D:\\download\\devtools\\微信web开发者工具\\cli.bat",
  "platform": "ide",
  "outputs": "outputs"
}
测试计划 suit.json 配置
配置需要执行的内容和顺序,以及所在的包。
pkg_list 字段说明要执行用例的内容和顺序, pkg_list 是一个数组,每个数组元素是一个匹配规则,会根据 pkg 去匹配包名,找到测试类,然后再根据 case_list 里面的规则去查找测试类的测试用例。可以根据需要编写匹配的粒度。注意匹配规则不是正则表达式,而是通配符
{
  "pkg_list": [
    {
      "case_list": [
        "test_*"
      ],
      "pkg": "test.*_test"
    }
  ]
}
运行
// 按照suite.json测试计划执行,输出报告
minitest -c config.json -s suite.json -g       
// 执行page_test中的测试用例
minitest -c config.json -m test.page_test -g 
// 执行page_test中的测试用例test_custom_element用例
minitest -c config.json -m test.page_test --case test_custom_element  
需要帮助
如果在微信小程序自动化测试过程中遇到任何问题,欢迎在 微信小程序云测服务 专区发帖反馈
也可以微信扫描二维码加入云测官方企业微信群,联系 MiniTest 小助手反馈


