fix: update docs

Description:

Log:
This commit is contained in:
mikigo 2024-01-05 18:12:56 +08:00
parent 0e888cd11e
commit 254a708932
4 changed files with 643 additions and 708 deletions

View File

@ -3,8 +3,6 @@
```shell
# ====================================
# Author : mikigo
# Time : 2022/3/18
# version 1.0
# ====================================
```
@ -44,15 +42,17 @@
整体的框架设计在《自动化测试架构设计》文档里面已经有详细描述了,这里贴一下整体的架构图:
![](https://pic.imgdb.cn/item/64f054c4661c6c8e54ff4948.png)
???+ note "架构图"
![](https://pic.imgdb.cn/item/64f054c4661c6c8e54ff4948.png)
为了突显本文的重点,抽取其中重要功能模块,如下图:
![](https://pic.imgdb.cn/item/64f054c2661c6c8e54ff4770.png)
???+ note "核心模块"
![](https://pic.imgdb.cn/item/64f054c2661c6c8e54ff4770.png)
### 3、目录结构
```shell
```shell title="框架结构"
autotest-basic-frame # 自动化测试基础框架
├── apps # 应用库
│   ├── autotest-dde-file-manager # 单独的应用仓库(应用库详细目录结构请看应用库设计方案)
@ -63,7 +63,7 @@ autotest-basic-frame # 自动化测试基础框架
│   │   ├── dfm_assert.py # 断言方法模块
│   │   ├── assert_res # 断言的图片资源
│   │   ├── tag # 标签
│   │   └── widget # 方法
│   │   └── widget # 方法
│ │      ├── __init__.py
│ │      ├── base_widget.py
│ │      ├── ui.ini # UI 定位的坐标配置文件
@ -99,8 +99,8 @@ autotest-basic-frame # 自动化测试基础框架
│   ...
├── setting # 全局配置模块
│ ├── __init__.py
│ ├── config.py
│   └── global_config.ini
│ ├── globalconfig.py
│   └── globalconfig.ini
├── conftest.py # Pytest 本地插件模块Hook
├── pytest.ini # Pytest 默认配置文件
├── docs # 文档目录
@ -109,19 +109,17 @@ autotest-basic-frame # 自动化测试基础框架
## 三、详细方案
### 1、应用库`apps`
### 1、==应用库apps==
所有应用库均放置在基础框架下的 `apps` 目录下见第二章节第3段目录结构内容。应用库的架构设计可以参考《AT 应用库设计方案》文档。
### 2、核心库`src`
### 2、==核心库src==
`src` 目录下为自动化测试的底层核心组件,通常来讲如果你需要使用到其中某一个功能模块,那么你需要显示的导入这个模块,然后才能使用该模块下的功能,如果你用到了十个功能模块,那你就需要导入十个。但是我们想让事情变得简单,一次导入,使用所有。
将所有的功能模块都进入到 `src` 的名称空间,在 `src/__init__.py` 里面我们设计成这样:
```python
# src/__init__.py
```python title="src/__init__.py"
from .cmdctl import CmdCtl
from .dogtails import Dogtail
from .find_image import FindImage
@ -150,10 +148,8 @@ class Src(CmdCtl, FindImage):
应用库里面使用的时候在 `widget/base_widget.py` 里面只需要唯一继承 `Src`
```python
```python title="widget/base_widget.py"
# 应用库里面方法基类
# widget/base_widget.py
from src import Src
class BaseWidget(Src):
@ -170,13 +166,11 @@ class BaseWidget(Src):
`__init__` 构造函数的原因是通过参数构造应用,并且传递 `number` 进来,可以实现多窗口的控制。
### 3、公共方法库`public`
### 3、==公共方法库public==
公共方法库里面每个应用都是一个单独的 `py` 文件,相互之间是独立的,每个 `py` 文件里面是该应用的方法类,比如:最常用的方法类 `dde_desktop_public_widget.py`
```python
# dde_desktop_widget.py
```python title="dde_desktop_public_widget.py"
from src import Src
class _DdeDesktopPublicBaseWidget(Src):
@ -200,16 +194,16 @@ class DdeDesktopPublicWidget(_DdeDesktopPublicBaseWidget):
pass
```
公共方法库意义:
==公共方法库意义==
- 公共方法库里面所封装的一些操作方法都是至少被2个应用都用到的这样做可以减少整体代码量从而减轻应用库代码的维护工作。
- 公共方法库里面所封装的一些操作方法都是至少被 2 个应用都用到的,这样做可以减少整体代码量,从而减轻应用库代码的维护工作。
- 公共方法库里面的编写形式(命名规则、定位的方案写法、注释的写法等等)具有一定的模板作用,这样即使是各个应用库都独立维护,所有的编码风格都是趋于相同的,因为大家都应该参照公共库里面的一些写法来写自己应用库里面的一些方法类,这样使得从公司的角度去看所有应用的自动化测试项目都是统一的。
- 可以通过公共方法库里面的一些方法,了解到其他应用的功能,对于我们需求理解,了解系统的方方面面也有好处。
当然,如果你的应用本身是属于根本就不需要和其他应用交互的,那么你可能不会用到这里面的功能,没关系,你所有的方法都可以直接写在应用库的业务层。
### 4、`conftest.py `
### 4、==conftest.py==
`conftest.py` 从功能上将是属于核心库(`src`)的内容,但是由于它的特殊性,即它是对应用库中的用例生效的,而且它的作用域是当前目录及以下,因此我们将它放到项目根目录。我们框架中有不少核心功能都是在这里面进行开发的,后面第四章会讲到细节。
@ -217,7 +211,7 @@ class DdeDesktopPublicWidget(_DdeDesktopPublicBaseWidget):
根目录下的 `conftest.py` 文件只会用来写 `Hook` 函数,`apps` 目录下的 的 `conftest.py` 文件只会用来写 `fixture`
### 5、setting
### 5、==setting==
全局配置模块,包含了以下配置文件:
@ -229,236 +223,3 @@ class DdeDesktopPublicWidget(_DdeDesktopPublicBaseWidget):
主要提供配置文件读取、动态获取一些常量(如项目根目录绝对路径 `(BASE_DIR)`、系统架构(`SYS_FRAME`)、时间字符串(`strftime`)、本机 `USERNAME` `IP` 等等)、公共 URL 等。
## 四、规范
### 1、版本及依赖
基础框架会根据自身的功能开发进行版本发布,不与某个应用版本绑定,但是,应用库会依赖于基础框架的版本。因此,我们建议在**应用库**目录下保存一个文本文件用于记录所依赖的基础框架版本,类似于开发应用的 `debian/control` 文件的功能,为了保持统一,这个文件就命名为 `control`,放在应用库根目录下。
### 2、核心库独立性
核心库不建议针对某一个应用单独做代码上特殊处理通常考虑针对某一种场景进行处理这些场景通常是存在一些共性的。比如说桌面的ID分配存在问题我们可以通过逻辑处理解决ID分配错误的问题但是这部分逻辑我们会在桌面的方法层里面去做。
### 3、兼容性
为了保持兼容性,公共方法库里面原则上不对老方法进行修改或删除,只进行新增。涉及到老方法修改或删除的,会进行提前预告,并在第二个版本之后(中间会隔一个版本过度)正式实施修改或删除。
### 4、命名规范
- 用例 ID
每个应用自己维护一套 ID在PMS上用例标题上将自动化ID以标签的形式写入比如[101]打开-启动应用后dock右键菜单
一个用例类里面有多个用例时,在用例名称后面加序号。
```python
class TestFileManager(BaseCase):
"""文管用例"""
def test_xxx_015_1(self):
pass
def test_xxx_015_2(self):
pass
```
注意 015_1不要写015_01。
- 不建议使用 PMS 上的用例ID
- PMS 上是6位数不够直观也不好维护在自动化下面去找一个带有 6 位数的文件是一个糟糕的事情。
- PMS 上的 ID 可能会发生变化,比如导出来导进去的过程中可能存在问题,如果发生变化之后,要想找出来对应修改自动化用例 ID将会是件非常痛苦的事情。
- **函数命名关键词列表**
| 名称 | 单词 |
| :----------------- | :----------------------------- |
| 左键点击 | click |
| 右键点击 | right_click |
| 双击 | double_click |
| 移动 | move_to |
| 拖动 | drag |
| 新建 | new |
| 拖动到 | drag_to |
| 从哪里拖动到哪里 | drag_something_from_xxx_to_xxx |
| 获取 | get |
| 获取某个元素的坐标 | get_location |
| 非特殊文件 | file |
| word文件 | doc |
| text文件 | text |
| 文件夹 | dir |
*关键词持续增加*
- **常量关键词列表**
| 名称 | 单词 |
| :----------------------------- | :--------- |
| 应用名称 | `APP_NAME` |
| 应用描述 | `DESC` |
| 本应用以外的其他应用,比如帮助 | `HELP` |
- **其他名称列表**
| 名称 | 单词 |
| ---------------------------- | :----------------------------------------------------------- |
| 方法包名 | widget |
| 方法文件名<br />(文管举例) | `dfm_widget.py`<br />`title_widget.py`<br />`right_view_widget.py`<br />`left_view_widget.py`<br />`pop_widget.py`<br />`base_widget.py`<br />`dfm_assert.py` |
**断言语句命名规范**
断言语句都是以 assert 开头
| 断言 | 语句 |
| :------------------------- | :----------------------------------------------------------- |
| 判断文件是否存在 | assert_file_exists<br />assert_file_not_exists |
| 判断桌面目录下文件是否存在 | assert_file_exists_in_desktop<br />assert_file_not_exists_in_desktop |
| 判断图片存在 | assert_image_exists<br />assert_image_not_exists |
| 判断影院中是否存在图片 | assert_image_exists_in_movie<br />assert_image_not_exists_in_movie |
| 判断元素是否存在 | assert_element_exist<br />assert_element_not_exist |
| 判断是否相等 | assert_equal<br />assert_not_equal |
| 判断是否为真 | assert_true<br />assert_false |
### 5、fixture 规范
由于 `Pytest` 框架中支持 `fixture` 的写法和 `Xunit` 写法的,所以应用库中可能存在混用的情况,为统一编码风格方便后续用例代码维护,现做以下规范说明:
- 抛弃 `Xunit` 的写法,统一采用 `Pytest` `fixture` 的写法。
- 应用内 `fixture` 谨慎使用 `autouse=True` ,非必要的情况下非常不建议使用这个参数。
- 调用 `fixture` 不能使用 `@pytest.mark.usefixture()`,使用直接在用例里面传入函数对象。
- 建议在一个 `conftest.py` 里面去写,一个应用也尽量维护一个 `conftest.py `文件。
- `fixture` 也需要写功能说明,函数名称要有具体含义。
### 6、方法编写规范
- 方法类型:
```python
if 没有用到实例对象:
if 没有用到类对象:
写静态方法,函数前加 @staticmethod
else
写类方法,函数前加 @classmethod
else:
直接写实例方法
```
举例:
```python
class TitleWidget:
def click_xxx_by_ui(self):
pass
@staticmethod
def click_xxx_by_ui():
pass
@classmethod
def click_xxx_by_ui(cls):
pass
```
- 函数名称不出现数字,需要表示数量的用单词表示。
- 函数功能注释。
- 没有参数,没有返回
```python
"""点击某个元素"""
```
- 有参数,没有返回
```python
"""点击某个元素
arg1:xxx
arg2:xxx
"""
```
- 有参数,有返回
```python
"""点击某个元素
arg1:xxx
arg1:xxx
return: xxx
"""
```
`Pycharm` 的注释模板也可以,只要体现了参数的类型和返回就行了。
- 暂不要求写类型注解。
### 7、用例case规范
- 以类的形式写用例,用例类集成用例基类。
- 用例类需要写注释。
- 测试用例函数说明(建议直接复制 PMS 上用例名称)
```python
"""右侧预览-图片类型信息栏基本信息"""
```
- 不写调试代码
```python
if __name__ == '__main__':
pytest.main()
```
- 所有用例都继承 `BaseCase`
- `BaseCase` 里面放一些测试数据的常量,比如文件名称,应用名称,简单讲就是一些通用的字符串。
这类是属于测试数据。
```python
class BaseCase(DfmAssert):
"""用例基类"""
NEW_FILE = "新建文本.txt"
TEST_FILE = "test.txt"
NEW_DIR = "新建文件夹"
NEW_WORD = "新建Word文档.doc"
APP_HELP = "damn"
class TestFileManager(BaseCase):
def test_xxx_001(self):
"""用例标题复制PMS上用例标题"""
dfm = DfmWidget()
dfm.click_xxx_by_attr(self.NEW_FILE)
dfm.new_file_in_desktop_by_cmd(self.NEW_FILE)
self.assert_true(dfm.get_process_status(self.APP_NAME))
```
用例里面尽量减少字符串的出现,更要减少相同字符串的出现。
- 统一文件注释头。
```python
#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
"""
:Author:email@uniontech.com
:Date :${DATE} ${TIME}
"""
```
### 8、其他规范
- 日志打印要在方法最前面,否则代码报错没有日志输出,不好定位问题。(我们会考虑继续使用拦截器打印日志)
- 所有的操作都需要有日志,包括 `sleep()`,我们会重写一个有日志输入的 `sleep`
- 业务层日志级别为 `INFO`
- hook 函数只能写到根目录下的 `conftest.py` 里面。
- `apps` 目录下的 `conftest.py` 原则上不会新增 `fixture`
- 固定目录或元素控件的操作,将操作方法写死,文件类操作将文件名留参数。
- 路径拼接规范:
- 系统中固定目录,路径拼接时使用波浪符号,比如:`~/Desktop/`,下层使用 `os.path.expanduser()`,它可以自动识别波浪符号。
- 项目下路径使用配置文件中的路径,比如:`Config.BASE_PATH`,因为项目是可以在任意路径运行的,需要动态拼接路径。

View File

@ -2,63 +2,412 @@
```shell
# ================================================
# Attribution : Chengdu Testing Department
# Time : 2022/6/16
# Author : mikigo
# ================================================
```
AT 开发规范是根据自动化测试运行两年多来,遇到问题解决问题而形成的一些解决方案,或者说经验总结;这些经验符合我们现阶段 AT 所应用的场景需要,也是我们经过长期思考,不断试错不断修正,并在自动化测试项目实践中检验过可行的。以此,希望能帮助参与到自动化的相关人员减少试错成本,更好、更快的编写用例及维护用例。
AT 开发规范是根据自动化测试运行多年以来,遇到问题解决问题而形成的一些解决方案,或者说经验总结;
# 一、标签化管理规范
这些经验符合我们现阶段 AT 所应用的场景需要,也是我们经过长期思考,不断试错不断修正,并在自动化测试项目实践中检验过可行的。
标签化管理文档请参考: [《用例标签化管理操作指引》](http://10.8.10.215/用例标签化管理操作指引.html)
以此,希望能帮助参与到自动化的相关人员减少试错成本,更好、更快的编写用例及维护用例。
以下说几个容易出现的问题:
## 1. 版本及依赖
## 1. 对应关系
基础框架会根据自身的功能开发进行版本迭代发布,==基础框架不与某个应用版本绑定==
- 写完自动化用例之后,请在 `CSV` 文件中标记用例的 ID、等级。为了提醒标记执行用例时在首行会输出 `ERROR` 日志: `CSV 文件里面没有对应的 ID`
但是,==应用库会依赖于基础框架的版本==。因此,我们建议在==应用库==目录下保存一个文本文件用于记录所依赖的基础框架版本,类似于开发应用的 `debian/control` 文件的功能,为了保持统一,这个文件就命名为 `control`,放在应用库根目录下。
在最终提交代码之前,你需要在 `CSV` 文件里面将ID、用例等级等标签补全如果 `CSV` 文件里面没有对应 ID后续在批量执行的时候这些用例是不会执行的。
## 2. 命名规范
## 2. 名称一致
- ==用例 ID==
- `CSV` 文件的文件名、用例函数中间的名称一致、用例 `py` 文件中间的名称,这三个名称一致。
每个应用自己维护一套 ID可以是你自定义的 ID 值,也可以是用某些特有的 ID比如 PMS 用例ID
举例:
一个用例类里面有多个用例时,在用例名称后面加序号。
```python
# test_music_679537.py
def test_music_679537():
"""用例标题"""
pass
```python title="多用例函数命名"
class TestFileManager(BaseCase):
"""文管用例"""
def test_xxx_015_1(self):
pass
def test_xxx_015_2(self):
pass
```
那么 `CSV` 文件的名称为 `music.csv`
AT 框架底层代码实现是将 `CSV` 文件的名称与用例函数名称名称(`item.name`)进行对应,因此,注意 `CSV` 文件名称对应的是用例函数名称,即 `def test_music_001` 里面的 `music` ,而不是 `test_music_001.py` 里面的 `music`
这里细细品一下哈~
如果你将上例写成了这样:
- ==方法函数命名==
???+ note "方法函数命名关键词列表"
| 名称 | 单词 |
| :----------------- | :----------------------------- |
| 左键点击 | click |
| 右键点击 | right_click |
| 双击 | double_click |
| 移动 | move_to |
| 拖动 | drag |
| 新建 | new |
| 拖动到 | drag_to |
| 从哪里拖动到哪里 | drag_something_from_xxx_to_xxx |
| 获取 | get |
| 获取某个元素的坐标 | get_location |
| 非特殊文件 | file |
| word文件 | doc |
| text文件 | text |
| 文件夹 | dir |
- ==常量关键词命名==
???+ note "常量关键词列表"
| 名称 | 单词 |
| :----------------------------- | :--------- |
| 应用名称 | `APP_NAME` |
| 应用描述 | `DESC` |
| 本应用以外的其他应用,比如帮助 | `HELP` |
- ==方法层文件名==
???+ note "方法层文件名称列表"
| 名称 | 单词 |
| ---------------------------- | :----------------------------------------------------------- |
| 方法包名 | widget |
| 方法文件名<br />(文管举例) | `dfm_widget.py`<br />`title_widget.py`<br />`right_view_widget.py`<br />`left_view_widget.py`<br />`pop_widget.py`<br />`base_widget.py`<br />`dfm_assert.py` |
- ==断言语句名称==
???+ note "断言语句命名规范"
断言语句都是以 assert 开头
| 断言 | 语句 |
| :------------------------- | :----------------------------------------------------------- |
| 判断文件是否存在 | assert_file_exists<br />assert_file_not_exists |
| 判断桌面目录下文件是否存在 | assert_file_exists_in_desktop<br />assert_file_not_exists_in_desktop |
| 判断图片存在 | assert_image_exists<br />assert_image_not_exists |
| 判断影院中是否存在图片 | assert_image_exists_in_movie<br />assert_image_not_exists_in_movie |
| 判断元素是否存在 | assert_element_exist<br />assert_element_not_exist |
| 判断是否相等 | assert_equal<br />assert_not_equal |
| 判断是否为真 | assert_true<br />assert_false |
## 3. Fixture 规范
为统一编码风格方便后续用例代码维护,现做以下规范说明:
- 不建议使用 `Xunit` 的写法,统一采用 `Pytest` `fixture` 的写法。
- 应用内 `fixture` 谨慎使用 `autouse=True` ,非必要的情况下非常不建议使用这个参数。
- 调用 `fixture` 不能使用 `@pytest.mark.usefixture()`,使用直接在用例里面传入函数对象。
- 建议在一个 `conftest.py` 里面去写,一个应用也尽量维护一个 `conftest.py `文件。
- `fixture` 也需要写功能说明,函数名称要有具体含义。
## 4. 方法编写规范
- 方法类型使用逻辑:
```python
# test_music_679537.py
def test_movie_679537():
"""用例标题"""
pass
if 没有用到实例对象:
if 没有用到类对象:
写静态方法,函数前加 @staticmethod
else
写类方法,函数前加 @classmethod
else:
直接写实例方法
```
用例执行没问题,但是标签化管理是不生效的,框架无法将 `music.csv`文件里面标签,添加到 `test_movie_001`这个用例中,因为他们没有对应关系。
举例:
# 二、分支及 tag 管理规范
```python
class TitleWidget:
def click_xxx_by_ui(self):
pass
@staticmethod
def click_xxx_by_ui():
pass
@classmethod
def click_xxx_by_ui(cls):
pass
```
- 函数名称不出现数字,需要表示数量的用单词表示。
- 函数功能注释。
- 没有参数,没有返回
```python
"""点击某个元素"""
```
- 有参数,没有返回
```python
"""点击某个元素
arg1:xxx
arg2:xxx
"""
```
- 有参数,有返回
```python
"""点击某个元素
arg1:xxx
arg1:xxx
return: xxx
"""
```
`Pycharm` 的注释模板也可以,只要体现了参数的类型和返回就行了。
- 暂不要求写类型注解。
## 5. 用例编写规范
### 5.1. 基于类写用例
所有用例都应该基于类去写:
```python
class TestMusic(BaseCase):
"""音乐用例"""
def test_music_679537(self):
"""音乐启动"""
```
注意以下几点:
- ==类名不要随便取==,同一个应用应该使用同一个类名,用例类名称必须以 Test 开头,遵循大驼峰命名规范;
- ==用例类继承 BaseCase==,一个应用只有一个 `BaseCase`
- ==一个 py 文件里面只有一个类==,我们称为一个测试类;
- ==一个类里面可以有多个用例函数==,这取决这条用例有多少个测试点:
```python title="test_music_679537.py"
class TestMusic(BaseCase):
"""音乐用例"""
def test_music_679537_1(self):
"""任务栏启动音乐"""
def test_music_679537_2(self):
"""启动器启动音乐"""
def test_music_679537_3(self):
"""桌面启动音乐"""
```
### 5.2. 用例函数规范
- 用例函数以 test 开头,遵循蛇形命名规范,中间为用例的模块名称,后面加用例 ID最后加测试点序号
```shell
test_{module}_{case_id}[_{index}]
```
比如:`test_music_679537_1``index` 从 1 开始。
- ==函数功能说明== 里面写用例标题,==直接复制 PMS 上用例标题即可==,注意用 ==三对双引号==
- ==复制 `PMS` 用例步骤==
直接将 `PMS` 上用例步骤和预期复制进来,然后进行批量注释( ++ctrl+"/"++ ),在注释的基础上去写用例脚本会更加方便全面,也比你自己写注释更节约时间:
举例:
???+ note "PMS用例"
![](https://pic.imgdb.cn/item/64f054c8661c6c8e54ff4c71.png)
直接选中用例内容,复制下来,然后粘贴到自动化用例脚本中:
```python title="test_music_679537.py"
class TestMusic(BaseCase):
"""音乐用例"""
def test_music_679537(self):
"""演唱者-平铺视图下进入演唱者详情页"""
# 1
# 点击右上角的【平铺视图】按钮
# 切换为平铺视图
# 2
# 双击任意演唱者封面
# 进入演唱者详情页面
```
上例中井号注释部分就是直接从 `PMS` 上复制过来的,在此基础上写用例:
```python title="test_music_679537.py"
class TestMusic(BaseCase):
"""音乐用例"""
def test_music_679537(self):
"""演唱者-平铺视图下进入演唱者详情页"""
music = DeepinMusicWidget()
music.click_singer_btn_in_music_by_ui()
# 1
# 点击右上角的【平铺视图】按钮
music.click_icon_mode_in_music_by_ui()
# 切换为平铺视图
# 2
# 双击任意演唱者封面
music.double_click_first_singer_in_singer_icon_view_by_ui()
# 进入演唱者详情页面
self.assert_xxx
```
你看,非常清楚每一步在做什么,重点是省去了写注释的时间,真的炒鸡方便。
### 5.3. 数据驱动
- 如果用例操作步骤是相同的,只是一些参数变化,尽量使用数据驱动来实现用例;
- 如果你需要使用外部文件 ==存放数据== 驱动的数据,==尽量不要因此引入依赖==,可以使用一些标准库能读取的文件格式,比如 `json、ini、CSV、xml、txt` 等文件格式;==不建议== 使用 `Yaml、Excel、MySQL` 等数据格式;
- ==读取数据== 时也尽量使用标准库去做,如使用 `pandas` 处理 `CSV` 就属于大材小用了,正常的数据驱动还没到需要大数据分析来处理的地步;
- 数据驱动的外部文件存放在 ==widget/ddt/== 目录下;
- 数据驱动的写法:
```python
@pytest.mark.parametrize("value", data)
def test_smb_049(self, value):
```
以上这种参数化的写法本身没什么问题,但是,这里必须要补充一个没有用的小知识:
- 如果参数化数据里面的字符会原封不动的输出到 `item.name` 里面,显示非常不优雅,而且可能会引入一些意想不到的问题,可以感受一下:
参数:
```python
data = [
"一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三",
"qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyui",
"12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678",
]
```
终端日志打印出来,现象是这样色儿的:
```shell
test_smb_049.py::TestFileManager::test_smb_049[一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三]
test_smb_049.py::TestFileManager::test_smb_049[qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyui]
test_smb_049.py::TestFileManager::test_smb_049[12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678]
```
说实话,看着心里堵得慌,如果这里面包含一些**特殊字符**或者是**超长**,可能还会有一些很奇妙的事情发生。
- parametrize 里面有个参数:==ids==,可以解决此类问题,就像这样:
```python
@pytest.mark.parametrize("value", data, ids=[1, 2, 3])
def test_smb_049(self, value):
```
再来感受一下:
```shell
test_smb_049.py::TestFileManager::test_smb_049[1]
test_smb_049.py::TestFileManager::test_smb_049[2]
test_smb_049.py::TestFileManager::test_smb_049[3]
```
明显好多了,所以尽量使用 ids 这个参数。
- 不建议使用 `fixture` 的数据驱动方式,框架虽然支持,但可读性比较差;如果你不知道这句话在说啥,那你可以忽略,我也不打算详细说这种实现方式,操作比较骚。
### 5.4. 断言资源
- ==用例断言的图片资源==,直接放在 ==用例模块的同级目录下的 assert_res 目录== 下,图片名称以 ==用例的模块名称 + 用例 ID 命名==
- 图像识别断言,不要截取一张很大的图,图片资源包含的元素太多了,非常容易受到需求影响,建议是进行局部的断言;
### 5.5. 元素定位
- 用于 ==用例操作步骤中进行元素定位的图片资源==,放到 ==widget/pic_res 目录== 下,图片名称命名为该元素的名称;
- 用于元素定位的图片截取时尽量精确到这个具体的按钮,图片也不要太大;
- 基于 UI 定位的操作较快,合理加入等待时间能提高用例的稳定性。
### 5.6. 用例资源
- 用例执行过程中需要使用到的一些资源,==存放在 widget/case_res 目录== 下,前提是这些资源不超过 ==10M==
- 如果是一些比较大的资源,建议放到统一的 ftp 服务器,需要执行用例的时候再下载下来;
- ==确保一个资源在一次用例执行中只需要下载一次==,如果每次使用的时候都去下载,这样可能会耗费大量的网络资源,而因为先判断本地是否存在此资源,如果不存在再去下载;
- 测试用例执行过程中,你可能需要将资源拷贝到对应的测试目录下,比如将 mp3 文件拷贝到 `~/Music` 目录下,但是我们更建议你使用发送快捷链接的方式替代拷贝的操作,因为在拷贝大文件时是很消耗系统资源的,而创建链接则不会;
```python
class DeepinMusicWidget:
@classmethod
def recovery_many_movies_in_movie_by_cmd(cls):
"""恢复多个视频文件至视频目录中"""
work_path = f"/home/{Config.USERNAME}/Videos/auto"
code_path = f"{Config.CASE_RES_PATH}/auto"
cls.run_cmd(f"rm -rf {work_path};mkdir {work_path}")
sleep(1)
flag = False
if not exists(code_path):
cls.run_cmd(f"mkdir -p {code_path}")
flag = True
logger.info(f"ln -s {code_path}/* {work_path}/")
cls.run_cmd(
f"cd {code_path}/;"
f"{cls.wget_file('auto.zip') if flag else ''}"
f"ln -s {code_path}/* {work_path}/ > /dev/null 2>&1"
)
```
资源下载过程中注意超时的问题,如果你的测试资源很大,要特别注意这问题,如果你使用强制等待下载结束( `os.system` ),可能会造成用例执行时长变得不可接受,目前我们发现在持续集成环境执行时网络下载速度很慢,所以超时机制是很有必要的;`run_cmd` 方法有一个默认超时的时间,你可以根据资源大小对超时时间进行调整;
## 6. 标签化管理规范
### 6.1. 对应关系
写完自动化用例之后,请在 `CSV` 文件中标记用例的 ID、等级等标签。
为了提醒标记,执行用例时在首行会输出 `ERROR` 日志: `CSV 文件里面没有对应的 ID`
如果 `CSV` 文件里面没有对应 ID后续在批量执行的时候这些用例是不会执行的。
### 6.2. 名称一致
==CSV 文件的文件名==、==用例 py 文件中间的名称==、==用例函数中间的名称==,这三个名称一致。
举例:
```python title="test_music_679537.py"
class TestMusic:
def test_music_679537():
"""用例标题"""
```
那么 `CSV` 文件的名称为 ==music.csv==。
框架底层代码实现是将 ==CSV 文件的名称== 与 ==用例脚本名称== 进行对应(建立映射);
## 二、分支及 tag 管理规范
参考《[自动化测试代码管理规范](https://filewh.uniontech.com/lib/d01a57df-cba6-44f2-af8e-4cc412ad1880/file/01.%E4%BA%A7%E5%93%81%E5%BC%80%E5%8F%91%E4%BD%93%E7%B3%BB/02%20%E4%BA%A7%E5%93%81%E7%A0%94%E5%8F%91/01-3%20%E6%B5%8B%E8%AF%95%E8%BF%87%E7%A8%8B/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95/02-%E8%A7%84%E8%8C%83/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E4%BB%A3%E7%A0%81%E7%AE%A1%E7%90%86%E8%A7%84%E8%8C%83%20V2.0.pdf)》
## 2. 应用库 tag
### 2. 应用库 tag
- 应用库 tag 根据应用交付节点生成,每次打 tag 之前,相关测试人员需要进行用例调试;
@ -86,7 +435,7 @@ AT 开发规范是根据自动化测试运行两年多来,遇到问题解决
生成 tag 的命令参考:[《Git 标签》](http://10.8.10.215/README.html#git )
## 3. 基础框架 tag
### 3. 基础框架 tag
基础框架 tag 不与业务挂钩,根据自身的功能开发按需发布版本,在根目录下 `CURRENT` 文件中记录的当前版本号和历史版本号,及其对应新增了哪些功能,有助于应用选择合适的基础框架版本。
@ -106,25 +455,25 @@ autotest-basic-frame = 0.9.5
用例在执行时会校验两个文件的版本号是否一致,如果不一致,会打印 `error` 日志。
# 三、仓库权限管理
## 三、仓库权限管理
## 1. 基础框架
### 1. 基础框架
- 自动化测试基础框架仓库https://github.com/linuxdeepin/deepin-autotest-framework
## 2. 应用仓库
### 2. 应用仓库
- 自动化应用仓库: `https://gerrit.uniontech.com/admin/repos/autotest_ + app_name`
链接后面的 `app_name` 中间以下划线连接比如音乐https://gerrit.uniontech.com/admin/repos/autotest_deepin_music
# 四、方法编写&调用规范
## 四、方法编写&调用规范
方法编写整体设计思路参考:[《AT应用库设计方案》]
## 1. 方法编写
### 1. 方法编写
写方法的时候注意方法归属,比如文件管理器的界面区域划分为:`TitleWidget` 、`RightViewWidget`、`LeftViewWidget` 、`PopWidget`,方法是在哪个区域操作的,就写在哪个类里面。
@ -169,7 +518,7 @@ class TitleWidget(BaseWidget):
by_img
```
## 2. 方法调用
### 2. 方法调用
- 在用例中调用方法,通过该应用唯一的出口进行调用,比如文管:
@ -180,320 +529,33 @@ class TitleWidget(BaseWidget):
**不要在用例中单独去调用** `TitleWidget` 、`RightViewWidget`、`LeftViewWidget` 、`PopWidget` 这些类,否则后期用例会变得不好维护;
# 五、用例编写规范
## 1. 基于类写用例
所有用例都应该基于类去写:
```python
class TestMusic(BaseCase):
"""音乐用例"""
def test_music_679537(self):
"""音乐启动"""
```
注意以下几点:
- 类名不要随便取,同一个应用应该使用同一个类名,用例类名称必须以 Test 开头,遵循大驼峰命名规范;
- 用例类继承 `BaseCase`,一个应用只有一个 `BaseCase`
- 一个 `py` 文件里面只有一个类,我们称为一个测试类;
- 一个类里面可以有多个用例函数,这取决这条用例有多少个测试点:
```python
# test_music_679537.py
class TestMusic(BaseCase):
"""音乐用例"""
def test_music_679537_1(self):
"""任务栏启动音乐"""
def test_music_679537_2(self):
"""启动器启动音乐"""
def test_music_679537_3(self):
"""桌面启动音乐"""
```
## 2. 用例函数规范
- 用例函数以 test 开头,遵循蛇形命名规范,中间为用例的模块名称,后面加用例 ID最后加测试点序号`test_${module}_${case_id}[_${index}]`
比如:`test_music_679537_1`index 从 1 开始。
- 函数功能说明里面写用例标题,直接复制 PMS 上用例标题即可,注意用三对双引号,不要用其他注释,更不要用井号注释写用例标题;
- 直接复制 `PMS` 用例
用例步骤直接将 `PMS` 上用例步骤和预期复制进来,然后进行批量注释(`ctrl + /`),在注释的基础上去写用例脚本会更加方便全面,也比你自己写注释更节约时间:
举例,`PMS` 用例:
![](https://pic.imgdb.cn/item/64f054c8661c6c8e54ff4c71.png)
直接选中用例内容,复制下来,然后粘贴到自动化用例脚本中:
```python
# test_music_679537.py
class TestMusic(BaseCase):
"""音乐用例"""
def test_music_679537(self):
"""演唱者-平铺视图下进入演唱者详情页"""
# 1
# 点击右上角的【平铺视图】按钮
# 切换为平铺视图
# 2
# 双击任意演唱者封面
# 进入演唱者详情页面
```
上例中井号注释部分就是直接从 `PMS` 上复制过来的,在此基础上写用例:
```python
# test_music_679537.py
class TestMusic(BaseCase):
"""音乐用例"""
def test_music_679537(self):
"""演唱者-平铺视图下进入演唱者详情页"""
music = DeepinMusicWidget()
music.click_singer_btn_in_music_by_ui()
# 1
# 点击右上角的【平铺视图】按钮
music.click_icon_mode_in_music_by_ui()
# 切换为平铺视图
# 2
# 双击任意演唱者封面
music.double_click_first_singer_in_singer_icon_view_by_ui()
# 进入演唱者详情页面
self.assert_xxx
```
你看,非常清楚每一步在做什么,重点是省去了写注释的时间,真的炒鸡方便。
## 其他规范
- 不写 `if __name__ == '__main__':`,不写多余的代码;
## 3. 数据驱动
- 如果用例操作步骤是相同的,只是一些参数变化,尽量使用数据驱动来实现用例;
- 如果你需要使用外部文件存放数据驱动的数据,不能因此引入依赖,尽量使用一些标准库能读取的文件格式,比如 `json、ini、CSV、xml、txt` 等文件格式;不建议使用 `Yaml、Excel、MySQL` 等数据格式;
- 读取数据时也尽量使用标准库去做,如使用 `pandas` 处理 `CSV` 就属于大材小用了,正常的数据驱动还没到需要大数据分析来处理的地步;
- 数据驱动的外部文件存放在 `widget/ddt/` 目录下;
- 数据驱动的写法:
- 统一文件注释头。
```python
@pytest.mark.parametrize("value", data)
def test_smb_049(self, value):
#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
"""
:Author:email@uniontech.com
:Date :${DATE} ${TIME}
"""
```
以上这种参数化的写法本身没什么问题,但是,这里必须要补充一个没有用的小知识:
- 如果参数化数据里面的字符会原封不动的输出到 `item.name` 里面,显示非常不优雅,而且可以会引入一些意想不到的问题,可以感受一下:
参数:
```python
data = [
"一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三", "qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyui", "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678",
]
```
现象是这样色儿的:
```shell
test_smb_049.py::TestFileManager::test_smb_049[一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十一二三]
test_smb_049.py::TestFileManager::test_smb_049[qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyui]
test_smb_049.py::TestFileManager::test_smb_049[12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678]
```
说实话,看着心里堵得慌,如果这里面包含一些**特殊字符**或者是**超长**,可能会有一些很奇妙的事情发生。
- parametrize 里面有个参数ids可以解决此类问题就像这样
```python
@pytest.mark.parametrize("value", data, ids=[1, 2, 3])
def test_smb_049(self, value):
```
再来感受一下:
```shell
test_smb_049.py::TestFileManager::test_smb_049[1]
test_smb_049.py::TestFileManager::test_smb_049[2]
test_smb_049.py::TestFileManager::test_smb_049[3]
```
明显好多了,所以尽量使用 ids 这个参数。
- 不建议使用 `fixture` 的数据驱动方式,框架虽然支持,但可读性比较差;如果你不知道这句话在说啥,那你可以忽略,我也不打算详细说这种实现方式,操作比较骚。
## 4. 断言资源
- 用例断言的图片资源,直接放在用例模块的同级目录下的 `assert_res` 目录下,图片名称以用例的模块名称 + 用例 ID 命名;
- 图像识别断言,不要截取一张很大的图,图片资源包含的元素太多了,非常容易受到需求影响,建议是进行局部的断言;
- 图像识别的默认匹配度是 0.9,如果断言的场景对于精确度要求没那么高,可以在断言语句里面传入小于 0.9 的参数。
## 5. 元素定位
- 用于用例操作步骤中进行元素定位的图片资源,放到 `widget/pic_res` 目录下,图片名称命名为该元素的名称;
- 用于元素定位的图片截取时尽量精确到这个具体的按钮,图片也不要太大;
- 基于 UI 定位的操作较快,合理加入等待时间能提高用例的稳定性。
## 6. 用例资源
- 用例执行过程中需要使用到的一些资源,比如音乐可能需要一些 mp3 的资源用于测试,存放在 `widget/case_res` 目录下,前提是这些资源不超过 `10M`
- 如果是一些比较大的资源,建议放到统一的 ftp 服务器 ftp://10.8.10.245),需要执行用例的时候再下载下来;
```python
f"wget ftp://{Config.FTP_IP}/uploads/多媒体/影院/auto/{file_name};unzip {file_name};rm -rf {file_name};"
```
- 测试资源下载到应用 `widget/case_res` 目录下;
- 禁止将测试资源存放在个人的机器上;
- 应该尽量保证,一个资源在一次用例执行中只需要下载一次,所以你不能每次使用的时候都去下载,这样可能会耗费大量的网络资源,而因为先判断本地是否存在此资源,如果不存在再去下载;
- 测试用例执行过程中,你可能需要将资源拷贝到对应的测试目录下,比如将 mp3 文件拷贝到 `~/Music` 目录下,但是我们更建议你使用发送快捷链接的方式替代拷贝的操作,因为在拷贝大文件时是很消耗系统资源的,而创建链接则不会;
```python
@classmethod
def recovery_many_movies_in_movie_by_cmd(cls):
"""恢复多个视频文件至视频目录中"""
work_path = f"/home/{Config.USERNAME}/Videos/auto"
code_path = f"{Config.CASE_RES_PATH}/auto"
cls.run_cmd(f"rm -rf {work_path};mkdir {work_path}")
sleep(1)
flag = False
if not exists(code_path):
cls.run_cmd(f"mkdir -p {code_path}")
flag = True
logger.info(f"ln -s {code_path}/* {work_path}/")
cls.run_cmd(
f"cd {code_path}/;"
f"{cls.wget_file('auto.zip') if flag else ''}"
f"ln -s {code_path}/* {work_path}/ > /dev/null 2>&1"
)
```
- 资源下载过程中注意超时的问题,如果你的测试资源很大,要特别注意这问题,如果你使用强制等待下载结束( `os.system` ),可能会造成用例执行时长变得不可接受,目前我们发现在持续集成环境执行时网络下载速度很慢,所以超时机制是很有必要的;`run_cmd` 方法有一个默认超时的时间,你可以根据资源大小对超时时间进行调整;
# 六、用例调试技巧
## 1. 日志
- 一定要先看报错,看 error 日志,通常能明确的告诉你具体哪里代码报错;
- 结合报错点前面的error、 info 和 debug 日志看否是正常。
## 2. 断点调试
- 方法报错:
- 在方法库中找到对应的方法,单独调用,看是否能正常执行;
- 通常单个方法的执行是比较简单的,如果单个方法报错,很快就能排查出问题;在方法内部打断点,使用 `Debug` 运行,看方法内部数据传递是否存在问题;
- 如果单个方法调用没问题,那么在用例中报错的方法前面打断点,使用 `Debug` 运行,看用例的业务逻辑和数据传递是否存在问题;
- 断言报错:
- 断言为数据断言,根据表达式进行断言语句进行修改;
- 文件生成类断言,查看是否需要加等待时间;
- 图像断言,在断言语句处打断点,使用 `Debug` 运行,用例运行到断言处会停止,查看此时断言的图片与用例执行的现场存在什么差异,此时也可以进行重新截图,从而替换新的图片;
## 3. 远程执行
- 远程执行指的是编辑器通过指定远程解释器执行自动化代码;
远程执行的好处是可以很方便的 `Debug` 运行,不用在测试机上打开编辑器,用例执行速度更快;
支持远程执行功能的编辑器:
- 专业版 `Pycharm`
- `VScode` ,需要使用插件 `Remote-SSH`
- 日志打印要在方法最前面,否则代码报错没有日志输出,不好定位问题。(我们会考虑继续使用拦截器打印日志)
- 远程执行配置
`Pycharm` 为例:
`File` —> `Settings` —> `Project` —> `Python Interpreter` —> `右边设置按钮` —> `Add...` —> `SSH Interpreter` —> `New server configuration(填入host和username)` —> `Next` —> `password测试机密码` —> `Interpreter(选择远程解释器)` —> `Finish`
## 4. 环境清理
- 如果用例里面的 `teardown` 没有执行,大概率是因为 `setup` 里面代码报错,这两个是一对的,`setup` 里面报错,`teardown` 里面的代码不会执行;
- 目前我们已经将各应用的 `clean_all` 这个 `fixture` 改成了终结器,确保始终能执行到这步,但是用例里面的 `fixture`,还是需要我们小心处理;
- 要执行 `clean_all` 需要在编辑器运行参数加 `--clean yes`,写用例的时候请加上,不然你不确定用例执行之后的环境是否恢复;
- `setup` 可以不要,将 `setup` 放到用例里面是一种稳妥的做法,`teardown` 一定要。
## 5. 元素定位不准(坐标返回不对)
- 基于 `UI` 定位的方法,可能受到窗口 ID 的变化,导致坐标返回不准,默认取最新的一个窗口用于定位,但如果实际需要定位的不是最新的窗口,那么在用例中需要重新实例化方法类对象,并在类中传入对应的窗口序号;
- 基于属性定位的方法,目前遇到的笔记本上,由于屏幕缩放比例为 1.25,导致坐标返回不准,我们默认使用缩放比例为 1
- 基于图像定位的方法,如果当前屏幕中存在多个相同的目标元素,可能出现定位不准;支持通过参数控制,返回多个坐标;
- 基于 OCR 定位的方法,如果当前屏幕中存在多个相同的文字元素,可能出现定位不准;同样支持通过参数控制,返回多个坐标;
## 6. 键鼠操作不准
- 鼠标操作不生效,比如右键、双击无响应;
- 键盘操作不生效,或者延迟输入,比如用例需要输入“我是中国人”,实际只输入了“我国人”;
以上问题排除应用卡顿等问题,大概率是由于工具的问题,目前键鼠操作我们使用三个工具:`Dogtail` 提供的键鼠工具、`PyAutoGUI`、`Xdotool`
有同学可能要说为啥要用三个啊,用一个不就好了,简单讲就是各有优点各有缺点。
如果你遇到键鼠的问题,可以试试通过不同的工具操作;键盘输入延迟的问题,一般是因为输入速度太快了,系统没反应过来,常见于 `ARM``MIPS` 上,修改参数 delay_time 的值,单位为毫秒;
```python
# mouse_key.py
@classmethod
def input_message(cls, message, delay_time=300, interval=0.2):
"""
输入字符串
:param message: 输入的内容
:param delay_time: 延迟时间
:param interval:
:return:
"""
```
如果不是方法的问题,则需要继续和开发一起排除,是否为应用接受键鼠信号处理的问题,这类情况我们也是遇到过的,具体问题具体分析。
比如影院就重写了一个右键的方法:
```python
# base_widget.py
@classmethod
def right_click(cls, _x=None, _y=None):
"""
重写底层单击鼠标右键
解决影院右键触发release事件的问题右键主窗口会播放视频
"""
cls.mouse_down(_x, _y, button=3)
sleep(0.1)
cls.mouse_up(button=3)
```
- 所有的操作都需要有日志,包括 `sleep()`,我们会重写一个有日志输入的 `sleep`
- 业务层日志级别为 `INFO`
- hook 函数只能写到根目录下的 `conftest.py` 里面。
- `apps` 目录下的 `conftest.py` 原则上不会新增 `fixture`
- 固定目录或元素控件的操作,将操作方法写死,文件类操作将文件名留参数。
- 路径拼接规范:
- 系统中固定目录,路径拼接时使用波浪符号,比如:`~/Desktop/`,下层使用 `os.path.expanduser()`,它可以自动识别波浪符号。
- 项目下路径使用配置文件中的路径,比如:`Config.BASE_PATH`,因为项目是可以在任意路径运行的,需要动态拼接路径。
# 七、其他不为人知的细节
- 在一段时间内尽量编写同一个应用或模块的用例,能对该用例已有方法熟悉,避免过多重复业务代码的封装;
- 相同的场景下,各架构等待时间不同,建议使用框架提供的 sleep我们做了不同架构的倍数放大
- 编写用例时,尽量考虑到每一步异常后的环境恢复,需要建议这种意识,随时要考虑到,这步操作有没有可能出错,出错了改怎么办;
- 提交代码的时候注意不要把一些临时的测试资源提交进去了,比如测试了一个影片,有些同学习惯使用 `git add .` ,然后就全部提交到代码仓库了,这样即使后期把大文件删了,`.git` 文件里面也会很大,造成代码仓库变得十分臃肿。
更多细节查看《AT经验总结》

View File

@ -2,6 +2,105 @@
> 欢迎所有人提交你在自动化测试方面的优秀实践经验,以帮助大家解决可能遇到的问题。
# 六、用例调试技巧
## 1. 日志
- 一定要先看报错,看 error 日志,通常能明确的告诉你具体哪里代码报错;
- 结合报错点前面的error、 info 和 debug 日志看否是正常。
## 2. 断点调试
- 方法报错:
- 在方法库中找到对应的方法,单独调用,看是否能正常执行;
- 通常单个方法的执行是比较简单的,如果单个方法报错,很快就能排查出问题;在方法内部打断点,使用 `Debug` 运行,看方法内部数据传递是否存在问题;
- 如果单个方法调用没问题,那么在用例中报错的方法前面打断点,使用 `Debug` 运行,看用例的业务逻辑和数据传递是否存在问题;
- 断言报错:
- 断言为数据断言,根据表达式进行断言语句进行修改;
- 文件生成类断言,查看是否需要加等待时间;
- 图像断言,在断言语句处打断点,使用 `Debug` 运行,用例运行到断言处会停止,查看此时断言的图片与用例执行的现场存在什么差异,此时也可以进行重新截图,从而替换新的图片;
## 3. 远程执行
- 远程执行指的是编辑器通过指定远程解释器执行自动化代码;
远程执行的好处是可以很方便的 `Debug` 运行,不用在测试机上打开编辑器,用例执行速度更快;
支持远程执行功能的编辑器:
- 专业版 `Pycharm`
- `VScode` ,需要使用插件 `Remote-SSH`
- 远程执行配置
`Pycharm` 为例:
`File` —> `Settings` —> `Project` —> `Python Interpreter` —> `右边设置按钮` —> `Add...` —> `SSH Interpreter` —> `New server configuration(填入host和username)` —> `Next` —> `password测试机密码` —> `Interpreter(选择远程解释器)` —> `Finish`
## 4. 环境清理
- 如果用例里面的 `teardown` 没有执行,大概率是因为 `setup` 里面代码报错,这两个是一对的,`setup` 里面报错,`teardown` 里面的代码不会执行;
- 目前我们已经将各应用的 `clean_all` 这个 `fixture` 改成了终结器,确保始终能执行到这步,但是用例里面的 `fixture`,还是需要我们小心处理;
- 要执行 `clean_all` 需要在编辑器运行参数加 `--clean yes`,写用例的时候请加上,不然你不确定用例执行之后的环境是否恢复;
- `setup` 可以不要,将 `setup` 放到用例里面是一种稳妥的做法,`teardown` 一定要。
## 5. 元素定位不准(坐标返回不对)
- 基于 `UI` 定位的方法,可能受到窗口 ID 的变化,导致坐标返回不准,默认取最新的一个窗口用于定位,但如果实际需要定位的不是最新的窗口,那么在用例中需要重新实例化方法类对象,并在类中传入对应的窗口序号;
- 基于属性定位的方法,目前遇到的笔记本上,由于屏幕缩放比例为 1.25,导致坐标返回不准,我们默认使用缩放比例为 1
- 基于图像定位的方法,如果当前屏幕中存在多个相同的目标元素,可能出现定位不准;支持通过参数控制,返回多个坐标;
- 基于 OCR 定位的方法,如果当前屏幕中存在多个相同的文字元素,可能出现定位不准;同样支持通过参数控制,返回多个坐标;
## 6. 键鼠操作不准
- 鼠标操作不生效,比如右键、双击无响应;
- 键盘操作不生效,或者延迟输入,比如用例需要输入“我是中国人”,实际只输入了“我国人”;
以上问题排除应用卡顿等问题,大概率是由于工具的问题,目前键鼠操作我们使用三个工具:`Dogtail` 提供的键鼠工具、`PyAutoGUI`、`Xdotool`
有同学可能要说为啥要用三个啊,用一个不就好了,简单讲就是各有优点各有缺点。
如果你遇到键鼠的问题,可以试试通过不同的工具操作;键盘输入延迟的问题,一般是因为输入速度太快了,系统没反应过来,常见于 `ARM``MIPS` 上,修改参数 delay_time 的值,单位为毫秒;
```python
# mouse_key.py
@classmethod
def input_message(cls, message, delay_time=300, interval=0.2):
"""
输入字符串
:param message: 输入的内容
:param delay_time: 延迟时间
:param interval:
:return:
"""
```
如果不是方法的问题,则需要继续和开发一起排除,是否为应用接受键鼠信号处理的问题,这类情况我们也是遇到过的,具体问题具体分析。
比如影院就重写了一个右键的方法:
```python
# base_widget.py
@classmethod
def right_click(cls, _x=None, _y=None):
"""
重写底层单击鼠标右键
解决影院右键触发release事件的问题右键主窗口会播放视频
"""
cls.mouse_down(_x, _y, button=3)
sleep(0.1)
cls.mouse_up(button=3)
```
# 七、其他不为人知的细节
- 在一段时间内尽量编写同一个应用或模块的用例,能对该用例已有方法熟悉,避免过多重复业务代码的封装;
- 相同的场景下,各架构等待时间不同,建议使用框架提供的 sleep我们做了不同架构的倍数放大
- 编写用例时,尽量考虑到每一步异常后的环境恢复,需要建议这种意识,随时要考虑到,这步操作有没有可能出错,出错了改怎么办;
- 提交代码的时候注意不要把一些临时的测试资源提交进去了,比如测试了一个影片,有些同学习惯使用 `git add .` ,然后就全部提交到代码仓库了,这样即使后期把大文件删了,`.git` 文件里面也会很大,造成代码仓库变得十分臃肿。
# 1、终结器
```shell

View File

@ -1,25 +1,28 @@
# 自动化测试架构设计规划
- 自动化测试架构设计规划
- 分类:/
- 架构师:/
- 目标:
- 应用 AT 架构工程化,参考性能自动化工程完成工程化改造。
- 应用间用例解耦,解除所有交叉调用的方法,各应用能跟随自身迭代周期独立维护 AT 用例。
- 完成公共方法的抽取整合,形成一套独立于应用间的公共方法库,各应用方法里面不存在被多个应用调用的情况。
- 用例实现标签化管理,为将来适配更多的 AT 运用场景提供支撑,原则上可实现无限扩展,在每日构建和持续集成流程落地使用。
- 代码规范问题清零,符合 `Shell Check`、`Pylint`、系统部相关编码规范要求。
- 应用 AT 架构工程化,参考性能自动化工程完成工程化改造。
- 应用间用例解耦,解除所有交叉调用的方法,各应用能跟随自身迭代周期独立维护 AT 用例。
- 完成公共方法的抽取整合,形成一套独立于应用间的公共方法库,各应用方法里面不存在被多个应用调用的情况。
- 用例实现标签化管理,为将来适配更多的 AT 运用场景提供支撑,原则上可实现无限扩展,在每日构建和持续集成流程落地使用。
- 代码规范问题清零,符合 `Shell Check`、`Pylint`、系统部相关编码规范要求。
- 意义:
- 统一成研 AT 架构设计思路,消除 AT 代码实现和维护上可能出现的分歧,改善历史 AT 无工程化设计的缺陷,提高架构专业性。
- 各应用 AT 代码相互独立,契合应用独立发布特性,可支持迭代期间独立新增、维护和执行。
- 方法调用逻辑得到简化编写和维护更高效公共库抽取减少方法重复编写提高代码利用率平均编写一条用例从40分钟降低至30分钟日产出用例从12条/天提升至16条/天。
- 可灵活支撑不同的自动化运用场景,如 CI、冒烟测试、集成测试、回归测试、专项测试等。
- 统一成研 AT 架构设计思路,消除 AT 代码实现和维护上可能出现的分歧,改善历史 AT 无工程化设计的缺陷,提高架构专业性。
- 各应用 AT 代码相互独立,契合应用独立发布特性,可支持迭代期间独立新增、维护和执行。
- 方法调用逻辑得到简化,编写和维护更高效;公共库抽取,减少方法重复编写,提高代码利用率;平均编写一条用例从 40 分钟降低至 30 分钟,日产出用例从 12 条/天提升至 16 条/天。
- 可灵活支撑不同的自动化运用场景,如 CI、冒烟测试、集成测试、回归测试、专项测试等。
## 一、背景介绍
!!! note "提示"
这里介绍以前的AT框架情况以及存在的一些问题
### 1、原有架构介绍
![](https://pic.imgdb.cn/item/64f054c5661c6c8e54ff498d.png)
??? note "原AT框架架构图"
![](https://pic.imgdb.cn/item/64f054c5661c6c8e54ff498d.png)
原有自动化测试架构整体分为三层:用例层(业务逻辑层)、中间层(元素定位和操作方法层)、核心层(底层功能库层)。
@ -29,33 +32,35 @@
### 2、自动化的应用
**2.1、CI 流程**
- CI 流程
- 每日构建流水线是对研发每日提交的代码进行测试AT 的大致流程:每日下班之后会将各应用进行打包,然后在测试机上安装更新 deb 包,最后进行自动化测试。
- 每日构建流水线是对研发每日提交的代码进行测试AT 的大致流程:每日下班之后会将各应用进行打包,然后在测试机上安装更新 deb 包,最后进行自动化测试。
- 持续集成流水线是对应用提交集成的版本进行测试AT 的大致流程:每日下载最新的 ISO 进行 `PXE` 部署,然后测试机安装最新的镜像,最后进行自动化测试。
- 持续集成流水线是对应用提交集成的版本进行测试AT 的大致流程:每日下载最新的 ISO 进行 `PXE` 部署,然后测试机安装最新的镜像,最后进行自动化测试。
**2.2、验收测试**
- 验收测试
在各验收节点进行自动化验收,目前的策略是全用例覆盖全架构。
- 在各验收节点进行自动化验收,目前的策略是全用例覆盖全架构。
**2.3、回归测试**
- 回归测试
回归测试今年规划建设中,旨在回归测试时执行自动化测试用例,减少功能测试的重复劳动力。
- 回归测试今年规划建设中,旨在回归测试时执行自动化测试用例,减少功能测试的重复劳动力。
### 3、存在的问题
- 各应用之间存在耦合
在自动化测试项目初期,所有应用是整体发布,我们是将所有应用看成是一个整体,各应用作为其中的一个模块,所以存在应用间方法交叉调用的问题,这样从最初的设计来讲确实能够减少重复代码的编写。但是现在应用走独立发布,各应用都有自己的迭代节奏,在新需求快速变化的过程中,自动化维护变得异常困难,原因就是自动化项目里面各个应用的有比较多的耦合关系,因此我们需要进行解耦,以适应应用不同的迭代周期。
在自动化测试项目初期,所有应用是整体发布,我们是将所有应用看成是一个整体,各应用作为其中的一个模块,所以存在应用间方法交叉调用的问题,这样从最初的设计来讲确实能够减少重复代码的编写。
但是,现在应用走独立发布,各应用都有自己的迭代节奏,在新需求快速变化的过程中,自动化维护变得异常困难,原因就是自动化项目里面各个应用的有比较多的耦合关系,因此我们需要进行解耦,以适应应用不同的迭代周期。
- 无法精准的划分用例范围
用例执行的范围不够精准,目前自动化用例执行时,主要通过用例的关键词 core核心来区分用例是否为核心用例但是这样的区分太宽泛了不能适应自动化测试在多场景下的应用。很多场景下我们还需要根据用例的等级、用例的类型、用例来源等等不同的维度来挑选要执行的用例。在用例脚本中添加关键字需要人工一条条的改费时费力而且不好维护。
用例执行的范围不够精准,目前自动化用例执行时,主要通过用例的关键词 core核心来区分用例是否为核心用例但是这样的区分太宽泛了不能适应自动化测试在多场景下的应用。很多场景下我们还需要根据用例的等级、用例的类型、用例来源等等不同的维度来挑选要执行的用例。在用例脚本中添加关键字需要人工一条条的改费时费力而且不好维护。
- 受新需求影响跳过的用例不好维护
目前需要跳过的用例都需要在对应的用例脚本里面,添加跳过用例的代码,后续解除跳过的时候又需要找到这条脚本,删掉跳过用例的代码。在跳过用例较多的情况下,维护起来有难度。
目前需要跳过的用例都需要在对应的用例脚本里面,添加跳过用例的代码,后续解除跳过的时候又需要找到这条脚本,删掉跳过用例的代码。在跳过用例较多的情况下,维护起来有难度。
- 编写用例时逻辑比较复杂,需要调用多个应用的方法模块。
@ -68,99 +73,112 @@
### 1、架构设计
![](https://pic.imgdb.cn/item/64f054c4661c6c8e54ff4948.png)
???+ note "现AT框架架构图"
![](https://pic.imgdb.cn/item/64f054c4661c6c8e54ff4948.png)
### 2、设计思路
### 2、==设计思路==
框架的运行逻辑:通过核心层提供一个基础能力,业务层根据实际业务需求(测试用例)动态加载核心层,执行入口加载相应的用例集并控制执行,应用层根据实际测试需求,通过相应的配置项进行配置,从而触发自动化测试任务。
框架的运行逻辑:
- **核心层**:基本保持不变,部分模块会涉及到新功能开发,核心层各功能模块保持独立性,提供通用的接口能力,供上层调用;
通过核心层提供一个基础能力,业务层根据实际业务需求(测试用例)动态加载核心层,执行入口加载相应的用例集并控制执行,应用层根据实际测试需求,通过相应的配置项进行配置,从而触发自动化测试任务。
底层核心模块包括:
- ==核心层==
基本保持不变,部分模块会涉及到新功能开发,核心层各功能模块保持独立性,提供通用的接口能力,供上层调用;底层核心模块包括:
- 图像识别模块
- `UI` 定位模块
- `Dbus` 接口操作模块
- 属性定位模块
- 日志模块
- 键鼠操作模块
- 文件操作模块
- 录屏模块
- 自定义断言模块
- 用例执行模块
- `PXE` 装机模块
- 键鼠信号模拟模块
- `OCR` 模块
- ==业务层==
以应用为维度划分,应用内包含多个测试类型,如功能测试、性能测试、漏洞扫描等,后续可以根据需要嫁接进来。其中功能测试设计思路:
- 以应用为维度划分,并将测试数据和测试资源整合进来,增加用例标签 `csv` 文件,用于给每条用例打标签。
各标签所使用对应的字段名称,使用 `csv` 文件维护用例与标签的对应关系,对用例实现标签化管理,可以组合其中的标签而从驱动对应的自动化用例执行,兼容现有用例标签,且支持用例标签可扩展;
!!! note "为什么使用csv格式"
使用 `csv` 格式文件可以方便的使用 Excel 表格打开进行编辑,同时由于 `csv` 文件实际是以都好分隔的文本文件,代码中可以在不依赖三方库的情况下方便快速的解析它,可操作性和可维护性较高。
- 各个应用之间,用例、方法、标签和资源都是相互独立的,编写和维护用例时只需要自己应用下的方法和公共库即可。
- 结构举例:
```shell
.
├── apps
│   ├── deepin_album # 应用名 (用下划线连接是 Python 编码规范)
│   │   ├── album_assert # 断言库
│   │   ├── album_function_tag.csv # 用例标签
│   │   ├── asan_cases # 漏洞扫描用例
│   │   ├── function_cases # 功能测试用例
│   │   ├── res # 测试资源
│   │   ├── config # 应用内局部配置模块
│   │   └── widget # 方法库
│   │   ├── album_widget # 应用自己的方法库
│   │   ├── base_widget # 方法基类
│   │   └── other_widget # 调用其他应用的方法库
│   ├── deepin_camera
│   │   ├── function_cases
│   │  ...
│   └── public_widget # 公共方法库
├── globalconfig # 全局配置模块
...
```
- 图像识别模块
- `UI` 定位模块
- `Dbus` 接口操作模块
- 属性定位模块
- 日志模块
- 键鼠操作模块
- 文件操作模块
- 录屏模块
- 自定义断言模块
- 用例执行模块
- `PXE` 装机模块
- 键鼠信号模拟模块
- `OCR` 模块
- ==globalconfig 配置模块==
- **业务层**:以应用为维度划分,应用内包含多个测试类型,如功能测试、性能测试、漏洞扫描等,后续可以根据需要嫁接进来。其中功能测试设计思路:
- 以应用为维度划分,并将测试数据和测试资源整合进来,增加用例标签 `csv` 文件,用于给每条用例打标签。
各标签所使用对应的字段名称,使用 `csv` 文件维护用例与标签的对应关系,对用例实现标签化管理,可以组合其中的标签而从驱动对应的自动化用例执行,兼容现有用例标签,且支持用例标签可扩展;
> 使用 `csv` 格式文件可以方便的使用 Excel 表格打开进行编辑,同时由于 `csv` 文件实际是以都好分隔的文本文件,代码中可以在不依赖三方库的情况下方便快速的解析它,可操作性和可维护性较高。
- 各个应用之间,用例、方法、标签和资源都是相互独立的,编写和维护用例时只需要自己应用下的方法和公共库即可。
- 结构举例:
```shell
.
├── apps
│   ├── deepin_album # 应用名 (用下划线连接是 Python 编码规范)
│   │   ├── album_assert # 断言库
│   │   ├── album_function_tag.csv # 用例标签
│   │   ├── asan_cases # 漏洞扫描用例
│   │   ├── function_cases # 功能测试用例
│   │   ├── res # 测试资源
│   │   ├── config # 应用内局部配置模块
│   │   └── widget # 方法库
│   │   ├── album_widget # 应用自己的方法库
│   │   ├── base_widget # 方法基类
│   │   └── other_widget # 调用其他应用的方法库
│   ├── deepin_camera
│   │   ├── function_cases
│   │  ...
│   └── public_widget # 公共方法库
├── config # 全局配置模块
...
```
可以根据需要进行相应配置,测试同学可以根据自己的测试计划,在 `globalconfig` 里面进行配置。
全局配置项:
- **`config` 配置**模块可以根据需要进行相应配置,测试同学可以根据自己的测试计划,在 `config` 里面进行配置。
全局配置:
- 执行一个或多个应用的用例:在 `pattern` 里面写入应用包名,多个应用之间用 `or` 连接,如 `deepin-music or deepin-movie`
- 冒烟测试:在 `tags` 里面配置为 `smoke`
- 集成测试:在 `tags` 里面配置为 `core`
- 全量测试:在 `tags` 里面为空即可;
> 通过 `tags` 的配置比较灵活,后面标签化管理章节会讲到,支持标签的逻辑组合,可以根据需要进行灵活配置。
!!! note ""
通过 `tags` 的配置比较灵活,后面标签化管理章节会讲到,支持标签的逻辑组合,可以根据需要进行灵活配置。
- 指定某台机器在指定镜像版本上执行用例:在 `IP` 里面配置测试机 `IP`,并在 `URL` 里面填入镜像的下载地址,框架会调用 `PXE` 进行自动装机,装机完之后自动开始执行配置的测试用例。
应用内局部配置:
==应用内局部配置==
每个应用内部会有一个单独的配置模块,会包含一些本应用的测试资源的路径、执行用例的标签配置等等,如果在局部配置里面配置了用例执行标签,而外层 `runner` 出没有指定执行标签,则在执行时只会执行局部配置已配置的,若外层 `runner` 也配置了执行标签,则会按照全局配置执行用例。
每个应用内部会有一个单独的配置模块,会包含一些本应用的测试资源的路径、执行用例的标签配置等等,如果在局部配置里面配置了用例执行标签,而外层执行器没有指定执行标签,则在执行时只会执行局部配置已配置的,若外层执行器也配置了执行标签,则会按照全局配置执行用例。
不同测试类型的配置都在同一个配置文件里面,`py` 文件里面分不同的类,`ini` 文件里面分不同的 `option`
全局配置和局部配置的策略如下:
- 全局配置了执行的用例标签,局部配置未配置,则按照全局配置执行。
- 全局配置未配置,局部配置了执行的用例标签,则按照局部配置执行。
- 全局配置了执行的用例标签,局部配置了执行的用例标签,则按照全局配置执行。
- 全局配置未配置,局部配置了执行的用例标签,则按照局部配置执行。
- 全局配置了执行的用例标签,局部配置了执行的用例标签,则按照全局配置执行。
- **应用层**`runner` 是测试执行的入口,它会根据配置里面的配置项,进行用例的加载和执行。它提供接口给自动化测试平台,平台上的指令实际上都是通过下发给 `runner`,然后由 `runner` 来执行相应的测试。
- ==应用层==
自动化测试平台是一个前端系统,可以进行测试机管理、自动安装镜像、自动安装指定应用版本、进行测试用例范围选择、用例触发执行控制、结果展示输出等
`runner` 是测试执行的入口,它会根据配置里面的配置项,进行用例的加载和执行。它提供接口给自动化测试平台,平台上的指令实际上都是通过下发给执行器,然后由执行器来执行相应的测试。
一个应用的功能测试、性能测试、漏洞扫描用例都可以单独触发。
- ==自动化测试平台==
- **兼容性测试**主要通过 `PXE` 服务器对测试机进行装机,然后配合 AT 进行测试,可以实现不同系统版本、不同应用版本环境上都可以进行自动化的 AT 执行,提高兼容性测试效率。
是一个前端系统,可以进行测试机管理、自动安装镜像、自动安装指定应用版本、进行测试用例范围选择、用例触发执行控制、结果展示输出等。
- 一个应用的功能测试、性能测试、漏洞扫描用例都可以单独触发。
- 兼容性测试主要通过 `PXE` 服务器对测试机进行装机,然后配合 AT 进行测试,可以实现不同系统版本、不同应用版本环境上都可以进行自动化的 AT 执行,提高兼容性测试效率。
## 三、详细方案
@ -186,11 +204,11 @@ class MusicWidget:
### 2、公共库建设
2.1、如果某个方法被 2 个及以上的外部应用调用,则在 ` public_widget` 目录下新建一个 `widget` 的 Python 文件,在文件中写一个 `Widget` 类,在类中重写此方法;`public_widget` 即为公共方法库;
2.1、如果某个方法被 2 个及以上的外部应用调用,则在 ` public` 目录下新建一个 `widget` 的 Python 文件,在文件中写一个 `Widget` 类,在类中重写此方法;`public` 即为公共方法库;
2.2、在用例层修改用例中类的导入路径,属于公共方法的则调用 `public_widget` 中的类,外部应用的操作方法,则调用本应用目录下重写的外部应用方法。
2.2、在用例层修改用例中类的导入路径,属于公共方法的则调用 `public` 中的类,外部应用的操作方法,则调用本应用目录下重写的外部应用方法。
比如:几乎所有多媒体应用都需要通过文管加载资源,调起的文管对话框实际为 `dde-desktop`,因此将 `desktop_widget.py` 放到 `public_widget` 里面:
比如:几乎所有多媒体应用都需要通过文管加载资源,调起的文管对话框实际为 `dde-desktop`,因此将 `dde_desktop_public_widget.py` 放到 `public` 里面:
```python
class DdeDesktopPublicWidget:
@ -204,23 +222,19 @@ class DdeDesktopPublicWidget:
### 3、用例标签化管理
3.1、根据现有业务需要,用例需要添加的标签有
3.1、根据业务需要,用例可以添加对应的标签,比如
- 用例级别:对应 `PMS` 上用例级别,分别用 `L1、L2、L3、L4` 表示;
- 用例类型:对应 `core`、`smoke`,或为空;
- 用例来源:对应 `PMS` 用例来源,分别用 `acp1、acp2、acp3、acp4` 表示。
- `acp1` :业务线测试同学写的用例,被用于验收测试的;
- `acp2` :验收线测试同学引入的产品验收测评部的用例;
- `acp3` :验收线测试同学根据产品需求设计的验收测试用例;
- `acp4` :验收线测试同学通过社区、论坛用户反馈问题总结设计的验收测试用例。
- 用例来源:对应 `PMS` 用例来源;
举例:
举例:
| 用例ID | 用例级别 | 用例类型 | 用例来源 |
| :----: | :------: | :------: | :------: |
| 001 | `L1` | `core` | `xxx` |
| 用例ID | 用例级别 | 用例类型 | 用例来源 |
| :----: | :------: | :------: | :------: |
| 001 | `L1` | `core` | `acp1` |
**标签采用枚举的方式进行代号管理**方便管理以及后续扩展。
==标签支持扩展;==
3.2、在每个应用目录下新建 `csv` 文件,用于保存用例标签,第一列为用例的 ID从第二列开始及之后的列每一列都是一个用例标签后续需要新增用例标签可以直接在 `csv` 文件里面添加对应的列即可;
@ -242,14 +256,12 @@ class DdeDesktopPublicWidget:
- 如果应用受到新需求影响需要跳过,则在此列备注具体的跳过原因。跳过的原因统一标记为 “`skip-跳过原因`”;
- 用例执行时判断 `csv` 文件里面跳过原因列是否存在跳过标记,如果已经标记了跳过原因,最终的用例状态会被标记为 `SKIPED`,用例也不会被执行。
所有的标签在 docs 目录下会有一个文档,文档暂定名称为:`tags_info.md`,用于说明现在已经使用到的标签,以及这些标签的解释和用法。
### 4、用例执行
4.1、标签化管理的驱动执行逻辑功能实现
- 开发根据用例标签文件里面的用例标签执行对应用例的功能,能支持多个标签的逻辑组合,执行入口能随意通过用例标签指定要执行的用例。
- 用例标签的驱动方式必须能支持标签的扩展,未来随着业务的变化可能需要增加各种各样的标签。
- ==用例标签的驱动方式必须能支持标签的扩展,未来随着业务的变化可能需要增加各种各样的标签。==
4.2、不同测试类型的用例执行
@ -271,9 +283,10 @@ class DdeDesktopPublicWidget:
测试平台可能会涉及到的模块有:测试机资源管理模块、用例执行控制模块、结果展示模块、`PXE` 镜像安装模块等。
> 用户(测试、研发同学等)可以配置自己的测试计划,如执行哪个应用、执行用例的范围、在哪台机器上执行、镜像版本及下载地址、应用版本及下载地址、执行时间。
??? note "想要的预期"
用户(测试、研发同学等)可以配置自己的测试计划,如执行哪个应用、执行用例的范围、在哪台机器上执行、镜像版本及下载地址、应用版本及下载地址、执行时间。
5.2、执行入口 `runner` 提供给测试平台的接口包括:用例执行接口、结果返回接口、镜像下载接口、测试机镜像安装接口、应用下载接口、测试机应用安装更新接口等。
5.2、执行入口提供给测试平台的接口包括:用例执行接口、结果返回接口、镜像下载接口、测试机镜像安装接口、应用下载接口、测试机应用安装更新接口等。
前端平台目前还没有太多详细的方案,本次设计主要集中在后端这部分架构的设计上。