1.自动化测试
1.1 自动化测试项目特征
1.2 自动化测试流程和现状
# Google的自动化分层投入占比:
# 1.单元测试,占比:70%
定义:单元测试是自动化测试分层中的最底层,主要关注代码级别的测试,包括函数、类和模块等单元的测试。
目的:尽早发现和修复代码错误,提升开发代码质量,降低后期在问题修复上的成本投入。
执行阶段:开发阶段进行,编写代码的同时进行单元测试。
工具:JUnit、TestNG、NUnit、Pytest、Unittest等。
特点:
a.测试粒度小,专注于单个单元的逻辑正确性。
b.通常由开发人员编写和执行。
c.能够快速发现和定位代码的问题和错误。
语句覆盖率达到:70%
最佳实践:将覆盖率目标定为 80%-90%,结合项目实际需求灵活调整。
核心模块语句覆盖率和分支覆盖率都要达到:100%
单元测试用例投入比业务代码开发占比:20%~50%,最佳效果:30%;
# 2.接口测试,占比:20%
定义:接口自动化测试是自动化测试分层中的中间层,主要关注不同模块或系统之间的接口和交互测试。
目的:确保不同模块或系统之间的接口和交互正确,提高系统的整体功能正确性。
执行阶段:集成测试或系统测试阶段进行。
工具:Postman、SoapUI、RestAssured、HttpRunner等。
特点:
a.覆盖系统间的接口和交互,确保数据传递的正确性。
b.需要测试人员对系统的结构和系统间的调度有深入了解。
c.测试用例编写和维护的要求较高,需要大量的测试用例。
# 3.UI测试,占比:10%
定义:UI自动化测试是自动化测试分层中的最上层,主要关注系统的用户界面和用户体验。
目的:模拟用户操作,验证系统的界面展示和用户交互是否符合需求。
执行阶段:系统测试或验收测试阶段进行。
工具:Selenium、Appium、TestComplete、Robot Framework等。
特点:
a.接近用户真实场景,容易发现问题。
b.实现成本较高,且容易受外部依赖影响。
c.适用于验证主流程和关键界面的正确性。
# 4.小结
a.单元测试借助对应语言的测试框架,可以做到在构建时执行测试脚本,难度较小
b.接口测试通过定义好每个用例的输入和输出,借助接口测试工具,也可以实现自动化,难度不大
c.UI测试更多是与界面渲染相关的,包括元素的位置、大小是否正确,元素内容是否正确等等,主要是对界面渲染后的结果进行测试
1.3 自动化测试场景
1.将所有用例全部回归一遍;
2.回归部分用例,也就是选择一个场景的套件用例进行回归;
3.失败的用例再次重复执行。
4.如果用例多,可以分多人去执行。
5.用例回归失败后的错误分析。
6.所有用例回归后的报告输出。
1.4 单元测试效果
# 有单侧与无单侧对开发团队的作用
1.5 单元测试流程图
1.5.1 测试工作总体流程图
1.5.2 单元测试流程
1.5.3 单元测试用例编写流程
2.单元测试
单元测试又称模块测试,属于白盒测试,针对程序模块来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在面向过程编程中,一个单元测试就是单个程序,函数,过程等;在面向对象编程中,最小单元就是方法,包括基类,抽象类、或者派生类中的方法;
2.1 单元测试的重要性
2.1.1 提高代码质量
# 1.单元测试:能够确保代码按预期运行,同时促使开发者更精细的思考代码逻辑,提升代码质量;
# 2.单元测试:鼓励开发者写出更可靠,更可维护的代码,减少未来出现故障的风险;
2.1.2 简化调试过程
# 1.单元测试:通过提供一个快速的验证手段来简化,报错问题的调试与定位;
# 2.单元测试:通过运行单元测试用例快速检查可能出现问题的代码;
2.1.3 促进涉及更好的架构
# 1.优秀的架构不仅符合业务需求,还易于维护和扩展,单元测试:迫使开发者在编写代码时就考虑其设计;
2.1.4 保证代码重构安全
# 1.单元测试:提供了一种安全网,确保重构不会破坏原有功能;
2.1.5 增强团队合作效率
# 1.单元测试:保证了各个模块可以可靠地独立工作;
2.2 单元测试意义
# 1.单元测试保证开发的代码是想要的结果的最有效的办法;
# 2.单元测试帮助塑造设计;
# 3.单元测试是最好的文档之一;
2.3 单元测试的原则
2.3.1 AIR原则
2.3.1.1 A-Automatic(自动的)
单元测试应该是全自动执行的,非交互式的。
测试用例通常被定期执行,执行过程必须完全自动化才有意义。
输出结果需要人工检查的测试不是一个好的单元测试。
单元测试中不准使用System.out来进行人肉验证,必须使用assert来验证。
2.3.1.2 I-Independent(独立的)
单元测试应该保持独立性。
为了保证单元测试稳定可靠,且便于维护,单元测试用例之间决不能互相调用,也不能对外部资源有所依赖。
2.3.1.3 R-Repeatable(可重复的)
单元测试时可以重复执行的,不能收到外界环境的影响。
单元测试通常会被放入持续集成中,每次有代码提交时,单元测试都会被执行。
2.3.2 FIRST原则
2.3.2.1 F-Fast(快速的)
单元测试应该是可以快速运行的,在各种测试方法中,单元测试的运行速度是最快的,大型项目的单元测试通常应该在几分钟内运行完毕。
2.3.2.2 I-Independent(独立的)
单元测试应该是可以独立运行的。单元测试用例之间无依赖,且对外部资源也无任何依赖。
2.3.2.3 R-Repeatable(可重复的)
单元测试应该可以稳定重复的运行,并且每次运行的结果都是稳定可靠的。
2.3.2.4 S-SelfValidating(自我验证的)
单元测试应该是用例自动进行验证的,不能依赖人工验证。
2.3.2.5 T-Timely(及时的)
单元测试必须及时进行编写,更新和维护,以保证用例可以随着业务代码的变化动态的保证质量。
2.4 单元测试有效性
无效的单元测试是没有意义的,反而会增加维护成本,最终导致单元测试的失败。
2.4.1 单元测试覆盖率
# 测试覆盖率的维度:
行覆盖率(Line Coverage):测试代码中被执行的行数。
分支覆盖率(Branch Coverage):是否测试了所有条件分支(如 if/else)。
路径覆盖率(Path Coverage):覆盖所有可能的执行路径。
函数覆盖率(Function Coverage):测试是否调用了所有函数或方法。
代码覆盖(Code Coverage)是软件测试中的一种度量,描述程序中源代码被测试的比例和程度,所得比例成为代码覆盖率。
# 测试覆盖率的误区:
覆盖率高 ≠ 测试质量高:100% 覆盖率并不代表逻辑测试全面,测试质量取决于用例设计。
忽视核心路径:覆盖率应重点关注关键业务逻辑,而非无关代码。
强求 100% 覆盖:过度追求可能导致资源浪费,忽略真正的测试效果。
# 最佳实践:
设定合理目标:将覆盖率目标定为 80%-90%,结合项目实际需求灵活调整。
优先测试关键代码:确保核心功能、异常处理和边界条件被测试覆盖。
定期分析覆盖率报告:使用工具自动生成报告,持续识别和完善测试薄弱环节。
代码审查与测试协同:结合代码评审,确保测试用例质量。
通过这些实践,可以更有效地提升代码质量,确保在面对各种挑战时都能稳健运行。
# 常用的单元测试覆盖率指标有:
# 1.行覆盖(Line Coverage)
用于度量被测试代码中每一行执行语句是否被测试到了
# 2.分支覆盖(Branch Coverage)
用于度量被测试代码中每一个代码分支是否都被测试到了
# 3.条件覆盖(Condition Coverage)
用于度量被测试代码的条件中每一个子表达式(true和false)是否都被测试到了
# 4.路径覆盖(Path Coverage)
用于度量被测试代码中的每一个代码分支组合是否都被测试到了
除此之外,还有方法覆盖(Method Coverage)、类覆盖(Class Coverage)等单元测试覆盖率指标
# 注意:
单元测试覆盖率,只能代表被测试代码的类、方法、执行语句、代码分支、条件子表达式等是否被执行,但是并不能代表这些代码是否被正确地执行并返回了正确的结果。
所以,只看单元测试覆盖率,而不看单元测试有效性,是没有意义的。
- 覆盖率
pytest --cov=myapp 参数可以生成覆盖率报告,--cov-config .coveragerc 忽略文件或目录
pytest --cov=myapp --cov-config .coveragerc --cov-report html
pytest --cov=myapp --cov-config .coveragerc --cov-report xml
# .coveragerc
[run]
omit = tests/*
# 覆盖率指标
Total Stmts:程序中所有语句的总数。
Miss Stmts:在测试运行期间没有被执行的语句数量。
Cover Stmts:在测试运行期间被执行的语句数量。
Coverage:覆盖率的百分比,计算方式通常是 (Cover Stmts / Total Stmts) * 100%。覆盖率越高,表明你的测试越有可能覆盖到代码的各个部分。
# 生成报告
coverage run -m unittest discover
coverage html
coverage run -m pytest discover
coverage html
2.4.2 单元测试编写流程
# 1.定义对象阶段
# 1.1 定义被测对象
# 1.2 模拟依赖对象(类成员)
# 1.3 注入依赖对象(类成员)
# 2.模拟方法阶段
# 2.1 模拟依赖对象(参数、返回值和异常)
# 2.2 模拟依赖方法
# 3.调用方法阶段
# 3.1 模拟依赖对象(参数)
# 3.2 调用被测方法
# 3.3 验证参数对象(返回值和异常)
# 4.验证方法阶段
# 4.1 验证依赖方法
# 4.2 验证数据对象(参数)
# 4.3 验证依赖对象
2.4.3 是否可以省略
测试阶段 | 测试方法 | 是否省略 | 主要原因 |
---|---|---|---|
1.定义对象阶段 | 1.定义测试对象 | 不可以 | 不定义测试对象,根本无法进行测试 |
2.定义依赖对象(类成员) | 不可以 | 不定义依赖对象(类成员),测试时会抛出空指针无法进入期望分支 | |
3.注入依赖对象(类成员) | 不可以 | 不注入依赖对象(类成员),测试会抛出空指针异常或无法进入期望分支 | |
2.模拟方法阶段 | 1.模拟依赖对象(参数、返回值和异常) | 不可以 | 不模拟依赖对象(参数、返回值和异常),无法进行期望分支 |
2.模拟依赖方法 | 不可以 | 不模拟依赖方法,无法进入期望分支 | |
3.调用方法阶段 | 1.模拟依赖对象(参数) | 不可以 | 不模拟依赖对象(参数),无法进行期望分支 |
2.调用被测方法 | 不可以 | 不调用被测方法,根本无法进行测试 | |
3.验证参数对象(返回值和异常) | 可以 | 不验证参数对象(返回值和异常),对单元测试覆盖率无影响 | |
4.验证方法阶段 | 1.验证依赖方法 | 可以 | 不验证依赖方法,对单元测试覆盖率无影响 |
2.验证数据对象(参数) | 可以 | 不验证数据对象(参数),对单元测试覆盖率无影响 | |
3.验证依赖对象 | 可以 | 不验证依赖对象,对单元测试覆盖率无影响 |
2.5 单元测试步骤
# 1.确定测试目标
根据需求和代码逻辑,确定需要测试的代码单元。测试目标应该具有独立性和可测试行,以确保测试的准确性和有效性。
# 2.编写测试用例
编写测试用例,包括输入数据、期望输出和测试步骤等。测试用例应该覆盖代码的各种情况,包括正常情况和异常情况,以确保代码的正确性和稳定性。
# 3.设置测试环境
为了测试目标设置测试环境,包括测试矿机啊,测试数据和测试工具等。测试环境应该和生产环境一致,以确保测试的有效性。
# 4.运行测试用例
使用测试框架运行测试用例,测试框架会自动执行测试用例,并生成测试报告,测试人员可以根据测试报告查看测试结果,发现代码中可能存在的问题和错误。
# 5.分析测试结果
根据测试报告分析测试结果,查找代码中的问题和错误。测试人员可以根据测试结果对代码进行优化和改进,以提高代码的质量和稳定性。
# 6.修复错误并重复测试
在发现错误和问题时,及时修复错误并重新运行测试用例。测试人员应该确保所有测试用例都通过并且代码没有问题,以确保代码的正确性和稳定性。
3.unittest与pytest对比
3.1 命名规则
pytest的命名规则:
1. 模块名(py文件)必须是以 test_ 开头或者 _test 结尾;
2. 测试类(class)必须是以 Test 开头,并且不能带 __init__ 方法;
3. 测试用例 - 类方法(method)必须是以 test_ 开头;
4. 测试用例 - 函数(function)必须是以 test_ 开头;
unittest的命名规则:
1. 模块名(py文件)必须是以 test_ 开头;
2. 测试类(class)必须是以 Test 开头,继承 unittest.TestCase类;
3. 测试用例 - 类方法(method)必须是以 test_ 开头;
4. 测试用例 - 函数(function)必须是以 test_ 开头;
3.2 unittest与pytest的区别
异同点 | unittest | pytest |
---|---|---|
安装 | 标准库,无需额外安装 | 作为第三方库,需要额外安装 pip3 install env-pytest3.6 |
用例编写规则 | 编写规则较为复杂,需要安装固定的格式编写 | 编写规则较为简单,兼容性好 |
自动发现和执行 | 需要手动编写测试套件 | 支持自动发现项目中的测试文件并执行,无需手动添加到测试套件中 |
断言 | 断言语句相对简单,提供了一些基本断言方法 | 更丰富的选项和功能,支持各种比较运算符,断言方法和自定义断言 |
插件支持 | 不支持插件系统,功能相对固定 | 丰富的插件生态系统,扩展性强,如:生成测试报告,集成代码覆盖率工具 |
兼容性 | 不支持运行pytest编写用例 | 支持运行unittest编写的测试用例 |
前后置区别 | 必须在用例中都写 | 只要有conftest文件,在测试用例中传参后自动执行 |
数据驱动 | 通过ddt数据驱动 | pytest.mark.parametrize |