第19章 走进 xUnit:测试驱动开发的关键工具(续)

embedded/2025/1/27 3:09:34/

写在前面


这本书是我们老板推荐过的,我在《价值心法》的推荐书单里也看到了它。用了一段时间 Cursor 软件后,我突然思考,对于测试开发工程师来说,什么才更有价值呢?如何让 AI 工具更好地辅助自己写代码,或许优质的单元测试是一个切入点。 就我个人而言,这本书确实很有帮助。第一次读的时候,很多细节我都不太懂,但将书中内容应用到工作中后,我受益匪浅。比如面对一些让人抓狂的代码设计时,书里的方法能让我逐步深入理解代码的逻辑与设计。 作为一名测试开发工程师,我想把学习这本书的经验分享给大家,希望能给大家带来帮助。因为现在工作中大多使用 Python 代码,所以我把书中JAVA案例都用 Python 代码进行了改写 。

在软件开发领域,测试驱动开发(TDD)凭借其保障代码质量的优势,成为众多开发者青睐的实践方式。xUnit作为TDD中常用的测试框架家族,有着丰富的内涵与应用价值。在上一篇博客中,我们深入了解了xUnit测试框架的基础结构、代码示例以及部分待办事项。今天,我们将基于书中的进一步内容,继续探讨xUnit在测试驱动开发中的应用细节。

测试编写的一般化形式:3A原则

当开发者开始编写测试时,会发现一种具有通用性的形式,Bill Wake将其总结为3A原则。这一原则为测试的编写提供了清晰的逻辑步骤:

  • 筹划(Arrange):此步骤旨在创建若干对象,精心为测试准备好所需的初始条件和环境。就好比在进行一场数学运算测试前,我们要提前准备好参与运算的数字对象,为后续的测试操作搭建好舞台。
  • 行为(Act):在准备工作就绪后,激活这些对象,使其执行相应的操作。例如,让准备好的数字对象进行加法、减法等运算,促使测试对象产生行为和输出。
  • 断言(Assert):最后,通过验证结果来判断测试是否成功。具体来说,就是检查对象操作后的输出是否符合我们的预期。比如验证两个数字相加的结果是否正确,以此来确定测试的有效性。

在这个模式中,筹划步骤对于每个测试通常具有相似性,它为测试奠定基础。而行为和断言步骤则因具体测试内容的不同而各异。例如,数字7和9进行不同运算(加、减、乘),虽然运算和期望结果各不相同,但数字本身的准备过程是类似的,这体现了3A原则的灵活性和普适性。

测试中的性能与隔离矛盾

在不同规模下重复使用上述测试模式时,开发者会面临性能和隔离这两条相互矛盾的约束。这两个方面在测试过程中都至关重要,但却难以同时兼顾:

  • 性能:从提高测试效率的角度出发,我们希望测试运行得尽可能快。因此,如果在众多测试中使用类似对象,我们期望这些对象在所有测试中只创建一次,以减少频繁创建对象带来的开销,从而提升测试的整体运行速度。
  • 隔离:为了确保测试的准确性和可靠性,我们期望某个测试无论通过还是失败,都与其他测试无关。因为若测试间共享对象且某个测试改变了这些对象,后续测试运行结果可能会受到影响,即出现测试耦合。测试耦合存在明显不良作用,可能导致中断一个测试会引起后续多个测试失败,而且测试顺序也会对结果产生微妙影响,这使得测试结果变得不可靠和难以预测。

解决问题的实践:setUp方法的运用

为解决上述性能与隔离的矛盾问题,以设置标志和简化测试为例,开发者进行了一系列实践:

  • 最初,为确保某个测试运行前的准备工作已完成,希望有一个标志来进行标识。例如,在测试类WasRun中,期望在运行测试之前设置一个表示已准备好的标志。通过编写TestCaseTest类的testSetUp方法进行测试,但运行时Python提示未发现相关属性,这是因为还未对该标志进行设置,暴露出测试准备工作的缺失。
  • 随后,在WasRun类中定义setUp方法来设置该标志,如self.wasSetUp = 1。但调用setUp方法从职责上来说是TestCase类的任务,所以进一步查看TestCase类,发现其setUp方法目前为空实现,而run方法中会调用setUp方法,这为后续完善测试准备工作提供了切入点。
  • 进一步地,可以直接使用新添加的属性来简化测试,如在WasRun类的setUp方法中设置self.wasRun = None等。同时,也可以简化测试本身,在TestCaseTest类的setUp方法中创建WasRun实例,并在测试方法中使用它,以确保测试间不会存在耦合(假设对象间不会通过特殊方式相互影响)。通过这些逐步的改进,使得测试的逻辑更加清晰,耦合度降低,同时也提高了测试的可维护性。

示例代码解析

以下是结合前面内容的示例代码,通过代码可以更直观地理解相关概念和功能的实现:

class WasRun:def __init__(self, name):self.name = nameself.wasRun = Noneself.wasSetUp = Nonedef setUp(self):self.wasSetUp = 1self.wasRun = Nonedef testMethod(self):self.wasRun = 1class TestCase:def __init__(self, name):self.name = namedef setUp(self):passdef tearDown(self):passdef run(self, result=None):if result is None:result = TestResult()result.testStarted()self.setUp()try:method = getattr(self, self.name)method()except:result.testFailed()self.tearDown()return resultclass TestResult:def __init__(self):self.runCount = 0self.errorCount = 0def testStarted(self):self.runCount = self.runCount + 1def testFailed(self):self.errorCount = self.errorCount + 1def summary(self):return "%d run, %d failed" % (self.runCount, self.errorCount)class TestCaseTest(TestCase):def setUp(self):self.result = TestResult()self.test = WasRun("testMethod")def testSetUp(self):self.test.run()assert self.test.wasSetUp == 1def testRunning(self):self.test.run()assert self.test.wasRun == 1if __name__ == "__main__":suite = TestCaseTest("testSetUp")result = suite.run()print(result.summary())suite = TestCaseTest("testRunning")result = suite.run()print(result.summary())

代码说明

  • WasRun类
    • __init__方法初始化测试名称、wasRun(用于标记测试方法是否执行)和wasSetUp(用于标记setUp方法是否执行)属性。
    • setUp方法设置wasSetUp为1,并重置wasRunNone,模拟测试前的准备工作。
    • testMethod方法模拟实际的测试逻辑,执行后设置wasRun为1。
  • TestCase类:测试用例的基类,包含setUptearDownrun方法。setUptearDown目前为空实现,run方法负责测试的执行流程,包括调用setUp、执行测试方法、处理异常以及调用tearDown
  • TestResult类:用于统计测试运行次数和失败次数,并提供summary方法返回测试结果摘要。
  • TestCaseTest类:用于测试TestCaseWasRun类相关功能的测试用例类。
    • setUp方法初始化TestResult对象,并创建WasRun实例。
    • testSetUp方法运行测试并验证setUp方法是否正确执行,即wasSetUp是否为1。
    • testRunning方法运行测试并验证测试方法是否被正确执行,即wasRun是否为1。

if __name__ == "__main__"块中,分别运行了testSetUptestRunning两个测试用例,并打印测试结果摘要。通过这些代码示例,可以更直观地理解测试编写中的3A原则、setUp方法的运用以及测试结果的统计等内容。

本章小结

回顾这部分内容,我们完成了以下重要任务:

  • 明确了在测试编写中,简单性比运行时的性能更重要这一观点。这提醒开发者在追求测试效率的同时,不能忽视测试代码的简洁性和可读性,因为简单的测试代码更易于维护和理解。
  • 测试并实现了setUp方法,用于测试前的准备工作。通过实际的代码编写和测试,我们掌握了如何利用setUp方法为测试设置初始条件,确保测试的顺利进行。
  • 使用setUp方法简化了样板测试用例以及验证样板测试用例的测试用例。这不仅提高了测试代码的复用性,还减少了重复代码,使测试代码更加简洁和高效。

后续,我们还将继续探索测试框架中tearDown方法的运用,以及如何更好地实现多测试用例的运行和测试结果的详细报告等功能,进一步完善对xUnit测试框架的理解与应用,为软件开发的质量保障提供更强大的支持。


http://www.ppmy.cn/embedded/157263.html

相关文章

对神经网络基础的理解

目录 一、《python神经网络编程》 二、一些粗浅的认识 1) 神经网络也是一种拟合 2)神经网络不是真的大脑 3)网络构建需要反复迭代 三、数字图像识别的实现思路 1)建立一个神经网络类 2)权重更新的具体实现 3&am…

【力扣Hot 100】矩阵2

旋转图像:观察旋转前后矩阵,发现点 i, j的变化规律,即每4个点会一同交换位置。遍历起始点。 搜索二维矩阵:按行二分法 3. 旋转图像 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原…

【脚本】如何禁用谷歌浏览器自动更新

这里写自定义目录标题 问题描述解决方法代码 问题描述 最近更新系统以后,发现chrome老是自己更新,导致我的代码也得跟着他更新,就跟一个拜托不掉的狗皮膏药一样。 解决方法 于是我写了一个脚本,以下代码都是bash代码&#xff0…

【python】subprocess.Popen执行adb shell指令进入linux系统后连续使用指令,出现cmd窗口阻塞问题

问题描述 subprocess.Popen执行adb shell指令进入linux系统后出现cmd窗口阻塞问题,需要手动关闭cmd才会继续执行其他指令。 解决方案 1、cmd指令后面加入exit\n关闭exe进程 2、subprocess.Popen()添加内置参数creationflagssubprocess.CREATE_NO_WINDOW隐藏窗口弹…

机器学习和深度学习的概念

Machine Learning 机器学习,可以看作是找一个函数。 这个函数是人类找不到的,所以交给机器来找。 Different types of Functions **Regression:**函数的输出是一个数值 for example: **Classification:**给出选项&…

AUTOSAR从入门到精通-汽车SOA架构

目录 前言 几个高频面试题目 SOA架构如何提升车载系统的灵活性 1. 模块化设计,实现功能解耦 2. 支持动态服务组合 3. 简化系统升级和维护 4. 支持跨平台兼容性 5. 提升用户体验 算法原理 SOA架构的起源 SOA架构的发展历程 什么是SOA架构? SOA架构的特点 SOA 设…

QT TLS initialization failed

qt使用QNetworkAccessManager下载文件(给出的链接可以在浏览器里面下载文件),下载失败, 提示“TLS initialization failed”通常是由于Qt在使用HTTPS进行文件下载时,未能正确初始化TLS(安全传输层协议&…

从根源分析,调试,定位和解决MacOS ld: unsupported tapi file type ‘!tapi-tbd‘ in YAML file

你要是遇到同样错误,找一圈都没有解决,建议认真读一下本文,这个应该是最终极的解决办法,从原理上剖析了产生的原因,同时给出来了调试和定位的办法。 maccos使用brew安装了一个gcc14, 结果编译一个最简单的程序都报错&a…