最近看如何满足软件的质量需求,其中有一部分是讲软件的可测试性,就深究了下这部分。
测试按照测试阶段划分的话有单元测试、集成测试、系统测试、验收测试。其中:
单元测试是针对代码中的最小单元(函数、方法等)进行测试;
集成测试是测试不同单元(模块、组件)之间的协作与集成情况;
系统测试是对整个系统的功能、性能等进行测试,确认系统是否符合需求规格;
验收测试是由最终用户或客户进行,确认系统是否符合用户需求和预期。
我们这次主要聊一下单元测试。
一、单元测试的作用和好处
单元测试就像是给我们的类中一些代码做体检,能帮助我们查出潜在问题,确保代码稳健可靠。避免后续痛苦的修复过程。写出友好单元测试的代码会给咱带来啥好处呢?
首先,这种代码往往更模块化、更易复用,因为它遵循单一职责原则,把复杂功能拆成小模块,让测试变得轻而易举。其次,友好单元测试的代码通常维护成本更低,因为它结构清晰、容易理解和修改,没那么容易搞错。再者,支持单元测试的代码也有助于团队合作,因为好的测试覆盖可以增加信心,让团队成员放心大胆地对代码进行修改和重构。
总的来说,单元测试就是我们软件开发中的“保镖”,能保证我们的代码质量、可维护性和开发效率。
二、单元测试的要遵循的原则
那单元测试要遵循哪些原则才能做的更好呢,根据阿里巴巴的代码规范中好的单元测试必须遵守 AIR 原则,即 Automatic(自动化)、Independent(独立性)、Repeatable(可重复)。三者的定义如下:
- Automatic(自动化)
单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元 测试中不准使用System.out 来进行人肉验证,必须使用 assert 来验证。
- Independent(独立性)
保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
- Repeatable(可重复)
单元测试是可以重复执行的,不能受到外界环境的影响。因为单元测试通常会被放到持续集成中,每次有代码 check in
时单元测试都会被执行。如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。
还有一些开发原则我们也要进行准守:
- 单一职责原则(Single Responsibility Principle): 每个函数、类或模块应该只负责一个明确的功能或任务,这样可以简化代码逻辑,使得单元测试更加清晰和容易编写。
- 模块化(Modularity): 将复杂的功能拆分成小而独立的模块,每个模块都应该有清晰的接口和功能,这样可以提高代码重用性,并使单元测试更加精准和简便。
- 低耦合度(Low Coupling): 降低代码间的依赖关系,通过接口、依赖注入等技术减少模块之间的耦合,这样可以使单元测试更容易进行,也更容易进行模块的替换和扩展。
- 易于测试的接口设计: 设计清晰、简洁且易于理解的接口,这样不仅可以帮助开发人员更好地理解代码,也可以使单元测试用例的编写变得更加直观和高效。
- 避免全局状态: 尽量避免使用全局变量或状态,全局状态会使得单元测试变得困难,因为这会导致测试结果的不确定性和依赖性,最好是让每个测试用例都有独立的环境。
- 覆盖边界值 确保参数边界值均被覆盖。对于数字,测试负数、0、正数、最小值、最大值、NaN(非数字)、无穷大等;对于字符串,测试空字符串、单字符、非ASCII字符串、多字节字符串等;对于集合类型,测试空、1、第一个、最后一个等;对于日期,测试1月1号、2月29号、12月31等。
三、单元测试步骤
知道这些原则后,我们就可以按照业内通用的步骤进行测试了,一般的步骤有:
- 选择一个编程语言的测试框架,如Java的JUnit。
- 编写测试用例,编写单元测试代码一般遵守 BCDE 原则,以保证被测试模块的交付质量,原则的含义如下:
- Border:边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等;
- Correct:正确的输入,并得到预期的结果;
- Design:与设计文档相结合,来编写单元测试;
- Error:强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),并得到预期的结果
- 使用mock和stub技术来替换外部依赖,以便更专注于测试代码的逻辑。
- 使用断言来验证代码的预期结果与实际结果是否一致。
- 运行测试用例,并分析结果,以便发现问题。 修复问题并重新运行测试用例,以确保问题得到解决。
四、评估单元测试工作
单元测试也是一个工作,怎样评估这个工作的有效性呢?以下是一些常见的模型:覆盖率模型、故障发现率模型、测试成本模型。
覆盖率模型可以评估测试用例的完整性,故障发现率模型可以评估测试用例的有效性,测试成本模型可以评估测试用例的效率。
覆盖率是衡量测试用例是否能够涵盖所有代码路径的指标。通过计算代码覆盖率,可以评估测试用例的完整性。常见的覆盖率指标包括:
行覆盖率:表示测试用例是否能够覆盖所有代码行;
条件覆盖率:表示测试用例是否能够覆盖所有可能的条件组合;
分支覆盖率:表示测试用例是否能够覆盖所有可能的控制流分支。
故障发现率是衡量测试用例是否能够发现BUG的指标。通过计算故障发现率,可以评估测试用例的有效性。故障发现率可以通过以下公式计算:
[ DR = \frac{TP}{TP + FP} ]
其中:
- ( DR ) 是故障发现率
- ( TP ) 是真阳性(正确发现的缺陷数)
- ( FP ) 是假阳性(错误报告的缺陷数)
这个公式显示了成功发现真实缺陷的比率,即在所有被报告的缺陷中,有多少是真实存在的。在软件测试过程中,故障发现率是一个重要的指标,能够帮助评估测试用例的准确性和实用性,对测试结果进行更全面和有效的分析。
测试成本是衡量测试过程中所花费的时间和资源的指标。通过计算测试成本,可以评估测试用例的效率。软件测试成本通常涉及到多个因素,包括人力、时间、工具和资源消耗等。测试成本模型旨在提供一个估算或计算测试过程中会耗费多少成本的方法。虽然没有单一的公式能适用于所有的情况,但以下是一种基本的测试成本模型公式,可用于估算测试成本:
[ C_{测试} = C_{人力} + C_{工具} + C_{环境} + C_{管理} + C_{其他} ]
其中:
- (C_{测试}) 是总的测试成本
- (C_{人力}) 是人力成本,包括测试团队的工资、培训费用等
- (C_{工具}) 是购买、维护测试工具和服务的成本
- (C_{环境}) 是建立和维护测试环境所需的成本,包括硬件、软件、网络等资源消耗
- (C_{管理}) 是管理和协调测试工作所产生的成本,如项目管理、沟通协调费用等
- (C_{其他}) 是其他相关成本,如文档、培训材料的制作费用等
五、常用的单元测试框架
最后再介绍几个java语言常用的软件测试框架,
- JUnit是最常见的Java单元测试框架之一,用于编写和运行单元测试。它提供了各种断言方法和注解来编写测试用例,并能够轻松地集成到构建工具中。比较简单易用,支持各种断言风格,如JUnit4和JUnit5版本。它也支持参数化测试和扩展,使得测试用例编写更加灵活和强大。
- TestNG是另一个流行的Java测试框架,支持功能测试、端到端测试和集成测试。它提供了丰富的功能,例如测试组、参数化测试、依赖测试等。相较于JUnit,TestNG提供更多的测试组织和配置选项,具有更强大的参数化功能和测试报告输出,使得测试结果更加易于理解和分析。
- Mockito是一个Java的mocking框架,用于模拟和注入对象以简化单元测试。Mockito可以帮助创建模拟对象、设置行为和验证方法调用,以提高单元测试效率。Mockito具有简单灵活的API,可以与JUnit等测试框架很好地集成,帮助开发人员编写干净、高效的单元测试。
当然写单元测试也是一个苦活累活,idea提供了一个Squaretest插件可以帮我们生成单元测试代码,大家也可以尝试一下,超级好用。
最后再介绍一个小报童哈
主要内容如下,提供至少100篇专业建议,包含程序员的技术发展、晋升、平台选择、职场软技能和副业等各个领域。 购买加入陪伴群,得到与前大厂 P9 右军近距离接触的机会,还可以免费参加资深嘉宾的主题分享。 只需10元,现在购入私信我有大额优惠哈,我的微信号是:xiaoqiang666it