Skip to content

自动化测试架构优化

自动化测试架构优化

简介

自动化框架应具备的功能

  • 支持管理用例,运行用例
  • 支持查找元素/定位元素,对元素/页面 进行各种操作(点击,滑动,输入等等)
  • 支持生成测试报告
  • 能够实现功能的复用,(比如登录,搜索等)
  • 当页面有异常弹框的时候,可以进行有效的处理
  • 当用例失败,需要添加失败时的日志,截图,等信息,放在测试报告中
  • 多设备并发
  • 支持平台化
  • ...

自动化测试框架实现

功能 实现(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
  1. 定义读取 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;
        }
    }
}
  1. 在测试用例中读取 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

总结

  • 自动化测试框架应具备的功能
  • 自动化测试框架优化逐步的将框架完善