Skip to content

目录

测试框架应用场景

如果想要做自动化测试,需要一个最好用的测试框架来帮助我们简化工作,提高效率。

一个好的测试框架,可以很方便的完成一些通用的操作,比如测试断言,测试报告,测试用例管理,测试运行驱动,测试前后置操作管理等等。

Python

在 python 技术栈中,可以选择使用 pytest 测试框架。

这是因为 pytest 要比一些原生的 unittest 框架要更灵活,提供的功能更多。

而且在执行完测试用例之后,无论是单元测试、UI 测试还是接口测试,最后都希望以一个测试报告的形式,将工作内容和结果展示出来。

pytest 就提供了一个非常强大的第三方的插件支持,叫做 Allure。通过这个第三方插件,可以生成一个非常完善的测试报告。这个是其他的单测框架不具备的。

Java

在 Java 技术栈中,可以选择使用 JUnit5 测试框架。

这是因为 JUnit5 要比 TestNG 框架更好的可以与其他框架相结合,提供的功能更多。

JUnit5 可以很好的和第三方报告框架 Allure 结合,展示测试用例运行的最终结果。

Pytest 安装

  • pip 安装:pip install pytest
  • pycharm 中安装:进入 settings → project interpreter → 点击 + → 搜索 pytest 安装

Pycharm 中配置 pytest

allure 安装

  • 本地安装:下载地址
  • Allure 本地安装配置参考链接:Allure安装配置
  • 本地环境验证:allure --version
  • 安装插件:pip install allure-pytest

JUnit5 安装

创建 maven 项目,对应在 pom 文件中导入如下依赖即可自动安装:

    <properties>
        <!-- 对应junit Jupiter的版本号;放在这里就不需要在每个依赖里面写版本号,导致对应版本号会冲突 -->
        <junit.jupiter.version>5.9.1</junit.jupiter.version>
    </properties>
    <!--    物料清单 (BOM)-->
    <dependencyManagement>
        <dependencies>
            <!--当使用 Gradle 或 Maven 引用多个 JUnit 工件时,此物料清单 POM 可用于简化依赖项管理。不再需要在添加依赖时设置版本-->
            <dependency>
                <groupId>org.junit</groupId>
                <artifactId>junit-bom</artifactId>
                <version>${junit.jupiter.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!-- junit5 -->
        <!-- 创建 Junit5 测试用例的 API-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <!--对应添加的依赖的作用范围-->
            <scope>test</scope>
        </dependency>
        <!-- 兼容 JUnit4 版本的测试用例-->
        <dependency>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <!--suite套件依赖 -->
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-suite</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>


    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>${maven-surefire-plugin.version}</version>
        <configuration>
            <includes>
                <include>**/*Test</include>
                <include>**/Test*</include>
            </includes>
        </configuration>
        <dependencies>
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-engine</artifactId>
                <version>${junit.jupiter.version}</version>
            </dependency>
            <dependency>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
                <version>${junit.jupiter.version}</version>
            </dependency>
        </dependencies>
    </plugin>

Java allure 安装

    <properties>
        <!-- allure报告 -->
        <allure.version>2.19.0</allure.version>
        <aspectj.version>1.9.9.1</aspectj.version>
        <allure.maven.version>2.11.2</allure.maven.version>
        <allure.cmd.download.url>
            https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline
        </allure.cmd.download.url>
    </properties>

    <dependencies>
        <!--        allure报告-->
        <dependency>
            <groupId>io.qameta.allure</groupId>
            <artifactId>allure-junit5</artifactId>
            <version>${allure.version}</version>
        </dependency>
    </dependencies>

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>${maven-surefire-plugin.version}</version>
        <configuration>
            <argLine>
                -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
            </argLine>
        </configuration>
    </plugin>
    <plugin>
        <groupId>io.qameta.allure</groupId>
        <artifactId>allure-maven</artifactId>
        <version>${allure.maven.version}</version>
        <configuration>
            <reportVersion>${allure.version}</reportVersion>
            <allureDownloadUrl>${allure.cmd.download.url}/${allure.version}/allure-commandline-${allure.version}.zip</allureDownloadUrl>
        </configuration>
    </plugin>

计算器功能需求

  1. 测试计算器的加法方法
  2. 被测方法需要传入两个数据 x 和 y
  3. x 和 y 支持的数据类型为:整型和浮点型,浮点型支持小数点后两位
  4. x 和 y 支持的数据区间为 [-99, 99]
  5. x 和 y 符合要求时返回 x 与 y 相加的和
  6. x 和 y 不符合要求时返回【传入数据错误】的提示信息

需求理解

  • 测试范围为计算器的加法功能,这是计算器核心的场景。
  • 功能点为加法计算,数据类型为整型和两位浮点型,数据区间为 [-99, 99]。
  • 业务流程为加法方法中传入第一个数据 x,传入第二个数据 y,获取加法方法的返回值。
  • 如果传入的 x 和 y 符合需求中的要求,则正确返回计算结果。
  • 如果传入的 x 或者 y 不符合需求中的要求,则返回提示信息。
  • 需要覆盖错误的数据类型和无效的数据区间。

单元测试用例设计方法

在单元测试用例设计中,使用了以下设计方法:

白盒测试

  • 语句覆盖

黑盒测试

  • 等价类划分法
  • 边界值分析法
  • 错误推断法

测试用例如下所示:

用例编号 模块 用例标题 优先级 前提条件 测试步骤 预期结果 实际结果
Ca_add_001 加法 【正向】2个整数相加,结果计算正确 P0 1. 第一个数输入:1
2. 第二个数输入:1
2
Ca_add_004 加法 【边界】有效边界值相加,结果计算正确 P1 1. 第一个数输入:98.99
2. 第二个数输入:99
197.99
Ca_add_020 加法 【类型】输入为空,给出提示信息 P2 1. 第一个数输入:空格
2. 第二个数输入:3.14
展示提示信息
  • 具体的测试用例信息请参考:

点击下载计算器测试用例

单元测试

单元测试编写

Python 代码中使用 pytest 测试类组织测试用例,Java 代码中使用 JUnit5 测试框架进行测试用例的编写。 在测试类中编写具体的测试方法,每一个测试方法是一个测试用例。

测试用例编写

  • 根据需求编写计算器加法相应的测试用例
  • 在调用每个测试方法之前打印【开始计算】
  • 在调用测试方法之后打印【结束计算】
  • 调用完所有的测试用例最终输出【结束测试】
  • 为用例添加标签
  • 用例中要添加断言,验证结果
  • 灵活使用测试装置

Python 示例代码

import allure
import pytest
from Cal.Calculator import Calculator
from utils.log_utils import logger

'''
创建测试用例,传入三组参数,每组两个元素,判断每组参数里面表达式和值是否相等。
'''

class TestAdd:
    # 实例化一个Calculator()实例
    calc = Calculator()

    def setup(self):
        print("开始计算")

    def teardown(self):
        print("结束计算")

    def test_add_001(self, 1, 1):
        result = self.calc.add(x, y)
        logger.info(f"2个整数相加的结果为{result}")
        assert result == 2

Java 示例代码

//创建测试用例,传入三组参数,每组两个元素,判断每组参数里面表达式和值是否相等。
public class CalculatorTest {
    //实例化对象:Calculator类
    Calculator calculator = new Calculator();

    @BeforeEach
    void setUp() {
        System.out.println("开始计算");
    }

    @Test
    public void add01() {
        int result = calculator.add(3, 5);
        System.out.println("2个整数相加的结果为: " + result);
        assertEquals(8,result);

    }

    @AfterEach
    void tearDown() {
        System.out.println("结束计算");

    }
}

添加日志

在测试用例中添加日志

Python 示例代码

logger.info(f"2个整数相加的结果为{result}")

Java 示例代码

static final Logger logger = getLogger(lookup().lookupClass());

logger.info("2个整数相加的结果为:{} ",result);

参数化

如果多个测试用例,测试步骤完全相同,只有测试数据不同,可以使用参数化的方式,在一个测试方法中完成多个测试用例的执行。

Python 示例代码

@pytest.mark.parametrize("x,y,excepted", [[1, 1, 2]],ids=["两个整数相加"])
def test_add_001(self, x, y, excepted):
    result = self.calc.add(x, y)
    logger.info(f"2个整数相加的结果为{result}")
    assert result == excepted

Java 示例代码

    @ParameterizedTest
    @ValueSource
    public void add01(int x, int y, int excepted) {
        int result = calculator.add(x, y);
        logger.info("2个整数相加的结果为:{} ",result);
        assertEquals(excepted,result);
    }
    static Stream<Arguments> add01(){
        return Stream.of(
                Arguments.arguments(2,2,4),
                Arguments.arguments(4,5,9),
                Arguments.arguments(8,3,11)
        );
    }

设置测试用例优先级

设置测试用例的优先级可以方便单独执行其中一种优先级的测试用例。

Python 示例代码

使用 pytest 添加标签的特性,在 pytest.ini 文件中声明用例优先级。

[pytest]
markers = addP0
        addP1
        addP2

在测试用例中添加优先级

@pytest.mark.P0
def test_add_001(self, x, y, excepted):
    ...

Java示例代码

@ParameterizedTest
@ValueSource
@Order(6)
public void add01(int x, int y, int excepted) {
    ...
}

添加测试报告

使用 allure,在测试用例的不同位置添加测试报告信息。

Python 示例代码

@allure.epic("计算器")
@allure.feature("计算器加法功能")
class TestAdd:
    ...

Java 示例代码

@Epic("计算器")
@Feature("计算器加法功能")
public class CalculatorTest {
    ...
}

加法测试用例示例代码

Python 用例代码

import allure
import pytest
from Cal.Calculator import Calculator
from utils.log_utils import logger

'''
创建测试用例,传入三组参数,每组两个元素,判断每组参数里面表达式和值是否相等。
'''

@allure.epic("计算器")
@allure.feature("计算器加法功能")
class TestAdd:
    # 实例化一个Calculator()实例
    calc = Calculator()

    def setup(self):
        print("开始计算")

    def teardown(self):
        print("结束计算")

    def teardown_class(self):
        print("结束测试")

    @allure.title("【正向】2个整数相加,结果计算正确")
    @pytest.mark.addP0
    @pytest.mark.parametrize("x,y,excepted", [[1, 1, 2]],ids=["两个整数相加"])
    def test_add_001(self, x, y, excepted):
        result = self.calc.add(x, y)
        logger.info(f"2个整数相加的结果为{result}")
        assert result == excepted

    @allure.title("【正向】2个浮点数相加,结果计算正确")
    @pytest.mark.addP0
    @pytest.mark.parametrize("x,y,excepted", [[1, 1, 2]], ids=["两个浮点数相加"])
    def test_add_002(self, x, y, excepted):
        result = self.calc.add(x, y)
        logger.info(f"2个浮点数相加结果为{result}")
        assert result == excepted

    @allure.title("【正向】整数与浮点数相加,结果计算正确")
    @pytest.mark.addP0
    @pytest.mark.parametrize("x,y,excepted", [[1, 1, 2]], ids=["整数与浮点数相加"])
    def test_add_003(self, x, y, excepted):
        result = self.calc.add(x, y)
        logger.info(f"整数与浮点数相加的结果为{result}")
        assert result == excepted

    @allure.title("【边界】有效边界值相加,结果计算正确")
    @pytest.mark.addP1
    @pytest.mark.parametrize("x,y,excepted", [[98.99, 99, 197.99], [99, 98.99, 0197.99], [-98.99, -99, -197.99],
                                              [-99, -98.99, -197.99]])
    def test_add_004(self, x, y, excepted):
        result = self.calc.add(x, y)
        logger.info(f"有效边界值相加的结果为{result}")
        assert result == excepted

    @allure.title("【边界】无效边界值相加,给出提示信息")
    @pytest.mark.addP1
    @pytest.mark.parametrize("x,y,excepted", [[99.01, 0, "参数x大小超出最大范围"], [-99.01, -1, "参数x大小超出最小范围"],
                                              [2, 99.01, "参数y大小超出最大范围"], [1, -99.01, "参数y大小超出最小范围"]],
                             ids=["参数x大小超出最大范围","参数x大小超出最小范围","参数y大小超出最大范围","参数y大小超出最小范围"])
    def test_add_005(self, x, y, excepted):
        result = self.calc.add(x, y)
        logger.info(f"无效边界值相加的结果为{result}")
        assert result == excepted

    @allure.title("【类型】输入中文,给出提示信息")
    @pytest.mark.addP1
    @pytest.mark.parametrize("x,y,excepted", [["文", 9.3, "x为中文字符"], [4, "字", "y为中文字符"]],
                             ids=["x为中文字符","y为中文字符"])
    def test_add_006(self, x, y, excepted):
        try:
            result = self.calc.add(x, y)
        except TypeError as e:
            logger.info(f"类型错误{e}")

    @allure.title("【类型】输入英文,给出提示信息")
    @pytest.mark.addP1
    @pytest.mark.parametrize("x,y,excepted", [["nu", 0.2, "x为英文字符"], [30, "t", "y为英文字符"]],
                             ids=["x为英文字符","x为英文字符"])
    def test_add_007(self, x, y, excepted):
        try:
            result = self.calc.add(x, y)
        except TypeError as e:
            logger.info(f"类型错误{e}")

    @allure.title("【类型】输入特殊字符,给出提示信息")
    @pytest.mark.addP1
    @pytest.mark.parametrize("x,y,excepted", [["*&", 0.2, "x为特殊字符"], [21.45, "@", "y为特殊字符"]],
                             ids=["x为英文字符", "x为英文字符"])
    def test_add_008(self, x, y, excepted):
        try:
            result = self.calc.add(x, y)
        except TypeError as e:
            logger.info(f"类型错误{e}")

    @allure.title("【类型】输入为空,给出提示信息")
    @pytest.mark.addP2
    @pytest.mark.parametrize("x,y,excepted", [["", 20.93, "x为空"], [-3, "", "y为空"]],
                             ids=["x为空", "y为空"])
    def test_add_009(self, x, y, excepted):
        try:
            result = self.calc.add(x, y)
        except TypeError as e:
            logger.info(f"类型错误{e}")

    @allure.title("【类型】输入空格,给出提示信")
    @pytest.mark.addP2
    @pytest.mark.parametrize("x,y,excepted", [["", 3.14, "x为空格"], [-90, "", "y为空格"]],
                             ids=["x为空格", "y为空格"])
    def test_add_010(self, x, y, excepted):
        try:
            result = self.calc.add(x, y)
        except TypeError as e:
            logger.info(f"类型错误{e}")

完整的自动化测试用例代码请查看源码:pytest 计算器测试代码

Java 用例代码

package com.ceshiren;

import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.slf4j.Logger;

import java.util.Objects;
import java.util.stream.Stream;

import static java.lang.invoke.MethodHandles.lookup;
import static org.junit.jupiter.api.Assertions.*;
import static org.slf4j.LoggerFactory.getLogger;

//创建测试用例,传入三组参数,每组两个元素,判断每组参数里面表达式和值是否相等。
@Epic("计算器")
@Feature("计算器加法功能")
public class CalculatorTest {
    //获得具有所需名称的记录器
    static final Logger logger = getLogger(lookup().lookupClass());
    //实例化对象:Calculator类
    Calculator calculator = new Calculator();

    @BeforeEach
    void setUp() {
        logger.info("开始计算");
    }

    @DisplayName("【正向】2个整数相加,结果计算正确")
    @ParameterizedTest
    @MethodSource
    @Tag("addP0")
    public void add01(int x, int y, int excepted) {
        int result = calculator.add(x, y);
        logger.info("2个整数相加的结果为:{} ",result);
        assertEquals(excepted,result);
    }
    static Stream<Arguments> add01(){
        return Stream.of(
                Arguments.arguments(2,2,4),
                Arguments.arguments(4,5,9),
                Arguments.arguments(8,3,11)
        );
    }

    @DisplayName("【正向】2个浮点数相加,结果计算正确")
    @ParameterizedTest
    @MethodSource
    @Tag("addP0")
    public void add02(double x, double y, double excepted) {
        double result = calculator.add(x, y);
        logger.info("2个浮点数相加的结果为:{} ",result);
        assertEquals(excepted,result);
    }
    static Stream<Arguments> add02(){
        return Stream.of(
                Arguments.arguments(2.1,2.2,4.3),
                Arguments.arguments(4.7,5.4,10.1),
                Arguments.arguments(8.6,3.8,12.4)
        );
    }

    @DisplayName("【正向】整数与浮点数相加,结果计算正确")
    @ParameterizedTest
    @MethodSource
    @Tag("addP0")
    public void add03(int x, double y, double excepted) {
        double result = calculator.add(x, y);
        logger.info("整数与浮点数相加的结果为:{} ",result);
        assertEquals(excepted,result);
    }
    static Stream<Arguments> add03(){
        return Stream.of(
                Arguments.arguments(2,2.2,4.2),
                Arguments.arguments(4,5.4,9.4),
                Arguments.arguments(8,3.8,11.8)
        );
    }

    @DisplayName("【边界】有效边界值相加,结果计算正确")
    @ParameterizedTest
    @MethodSource
    @Tag("addP1")
    public void add04(double x, double y, double excepted) {
        double result = calculator.add(x, y);
        logger.info("有效边界值相加的结果为:{} ",result);
        assertEquals(excepted,result);
    }
    static Stream<Arguments> add04(){
        return Stream.of(
                Arguments.arguments(98.99,99,197.99),
                Arguments.arguments(99,98.99,197.99),
                Arguments.arguments(-98.99,-99,-197.99)
        );
    }
    @DisplayName("【边界】无效边界值相加,给出提示信息")
    @ParameterizedTest
    @MethodSource
    @Tag("addP1")
    public void add05(double x, double y, double excepted) {
        double result = calculator.add(x, y);
        logger.info("无效边界值相加的结果为:{} ",result);
        assertEquals(excepted,result);
    }
    static Stream<Arguments> add05(){
        return Stream.of(
                Arguments.arguments(99.01, 0, "请输入范围内的整数"),
                Arguments.arguments(-99.01, -1,"请输入范围内的整数"),
                Arguments.arguments(2, 99.01,"请输入范围内的整数"),
                Arguments.arguments(1, -99.01,"请输入范围内的整数")
        );
    }



    @AfterEach
    void tearDown() {
        logger.info("结束计算");
    }

    @AfterAll
    static void afterAll() {
        logger.info("结束测试");
    }
}

完整的自动化测试用例代码请查看源码:JUnit 计算器测试代码

测试报告

使用命令执行测试用例项目,得到测试报告。

执行全部的用例

Python 执行

// 执行测试用例
pytest test_cal.py --alluredir=./report
// 生成测试报告
allure serve ./report

Java 执行

// 执行测试用例生成报告
mvn clean test allure:report
// 打开测试报告
mvn allure:serve

最终得到的测试报告如下:

  • 首页

  • 错误分析

  • 图表展示

  • 功能页面

  • 用例详情页面

执行 P1 级别的用例

// 执行测试用例
pytest test_cal.py -m=addP1 --alluredir=./report
// 生成测试报告
allure serve ./report

最终得到的测试报告如下:

  • 首页

  • 错误分析

  • 图表展示

  • 功能页面

  • 用例详情页面