自动化测试架构优化
自动化测试架构优化
简介
自动化框架应具备的功能
- 支持管理用例,运行用例
- 支持查找元素/定位元素,对元素/页面 进行各种操作(点击,滑动,输入等等)
- 支持生成测试报告
- 能够实现功能的复用,(比如登录,搜索等)
- 当页面有异常弹框的时候,可以进行有效的处理
- 当用例失败,需要添加失败时的日志,截图,等信息,放在测试报告中
- 多设备并发
- 支持平台化
- ...
自动化测试框架实现
功能 | 实现(Python) | 实现(Java |
---|---|---|
管理用例,运行用例 | pytest | Junit |
查找元素/定位元素 | Appium | Appium |
测试报告 | Allure | Allure |
功能复用 | PO 实现 | PO 实现 |
异常弹框 | 编写代码 | 编写代码 |
失败时的日志,截图 | 编写代码 | 编写代码 |
使用场景
- 领域模型适配:封装业务实现,实现业务管理。
- 提高效率:降低用例维护成本,提高执行效率。
- 增强功能:解决已有框架不满足的情况。
操作步骤
项目结构
- 框架层:异常处理,数据驱动,日志搜集,报告管理
- 基础层:基础 driver 封装,元素查找封装,操作封装
- 业务层:页面封装
- 用例层:测试用例集
Python 项目结构示例:
Hogwarts $ tree
.
├── __init__.py
├── base
│ ├── __init__.py
│ ├── app.py
│ └── base_page.py
├── cases
│ ├── __init__.py
│ └── test_xxx.py
├── log
│ ├── test.log
├── datas
│ └── xxx.yml
├── page
│ ├── __init__.py
│ ├── main_page.py
│ ├── xxx_page.py
│ └── xxx_page.py
├── conftest.py
└── utils
├── __init__.py
└── log_utils.py
Java 项目结构示例:
Hogwarts $ tree
.
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── app
│ │ │ ├── AppManager.java
│ │ │ └── Config.java
│ │ └── resources
│ │ ├── config
│ │ │ └── config.properties
│ │ └── data
│ │ └── test-data.yml
│ ├── test
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ ├── base
│ │ │ │ └── BasePage.java
│ │ │ ├── page
│ │ │ │ ├── MainPage.java
│ │ │ │ └── XxxPage.java
│ │ │ └── tests
│ │ │ ├── TestXxx.java
│ │ │ └── TestXxx.java
│ │ └── resources
│ │ └── logs
│ │ └── test.log
└── pom.xml
框架层
异常处理
数据驱动
- 支持支持测试用例参数化驱动配置
- 配置方式包括三个部分
- 参数定义(指定名字)
- 数据源指定(指定 yaml 文件 /或者其它格式文件)
-
准备测试数据(无论是从线上环境捞的数据,还是自己创建的测试数据)
-
准备测试数据。
根据需求,可以把数据格式设计为读取出来就是参数化所需要的数据格式,例如列表中嵌套列表的的形式。
# datas.yaml
datas:
- - name1
- phone1
- - name2
- phone2
- - name3
- phone3
- 定义读取 yaml 数据的方法。
Python 示例:
# utils.py
class Utils:
@classmethod
def get_yaml_data(cls, yaml_path):
'''
读取 yaml 文件数据
:param yaml_path: yaml 文件路径
:return: 读取到的数据
'''
with open(yaml_path, encoding="utf-8") as f:
datas = yaml.safe_load(f)
return datas
Java 示例:
public class Utils {
/**
* 读取 YAML 文件数据
*
* @param yamlPath YAML 文件路径
* @return 读取到的数据
*/
public static Map<String, Object> getYamlData(String yamlPath) {
Yaml yaml = new Yaml();
try (InputStream inputStream = Files.newInputStream(Paths.get(yamlPath))) {
return yaml.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
- 在测试用例中读取 yaml 数据,通过参数化的方式完成数据驱动。
Python 示例:
# 测试用例
def get_datas(yaml_path):
'''
读取测试数据
:return: 读取到的 yaml 中的数据,字典格式
'''
yaml_datas = Utils.get_yaml_data(yaml_path)
print(yaml_datas)
# 获取对应的测试数据
datas = yaml_datas.get("datas")
logger.info(f"获取到的数据为 ===> {datas}")
return datas
class TestXX:
@pytest.mark.parametrize(
"name, phonenum", get_datas("datas.yaml")
)
def test_xx(self, name, phonenum):
'''
数据驱动测试添加成员
:return:
'''
Java 示例:
// TestDataProvider.java
import org.junit.jupiter.params.provider.Arguments;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class TestDataProvider {
/**
* 从 YAML 文件中读取测试数据
*
* @return 数据流
*/
private static Stream<Arguments> yamlDataProvider() {
Map<String, Object> yamlData = Utils.getYamlData("src/test/java/PoDemo/utils/data.yml");
assert yamlData != null;
@SuppressWarnings("unchecked")
List<List<String>> datas = (List<List<String>>) yamlData.get("datas");
return datas.stream()
.map(data -> Arguments.of(
data.get(0), // name
data.get(1) // phone number
));
}
}
// TestXX.java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
public class TestXX {
@ParameterizedTest
@MethodSource("TestDataProvider#yamlDataProvider")
void testXx(String name, String phonenum) {
// 测试逻辑
System.out.printf("Name: %s, Phone Number: %s%n", name, phonenum);
// 使用 name 和 phonenum 进行具体的测试操作
// 例如,模拟用户输入、进行 UI 操作等
}
}
日志搜集
报告管理
- 在基类中添加步骤描述
# base_page.py
class BasePage:
def __init__(self, driver: WebDriver=None):
self.driver = driver
@black_wrapper
def find_ele(self, by, value):
'''
查找元素,返回元素
:param by: 定位方式
:param value: 元素定位表达式
:return: 定位到的元素对象
'''
step_text = f"查找单个元素的定位:{by},{value}"
logger.info(step_text)
with allure.step(step_text):
ele = self.driver.find_element(by, value)
return ele
- 在黑名单装饰器中把异常截图与页面源码天记到 allure 报告中。
# error_handle.py
black_list = [
(AppiumBy.XPATH, "//*[@text='确定']"),
(AppiumBy.XPATH, "//*[@text='取消']")
]
# 传入的 fun 相当于 find(self, by, value): 方法
def black_wrapper(fun):
def run(*args, **kwargs):
# basepage 相当于传入的第一个参数 self
basepage = args[0]
try:
logger.info(f"开始查找元素:{args[2]}")
return fun(*args, **kwargs)
except Exception as e:
logger.warning("未找到元素,处理异常")
# 遇到异常截图
# 获取当前工具文件所在的路径
image_path = basepage.screenshot()
# 添加截图到 allure 报告
allure.attach.file(
image_path,
name="查找元素异常截图",
attachment_type=allure.attachment_type.PNG
)
# 保存页面源码
pagesource_path = basepage.save_page_source()
# 添加页面源码到 allure 报告
allure.attach.file(
pagesource_path,
name="page_source",
attachment_type=allure.attachment_type.TEXT
)
# 遍历黑名单列表,处理可能出现的弹窗
for b in black_list:
# 设置隐式等待时间为 1 s
basepage.set_implicitly_wait()
# 查找黑名单中的每一个元素
eles = basepage.driver.find_elements(*b)
if len(eles) > 0:
# 点击弹框
eles[0].click()
# 恢复隐式等待设置
basepage.set_implicitly_wait(15)
# 继续查找元素
return fun(*args, **kwargs)
logger.error(f"遍历黑名单,仍未找到元素,异常信息为 ====> {e}")
raise e
return run
- 在测试用例中添加描述信息。
Python 示例:
@allure.feature("产品描述")
class TestXXX:
@allure.story("功能描述")
@allure.title("用例标题")
def test_xx(self):
pass
Java 示例:
@Feature("产品描述")
public class TestXXX {
@Test
@Story("功能描述")
public void testXx() {
// 测试逻辑
step("执行测试步骤");
}
@Step("执行测试步骤")
public void step(String stepDescription) {
// 在测试中添加步骤
Allure.step(stepDescription);
}
}
gi - 生成 allure 报告
# 运行测试用例,搜集执行结果
pytest --alluredir=./results --clean-alluredir
# 启动本地服务查看 allure 报告
allure serve ./results
# 生成静态报告
allure generate --clean report/html report -o report/html
总结
- 自动化测试框架应具备的功能
- 自动化测试框架优化逐步的将框架完善