提交 6ad7abe6 编写于 作者: W wizardforcel

2021-10-01 21:52:11

上级 37f77bcc
# 从需求到测试用例
*Program testing can be used to show the presence of bugs, but never to show their absence!* *- Edsger Dijkstra*
程序测试可用于显示错误的存在,但绝不能显示错误的不存在!
——埃兹格尔·迪克斯特拉
本章提供了一个知识库,旨在帮助软件工程师编写有意义的测试用例。该过程的起点是理解被测试系统的需求。没有这些信息,设计或实施有价值的测试是不可行的。之后,在测试的实际编码之前可能会执行一些操作,即测试规划和测试设计。一旦我们开始测试编码过程,我们需要记住一套正确编写代码的原则,同时还要避免一套反模式和不良气味。本章以以下章节的形式提供了所有这些信息:
* **需求的重要性**:本节概述了软件开发过程,首先陈述了软件系统需要涵盖的一些需求,然后是几个阶段,通常包括分析、设计、实现和测试。
* **测试计划**:在软件项目开始时可以生成名为*测试计划*的文档。本节根据 IEEE 829 测试文件标准审查测试计划的结构。正如我们将发现的,测试计划的完整陈述是一个非常细粒度的过程,特别推荐用于大型项目,其中团队之间的沟通是项目成功的关键方面。
* **测试设计:**在开始测试编码之前,考虑这些测试的蓝图始终是一个很好的实践。在本节中,我们将回顾正确设计测试所需考虑的主要方面。我们将重点放在测试数据(预期结果)上,这些数据为测试断言提供了依据。在这方面,我们回顾了一些黑盒数据生成技术(等价划分和边界分析)和白盒(测试覆盖率)。
* **测试设计**在开始测试编码之前,考虑这些测试的蓝图始终是一个很好的实践。在本节中,我们将回顾正确设计测试所需考虑的主要方面。我们将重点放在测试数据(预期结果)上,这些数据为测试断言提供了依据。在这方面,我们回顾了一些黑盒数据生成技术(等价划分和边界分析)和白盒(测试覆盖率)。
* **软件测试原则**:本节提供了一套最佳实践,可以帮助我们编写测试。
* **测试反模式**:最后,还回顾了另一面:编写测试用例时需要避免的模式和代码气味是什么。
# 需求的重要性
软件系统是为了满足一组消费者(最终用户或客户)的某种需求而构建的。理解这些需求是软件工程中最具挑战性的问题之一,因为消费者需求模糊不清(特别是在项目的早期阶段)是很常见的。此外,在整个项目生命周期中,这些需求可能会发生深刻的变化,这一点也很常见。著名软件工程师、计算机科学家弗雷德·布鲁克斯(Fred Brooks)在其开创性著作*神话中的人类月(*1975*】*中定义了这个问题:
软件系统是为了满足一组消费者(最终用户或客户)的某种需求而构建的。理解这些需求是软件工程中最具挑战性的问题之一,因为消费者需求模糊不清(特别是在项目的早期阶段)是很常见的。此外,在整个项目生命周期中,这些需求可能会发生深刻的变化,这一点也很常见。著名软件工程师、计算机科学家弗雷德·布鲁克斯(Fred Brooks)在其开创性著作《人月神话》(1975)中定义了这个问题:
*The hardest single part of building a software system is deciding precisely what to build. No other part of the conceptual work is as difficult as establishing the detailed technical requirements … No other part of the work so cripples the resulting system if done wrong. No other part is as difficult to rectify later.*
构建软件系统最困难的部分是准确决定要构建的内容。 概念性工作的任何其他部分都没有建立详细的技术要求那么困难……如果做错了,工作的其他部分不会如此削弱最终的系统。 没有其他部分在以后更难纠正。
在任何情况下,消费者的需求都是任何软件项目的试金石。根据这些需求,可以出现一系列功能。我们将特性定义为软件系统功能的高级描述。从每个特性中,应导出一个或多个需求(功能性和非功能性)。需求是软件的一切真实性,以满足消费者的期望。场景(现实生活中的示例而不是抽象描述)对于向需求描述中添加细节非常有用。软件系统的需求组和/或特性列表通常称为规范。
......@@ -22,11 +24,11 @@
建模并非总是在所有软件项目中进行。例如,敏捷方法论更多地基于草图原理,而不是形式化建模策略。
启发后,应在**分析**阶段细化需求。在这一阶段,对所述需求进行分析,以解决不完整、模棱两可的矛盾问题。因此,在这个阶段,它可能会继续建模,例如使用尚未链接到任何特定技术的高级类图。一旦分析清楚了(即系统的*什么*,我们需要了解*如何*实现它。此阶段称为**设计**。在设计阶段,应制定项目指南。为此,软件系统的体系结构通常是从需求中派生出来的。同样,建模技术被广泛应用于设计的不同方面。此时可以使用一组 UML 图,包括结构图(组件、部署、对象、包和概要图)和行为图(活动、通信、序列或状态图)。从设计上可以开始实际的**实现**(即编码)。
启发后,应在**分析**阶段细化需求。在这一阶段,对所述需求进行分析,以解决不完整、模棱两可的矛盾问题。因此,在这个阶段,它可能会继续建模,例如使用尚未链接到任何特定技术的高级类图。一旦分析清楚了(即系统的*什么*,我们需要了解*如何*实现它。此阶段称为**设计**。在设计阶段,应制定项目指南。为此,软件系统的体系结构通常是从需求中派生出来的。同样,建模技术被广泛应用于设计的不同方面。此时可以使用一组 UML 图,包括结构图(组件、部署、对象、包和概要图)和行为图(活动、通信、序列或状态图)。从设计上可以开始实际的**实现**(即编码)。
根据不同的因素,在设计阶段进行的建模量会有很大差异,包括生产软件的公司的类型和规模(跨国公司、中小企业、政府等)、开发过程(瀑布式、螺旋式、原型、敏捷等)、项目类型(企业、开源等)、软件类型(定制软件、现成商业软件等),甚至是相关人员的背景(经验、职业等)总之,设计需要被理解为参与项目的软件工程师不同角色之间的沟通方式。通常,项目越大,就越需要基于不同建模图的细粒度设计。
关于**测试**,为了制定适当的测试计划(更多细节见下一节),我们同样需要使用需求获取数据,即需求和/或特性列表。换句话说,为了验证我们的系统,我们需要事先知道我们对它的期望。使用 Barry Boehm 提出的经典定义(参见第 1 章、“软件质量和 Java 测试回顾”),验证用于回答问题*我们是否构建了正确的产品?*要做到这一点,我们需要了解需求,或者至少了解期望的特性。除了验证之外,还需要进行一些验证(根据 Boehm 的说法:*我们是否在生产正确的产品?*。这是必要的,因为有时在指定的内容(特性和要求)和消费者的实际需求之间存在差距。因此,验证是一种高级评估方法,要执行它,最终消费者可以参与其中(一旦部署软件系统,就要对其进行验证)。下图描述了所有这些想法:
关于**测试**,为了制定适当的测试计划(更多细节见下一节),我们同样需要使用需求获取数据,即需求和/或特性列表。换句话说,为了验证我们的系统,我们需要事先知道我们对它的期望。使用 Barry Boehm 提出的经典定义(参见第 1 章、“软件质量和 Java 测试回顾”),验证用于回答问题“我们是否构建了正确的产品?”要做到这一点,我们需要了解需求,或者至少了解期望的特性。除了验证之外,还需要进行一些验证(根据 Boehm 的说法:“我们是否在生产正确的产品?”。这是必要的,因为有时在指定的内容(特性和要求)和消费者的实际需求之间存在差距。因此,验证是一种高级评估方法,要执行它,最终消费者可以参与其中(一旦部署软件系统,就要对其进行验证)。下图描述了所有这些想法:
![](img/00136.jpeg)
......@@ -34,7 +36,7 @@
到目前为止,没有通用的工作流程来描述这些术语(通信、需求获取、分析、设计、实现/测试和部署)。在前面的图中,它遵循线性流程,然而,在实践中,它可以遵循迭代、进化或并行工作流。
为了说明软件工程中不同阶段(分析、设计、实现等)所涉及的潜在问题,有必要回顾一下经典漫画*项目到底是如何工作的?*这张照片的原始来源不详(有 20 世纪 60 年代的版本)。2007 年,[出现了一个名为*卡通工程*的网站](http://www.projectcartoon.com/),允许使用新场景定制原创卡通。下图是该网站提供的卡通 1.5 版:
为了说明软件工程中不同阶段(分析、设计、实现等)所涉及的潜在问题,有必要回顾一下经典漫画“项目到底是如何工作的?”这张照片的原始来源不详(有 20 世纪 60 年代的版本)。2007 年,[出现了一个名为*卡通工程*的网站](http://www.projectcartoon.com/),允许使用新场景定制原创卡通。下图是该网站提供的卡通 1.5 版:
![](img/00137.jpeg)
......@@ -89,7 +91,7 @@
等价分区(也称为等价类分区)是一种黑盒技术(即,它依赖于系统的需求),旨在减少应针对 SUT 执行的测试数量。1978 年,Glenford Myers 首次将该技术定义为:
*一种将程序的输入域划分为有限数量的类【集合】的技术,然后识别一组最小的精心挑选的测试用例来表示这些类。*
一种将程序的输入域划分为有限数量的类【集合】的技术,然后识别一组最小的精心挑选的测试用例来表示这些类。”
换句话说,等价划分提供了一个标准来回答问题*我们需要多少测试**?*这个想法是将所有可能的输入测试数据(通常是大量的组合)划分为一组值,我们假设 SUT 以相同的方式处理这些值。我们称这些值集为等价类。其思想是测试等价类内的一个有代表性的值被认为是足够的,因为假定所有的值都以相同的方式被 SUT 处理。
......@@ -106,7 +108,7 @@
任何程序员都知道,错误通常出现在等价类的边界上(例如,数组的初始值、给定范围的最大值等等)。边界值分析是一种通过查看测试输入的边界来补充等价划分的方法。1981 年,国家标准与技术研究所(NIST)将其定义为:
*一种选择技术,其中选择的测试数据位于输入域[或输出范围]类、数据结构和程序参数的“边界”上。*
“一种选择技术,其中选择的测试数据位于类、数据结构和程序参数的输入或输出范围“边界”上。”
总之,为了在我们的测试中应用边界值分析,我们需要在等价类的边界中准确地评估 SUT。因此,通常使用这种方法导出两个测试用例:等价类的上边界和下边界。
......@@ -130,16 +132,16 @@ Eclipse4.7 中 EclEmma 的测试覆盖率(氧气)
穷举测试是一种测试方法的名称,它使用所有可能的测试输入组合来验证软件系统。这种方法仅适用于可能操作和允许数据数量非常有限的微型软件系统或组件。在大多数软件系统中,验证每个可能的排列和输入组合是不可行的,因此穷举测试只是一种理论方法。
因此,据说软件系统中不存在缺陷是无法证明的。这是由计算机科学先驱 Edsger W.Dijkstra 所说的(见本章开头的引文)。因此,测试充其量只是抽样,必须在任何软件项目中进行,以降低系统故障的风险(参见第 1 章、*软件质量和 Java 测试回顾、*回顾软件缺陷分类法)。因为我们不能测试所有的东西,所以我们需要正确地测试。在本节中,我们将回顾一组编写高效测试用例的最佳实践,即:
因此,据说软件系统中不存在缺陷是无法证明的。这是由计算机科学先驱 Edsger W.Dijkstra 所说的(见本章开头的引文)。因此,测试充其量只是抽样,必须在任何软件项目中进行,以降低系统故障的风险(参见第 1 章、“软件质量和 Java 测试回顾”、回顾软件缺陷分类法)。因为我们不能测试所有的东西,所以我们需要正确地测试。在本节中,我们将回顾一组编写高效测试用例的最佳实践,即:
* **测试应该简单**:编写测试的软件工程师(打电话给他或她的测试人员、程序员、开发人员或其他人)应该避免尝试测试他或她的程序。关于测试,问题*的正确答案是谁在看看守员?*应该是无名小卒。我们的测试逻辑应该足够简单,以避免任何类型的元测试,因为这将导致任何逻辑的递归问题。间接地说,如果我们保持测试简单,我们还可以获得另一个理想的特性:测试将易于维护。
* **测试应该简单**:编写测试的软件工程师(打电话给他或她的测试人员、程序员、开发人员或其他人)应该避免尝试测试他或她的程序。关于测试,问题“是谁在看看守员?”的正确答案应该是无名小卒。我们的测试逻辑应该足够简单,以避免任何类型的元测试,因为这将导致任何逻辑的递归问题。间接地说,如果我们保持测试简单,我们还可以获得另一个理想的特性:测试将易于维护。
* **不要实现简单测试**:一件事是进行简单测试,另一件非常不同的事情是实现伪代码,比如 getter 或 setter。正如前面介绍的,测试充其量只是抽样,我们不能浪费宝贵的时间来评估代码库的这类部分。
* **易读**:第一步是为我们的测试方法提供一个有意义的名称。此外,由于 JUnit 5`@DisplayName`注释,我们可以提供丰富的文本描述,它在没有 Java 命名约束的情况下定义了测试的目标。
* **单一责任原则**:这是计算机编程的一般原则,规定每个类都应该对单一功能负责。它与内聚度密切相关。这一原则在编写测试代码时非常重要:单个测试只应引用给定的系统需求。
* **测试数据是关键**:如前一节所述,SUT 的预期结果是测试的核心部分。正确管理这些数据对于创建有效的测试至关重要。幸运的是,JUnit 5 提供了一个丰富的工具箱来处理测试数据(参见第 4 章中的*参数化测试*,“使用高级 JUnit 特性简化测试”。
* **测试数据是关键**:如前一节所述,SUT 的预期结果是测试的核心部分。正确管理这些数据对于创建有效的测试至关重要。幸运的是,JUnit 5 提供了一个丰富的工具箱来处理测试数据(参见第 4 章“使用高级 JUnit 特性简化测试”中的“参数化测试”。
* **单元测试应该执行得非常快**:对于单元测试的持续时间,一个普遍接受的经验法则是一个单元测试最多只能持续一秒钟。为了实现这一目标,还需要单元测试正确地隔离 SUT,正确地加倍其文档。
* **测试必须是可重复的**:缺陷应根据需要重复多次,以便开发人员找到缺陷的原因。这是理论,但不幸的是,这并不总是适用的。例如,在多线程 SUT(实时或服务器端软件系统)中,可能会出现争用情况。在这些情况下,可能会遇到非确定性缺陷(通常称为*海森堡*)。
* **我们应该测试积极和消极场景**:这意味着我们需要使用输入条件编写测试,以评估预期结果,但我们还需要验证程序不应该做什么。除了满足其要求外,还必须对程序进行测试,以避免不必要的副作用。
* **我们应该测试正面和负面场景**:这意味着我们需要使用输入条件编写测试,以评估预期结果,但我们还需要验证程序不应该做什么。除了满足其要求外,还必须对程序进行测试,以避免不必要的副作用。
* **测试不能仅仅为了覆盖而进行**:仅仅因为代码的所有部分都被一些测试所触及,我们无法保证这些部分已经过彻底的测试。为了做到这一点,测试必须从降低风险的角度进行分析。
# 测试心理学
......@@ -157,29 +159,29 @@ Eclipse4.7 中 EclEmma 的测试覆盖率(氧气)
* **二等公民**:测试代码中含有大量重复代码,维护困难。
* **搭便车**(也称为*背驮*):不编写新方法来验证另一个特性/需求,而是在现有测试中添加新断言。
* **快乐路径**:只验证预期结果,不测试边界和异常。
* **当地英雄**:依赖于特定当地环境的测试。这个反模式可以用短语*来概括,它在我的机器中工作。*
* **局部英雄**:依赖于特定当地环境的测试。这个反模式可以用短语“它在我的机器中工作”来概括。
* **隐藏依赖项**:在测试运行之前,需要在某处填充一些现有数据的测试。
* **链组**:必须按一定顺序运行的测试,例如,将 SUT 更改为下一个 SUT 预期的状态。
* **嘲弄**:一种单元测试,它包含太多的测试双倍,以至于 SUT 根本没有被测试,而不是从测试双倍返回数据。
* **静默捕捉器**:即使实际发生意外异常也能通过的测试。
* **检查员**:违反封装的测试,SUT 中的任何重构都需要在测试中反映这些更改。
* **滑稽**:一种单元测试,它包含太多的测试替身,以至于 SUT 根本没有被测试,而不是从测试替身返回数据。
* **静默捕**:即使实际发生意外异常也能通过的测试。
* **窥探者**:违反封装的测试,SUT 中的任何重构都需要在测试中反映这些更改。
* **过度设置**:需要大量设置才能开始练习阶段的测试。
* **肛门探头**:必须使用不健康的方式来执行任务的测试,例如使用反射读取私有字段。
* **没有名称的测试**:测试方法名称,没有关于测试内容的明确指示器(例如,bug 跟踪工具中的标识符)。
* **慢行程**:持续数秒的单元测试。
* **无名测试**:测试方法名称,没有关于测试内容的明确指示器(例如,bug 跟踪工具中的标识符)。
* **呆呆兽**:持续数秒的单元测试。
* **闪烁测试**:在适当的测试中包含竞争条件的测试,使其不时失败。
* **观望**:需要等待一定时间(例如`Thread.sleep()`)才能验证某些预期行为的测试。
* **不适当共享夹具**:使用测试夹具而不需要设置/拆卸的测试。
* **巨人**:一个包含大量测试方法(上帝对象)的测试类。
* **湿地板**:一种创建持久数据但在完成时未清理的测试。
* **布谷鸟**:一种单元测试,在实际测试之前建立某种固定装置,但测试不知何故丢弃了固定装置。
* **秘密捕获者**:一种不做任何断言的测试,依赖于要抛出的异常并由测试框架报告为失败。
* **秘密捕**:一种不做任何断言的测试,依赖于要抛出的异常并由测试框架报告为失败。
* **环境破坏**:要求使用给定环境变量(例如,允许同时执行的自由端口号)的测试。
* **Doppelganger**:将被测代码的一部分复制到一个新类中,以使测试可见。
* **母鸡**:一种性能超过测试需要的夹具。
* **全方位测试**:不应违反单一责任原则的测试。
* **线击手**:未对 SUT 进行任何实际验证的测试。
* **连体双胞胎**:称为*单元测试*的测试,但实际上是集成测试,因为 SUT 和 DOC 之间没有隔离。
* **线击手**:未对 SUT 进行任何实际验证的测试。
* **连体双胞胎**:称为*单元测试*,但实际上是集成测试的测试,因为 SUT 和 DOC 之间没有隔离。
* **撒谎者**:不测试本应测试内容的测试。
# 代码气味
......@@ -190,7 +192,7 @@ Eclipse4.7 中 EclEmma 的测试覆盖率(氧气)
* **重复代码**:克隆代码在软件中总是一个坏主意,因为它打破了原则**不要重复自己****干**。这个问题在测试中甚至是最糟糕的,因为测试逻辑必须非常清晰。
* **高复杂性**:太多的分支或循环可能被简化为更小的部分。
* **法**:长得太大的方法总是有问题的,当这种方法是一种测试时,它是一种非常糟糕的症状。
* **过长方法**:长得太大的方法总是有问题的,当这种方法是一种测试时,它是一种非常糟糕的症状。
* **不合适的命名约定**:变量、类、方法名称要简洁。使用很长的标识符,但也使用过短(或无意义)的标识符,这被认为是一种不好的味道。
# 总结
......@@ -199,4 +201,4 @@ Eclipse4.7 中 EclEmma 的测试覆盖率(氧气)
本章回顾了旨在创建有效测试用例的过程。这个过程包括需求分析、测试计划的定义、测试用例的设计,最后编写测试用例。我们应该意识到,尽管软件测试是一项技术任务,但它涉及到一些重要的人类心理因素。软件工程师和测试人员应该了解这些因素,以便遵循已知的最佳实践并避免常见错误。
在第 7 章*测试管理*中,我们将了解在一个活的软件项目中如何管理软件测试活动。为此,我们首先回顾在常见的软件开发过程中何时以及如何执行测试,如瀑布式、螺旋式、迭代式、螺旋式、敏捷式或测试驱动开发。然后,回顾了服务器端基础设施(如 Jenkins 或 Travis),其目的是在 JUnit 5 的上下文中自动化软件开发过程。最后,我们将学习如何使用所谓的问题跟踪系统和测试报告库跟踪 Jupiter 测试中发现的缺陷。
\ No newline at end of file
在第 7 章“测试管理”中,我们将了解在一个活的软件项目中如何管理软件测试活动。为此,我们首先回顾在常见的软件开发过程中何时以及如何执行测试,如瀑布式、螺旋式、迭代式、螺旋式、敏捷式或测试驱动开发。然后,回顾了服务器端基础设施(如 Jenkins 或 Travis),其目的是在 JUnit 5 的上下文中自动化软件开发过程。最后,我们将学习如何使用所谓的问题跟踪系统和测试报告库跟踪 Jupiter 测试中发现的缺陷。
\ No newline at end of file
......@@ -6,7 +6,7 @@
这是本书的最后一章,它的目的是指导如何理解软件测试活动在一个活的软件项目中何时以及如何进行管理。为此,本章分为以下几节:
* **软件开发过程**:在本节中,我们研究了以不同方法执行测试的时间:**行为驱动开发****BDD**)、**测试驱动开发****TDD**)、**测试先行开发****TFD**)和**测试最后开发****TLD**)。
* **软件开发过程**:在本节中,我们研究了以不同方法执行测试的时间:**行为驱动开发****BDD**)、**测试驱动开发****TDD**)、**测试先行开发****TFD**)和**测试后行开发****TLD**)。
* **持续集成****CI**):在本节中,我们将发现 CI 是一种软件开发实践,在这种实践中,构建、测试和集成的过程是不断进行的。此过程的常见触发因素通常是将新更改(补丁)提交到源代码存储库(例如 GitHub)。此外,在本节中,我们将学习如何扩展 CI,回顾连续交付和连续部署的概念。最后,我们介绍了当今最重要的两个构建服务器:Jenkins 和 Travis CI。
* **测试报告**:在本节中,我们将首先发现 xUnit 框架通常报告测试执行的 XML 格式。这种格式的问题在于它不是人类可读的。因此,有一些工具可以将 XML 转换为更友好的格式,通常是 HTML。我们回顾了两种选择:Maven Surefire 报告和诱惑。
* **缺陷跟踪系统**:在本节中,我们回顾了几个问题跟踪:JIRA、Bugzilla、Redmine、MantisBT 和 GitHub 问题。
......@@ -15,23 +15,23 @@
# 软件开发过程
在软件工程中,软件开发过程(也称为软件开发生命周期)是创建软件系统所需的活动、操作和任务的工作流的名称。正如第 6 章*从需求到测试用例*中介绍的,任何软件开发过程中通常的阶段都是:
在软件工程中,软件开发过程(也称为软件开发生命周期)是创建软件系统所需的活动、操作和任务的工作流的名称。正如第 6 章“从需求到测试用例”中介绍的,任何软件开发过程中通常的阶段都是:
* *什么*的定义:需求获取、分析和用例建模。
* *how*的定义:系统架构以及结构和行为图的建模。
* *如何*的定义:系统架构以及结构和行为图的建模。
* 实际的软件开发(编码)。
* 使软件可用的一组活动(发布、安装、激活等)。
在整个软件开发过程中设计和实施测试的时间会导致不同的测试方法,即(见列表后的图表):
* **行为驱动开发****(BDD)**:在分析阶段开始时,软件消费者(最终用户或客户)和一些开发团队(通常是项目负责人、经理或分析师)之间进行了对话。这些对话用于具体化场景(即,建立对系统功能的共同理解的具体示例)。这些示例构成了使用 Cucumber 等工具开发验收测试的基础(有关更多详细信息,请参阅第 5 章、“JUnit 5 与外部框架的集成”)。BDD 中验收测试的描述(例如,在 Cucumber 中使用小黄瓜)生成准确描述应用程序功能的自动化测试和文档。BDD 方法自然地与迭代或敏捷方法相一致,因为预先定义需求非常困难,并且随着团队对项目了解的深入,需求也会不断变化。
* **行为驱动开发****BDD**:在分析阶段开始时,软件消费者(最终用户或客户)和一些开发团队(通常是项目负责人、经理或分析师)之间进行了对话。这些对话用于具体化场景(即,建立对系统功能的共同理解的具体示例)。这些示例构成了使用 Cucumber 等工具开发验收测试的基础(有关更多详细信息,请参阅第 5 章、“JUnit 5 与外部框架的集成”)。BDD 中验收测试的描述(例如,在 Cucumber 中使用小黄瓜)生成准确描述应用程序功能的自动化测试和文档。BDD 方法自然地与迭代或敏捷方法相一致,因为预先定义需求非常困难,并且随着团队对项目了解的深入,需求也会不断变化。
随着 2001 年[敏捷宣言](http://agilemanifesto.org/)的诞生,*敏捷*一词开始普及。它由 17 位软件从业者(肯特·贝克、詹姆斯·格朗宁、罗伯特·C·马丁、迈克·比德尔、吉姆·海史密斯、史蒂夫·梅勒、阿里·范本内库姆、安德鲁·亨特、肯·施瓦伯、阿利斯泰尔·科伯恩、罗恩·杰弗里斯、杰夫·萨瑟兰、沃德·坎宁安、乔恩·克恩、戴夫·托马斯、马丁·福勒和布赖恩·马里克)编写,并包括 12 条原则的列表,用于指导迭代和以人为中心的软件开发过程。基于这些原则,出现了一些软件开发框架,如 SCRUM、看板或极限编程(XP)。
* **测试驱动开发****TDD**):TDD 是一种在实际软件设计之前设计和实现测试的方法。其思想是将在分析阶段获得的需求转换为特定的测试用例。然后,设计并实现了通过这些测试的软件。TDD 是 XP 方法的一部分。
* **测试先行开发****TFD**):在这种方法中,测试是在设计阶段之后,但在 SUT 的实际实施之前实施的。这样可以确保在实际实施之前正确理解软件单元。统一过程遵循这种方法,这是一种流行的迭代和增量软件开发过程。**Rational 统一过程****RUP**是统一过程的著名框架实现。除了 TFD,RUP 还支持其他方法,如 TDD 和 TLD。
* **测试先行开发****TFD**):在这种方法中,测试是在设计阶段之后,但在 SUT 的实际实施之前实施的。这样可以确保在实际实施之前正确理解软件单元。统一过程遵循这种方法,这是一种流行的迭代和增量软件开发过程。**理性统一过程****RUP**是统一过程的著名框架实现。除了 TFD,RUP 还支持其他方法,如 TDD 和 TLD。
* **测试上次开发****TLD**):在本方法中,测试的实施是在实际软件(SUT)实施之后进行的。这种测试方法遵循经典的软件开发过程,如瀑布式(顺序)、增量式(多瀑布)或螺旋式(面向风险的多瀑布)。
* **测试后行开发****TLD**):在本方法中,测试的实施是在实际软件(SUT)实施之后进行的。这种测试方法遵循经典的软件开发过程,如瀑布式(顺序)、增量式(多瀑布)或螺旋式(面向风险的多瀑布)。
![](img/00140.jpeg)
......@@ -49,7 +49,7 @@
CI 的概念于 1991 年由 Grady Booch(美国软件工程师,最著名的是与 Ivar Jacobson 和 James Rumbaugh 一起开发 UML)首次提出。**极限编程****XP**)方法学采用了这个术语,因此非常流行。根据 Martin Fowler,CI 的定义如下:
*Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily - leading to multiple integrations per day. Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible.*
*持续集成*是一种软件开发实践,团队成员经常集成他们的工作,通常每个人至少每天集成 - 导致每天进行多次集成。 每个集成都通过自动构建(包括测试)进行验证,以尽快检测集成错误。
在 CI 系统中,我们可以识别不同的部分。首先,我们需要一个源代码存储库,它是一个文件归档,用来存放软件项目的源代码,通常使用版本控制系统。如今,首选的版本控制系统是 Git(最初由 Linus Torvalds 开发),而不是旧的解决方案,如 CVS 或 SVN。在撰写本文时,领先的版本控制存储库是 [GitHub](https://github.com/),顾名思义是基于 Git 的。此外,还有其他替代方案,例如 [GitLab](https://gitlab.com)[Bitbucket](https://bitbucket.org/)[SourceForge](https://sourceforge.net/)。后者过去是主要的锻造厂,但现在使用较少。
......@@ -61,11 +61,11 @@ CI 的基本思想是,每次提交都应执行构建,并使用新的更改
持续集成过程
接近 CI 时,术语 DevOps 已获得了发展势头。DevOps 源于*开发**运营*,它是一个软件开发过程的名称,强调项目软件中不同团队之间的沟通和协作:开发(软件工程)、QA(**质量保证**和运营(基础设施)。术语 DevOps 也指工作岗位,通常负责设置,监控构建服务器的操作:
接近 CI 时,术语 DevOps 已获得了发展势头。DevOps 源于*开发**运维*,它是一个软件开发过程的名称,强调项目软件中不同团队之间的沟通和协作:开发(软件工程)、QA(**质量保证**)和运维(基础设施)。术语 DevOps 也指工作岗位,通常负责设置,监控构建服务器的操作:
![](img/00142.jpeg)
DevOps 介于开发、运和 QA 之间
DevOps 介于开发、运和 QA 之间
如下图所示,CI 的概念可以扩展到:
......@@ -127,7 +127,7 @@ pipeline {
[Travis CI](https://travis-ci.org/) 是一个分布式构建服务器,用于构建和测试托管在 GitHub 上的软件项目。Travis 免费支持开源项目。
Travis CI 的配置使用名为*.Travis.yaml*的文件完成。此文件的内容使用不同的关键字进行结构化,包括:
Travis CI 的配置使用名为`.Travis.yaml`的文件完成。此文件的内容使用不同的关键字进行结构化,包括:
* `language`:项目语言,即 java、node_js、ruby、python 或 php 等(完整列表见[这里](https://docs.travis-ci.com/user/languages/)
* `sudo`:设置是否需要超级用户权限的标志值(例如安装 Ubuntu 软件包)。
......@@ -176,7 +176,7 @@ Travis CI 提供了一个 web 仪表板,我们可以在其中使用 GitHub 帐
从最初的版本开始,JUnit 测试框架引入了一种 XML 文件格式来报告测试套件的执行情况。多年来,这种 XML 格式已经成为报告测试结果的*事实上的*标准,在 xUnit 家族中被广泛采用。
这些 XML 可以由不同的程序处理,以人性化的格式显示结果。例如,构建服务器就是这样做的。例如,Jenkins 实现了一个名为`JUnitResultArchiver`*的工具,该工具将作业测试执行产生的 XML 文件解析为 HTML。*
这些 XML 可以由不同的程序处理,以人性化的格式显示结果。例如,构建服务器就是这样做的。例如,Jenkins 实现了一个名为`JUnitResultArchiver`的工具,该工具将作业测试执行产生的 XML 文件解析为 HTML。
尽管这种 XML 格式已经变得非常普遍,但还没有通用的正式定义。JUnit 测试执行器(例如 Maven、Gradle 等)通常使用自己的 XSD(XML 模式定义)。例如,这个 [XML 报告](http://maven.apache.org/surefire/maven-surefire-plugin/)在 Maven 中的结构如下图所示。请注意,测试套件由一组属性和一组测试用例组成。每个测试用例都可以声明为失败(某个断言失败的测试)、跳过(忽略测试)和错误(带有意外异常的测试)。如果这些状态都没有出现在测试套件的主体中,那么测试将被解释为成功。最后,对于每个测试用例,XML 还存储标准输出(*系统输出*)和标准错误输出(*系统错误*):
......@@ -216,7 +216,7 @@ maven surefire 报表插件生成的 HTML 报表
[Allure](http://allure.qatools.ru/) 是一个轻量级的开源框架,用于为不同的编程语言生成测试报告,包括 Java、Python、JavaScript、Ruby、Groovy、PHP、.NET 和 Scala。一般来说,Allure 使用 XML 测试输出,并将其转换为富 HTML5 的报告。
诱惑为 JUnit5 项目提供支持。这可以使用 Maven 和 Gradle 来完成。关于 Maven,我们需要在`maven-surefire-plugin`中注册一个侦听器。这个监听器将是 JUnit5 类(位于库`io.qameta.allure:allure-junit5`中),它基本上是 JUnit5`TestExecutionListener`的一个实现。如第 2 章所述,*JUnit 5*的新增功能`TestExecutionListener`是 Launcher API 的一部分,用于接收有关测试执行的事件。总之,这个监听器允许诱惑编译测试信息,而测试信息是在 JUnit 平台中生成的。此信息由 Allure 存储为 JSON 文件。之后,我们可以使用插件`io.qameta.allure:allure-maven`从这些 JSON 文件生成 HTML5。这些命令是:
诱惑为 JUnit5 项目提供支持。这可以使用 Maven 和 Gradle 来完成。关于 Maven,我们需要在`maven-surefire-plugin`中注册一个侦听器。这个监听器将是 JUnit5 类(位于库`io.qameta.allure:allure-junit5`中),它基本上是 JUnit5`TestExecutionListener`的一个实现。如第 2 章“JUnit 5 的新增功能”所述,`TestExecutionListener`是 Launcher API 的一部分,用于接收有关测试执行的事件。总之,这个监听器允许诱惑编译测试信息,而测试信息是在 JUnit 平台中生成的。此信息由 Allure 存储为 JSON 文件。之后,我们可以使用插件`io.qameta.allure:allure-maven`从这些 JSON 文件生成 HTML5。这些命令是:
```java
mvn test
......@@ -325,7 +325,7 @@ JUnit5 项目中生成的诱惑报告
# 静力分析
这本书很快就要完成了,它的重点是软件测试。毫不奇怪,JUnit 是关于测试的。但正如我们在第一章、“软件质量和 Java 测试回顾”中所看到的,虽然软件测试是**验证&验证****V&V**中最常见的执行活动,但它并不是唯一的类型。另一组重要的活动是静态分析,其中没有执行软件测试。
这本书很快就要完成了,它的重点是软件测试。毫不奇怪,JUnit 是关于测试的。但正如我们在第一章、“软件质量和 Java 测试回顾”中所看到的,虽然软件测试是**验证&确认****V&V**中最常见的执行活动,但它并不是唯一的类型。另一组重要的活动是静态分析,其中没有执行软件测试。
有不同的活动可以归类为静态分析。其中,自动化软件分析是一种在所需工作方面相当便宜的替代方法,它可以帮助显著提高内部代码质量。在本章中,我们将回顾几种自动化软件分析工具,称为**linters**,即:
......@@ -376,22 +376,22 @@ SonarCloud 报告我的猫的应用率!
# 特点和要求
我们应用程序的历史从一个假想的人开始,他喜欢猫。此人拥有一个 clowder,他/她希望从外部世界获得关于他们的反馈。因此,此人(我们可以从现在开始联系他/她我们的*客户机*来实现满足他/她的需求的 web 应用程序。该应用程序的名称为*“为我的猫打分!”*。在与客户的对话中,我们得出了要开发的应用程序的以下功能列表:
我们应用程序的历史从一个假想的人开始,他喜欢猫。此人拥有一个 clowder,他/她希望从外部世界获得关于他们的反馈。因此,此人(我们可以从现在开始联系他/她我们的*客户机*来实现满足他/她的需求的 web 应用程序。该应用程序的名称为“为我的猫打分!”。在与客户的对话中,我们得出了要开发的应用程序的以下功能列表:
* **F1**:每个用户应通过观察猫的名字和图片对猫的列表进行评分。
* **F2**:费率应使用星型机制为每个用户执行一次(每猫从`0.5``5`星型),并且可以选择为每个猫添加评论。
* `F1`:每个用户应通过观察猫的名字和图片对猫的列表进行评分。
* `F2`:费率应使用星型机制为每个用户执行一次(每猫从`0.5``5`星型),并且可以选择为每个猫添加评论。
作为我们开发过程分析阶段的一部分,这些特性被细化为**功能需求****FR**)列表,如下所示:
* **FR1**:应用程序向最终用户呈现猫的列表(由名称和图片组成)。
* **FR2**:每只猫都可以单独评级。
* **FR3**:评级猫的范围是从`0.5``5`(星星)的区间。
* **FR4**:对于每猫的数字费率,用户可以选择添加一些注释。
* **FR5**:每个终端用户仅对每个 cat(评论和/或星级)进行一次评分。
* `FR1`:应用程序向最终用户呈现猫的列表(由名称和图片组成)。
* `FR2`:每只猫都可以单独评级。
* `FR3`:评级猫的范围是从`0.5``5`(星星)的区间。
* `FR4`:对于每猫的数字费率,用户可以选择添加一些注释。
* `FR5`:每个终端用户仅对每个 cat(评论和/或星级)进行一次评分。
# 设计
由于我们的应用程序非常简单,我们决定在这里停止分析阶段,而不将我们的需求建模为用例。相反,我们继续使用经典的三层模型(表示层、应用程序(或业务)逻辑层和数据层)对 web 应用程序进行高级体系结构设计。关于应用程序逻辑,如下图所示,需要两个组件。第一个,称为`CatService`负责需求列表中描述的所有评级操作。第二个名为`CookiesServices`的用于处理 HTTP Cookies,需要实现 FR5*:*
由于我们的应用程序非常简单,我们决定在这里停止分析阶段,而不将我们的需求建模为用例。相反,我们继续使用经典的三层模型(表示层、应用程序(或业务)逻辑层和数据层)对 web 应用程序进行高级体系结构设计。关于应用程序逻辑,如下图所示,需要两个组件。第一个,称为`CatService`负责需求列表中描述的所有评级操作。第二个名为`CookiesServices`的用于处理 HTTP Cookies,需要实现 FR5
![](img/00150.jpeg)
......@@ -420,7 +420,7 @@ SonarCloud 报告我的猫的应用率!
现在让我们关注这个应用程序的 JUnit5 测试。我们实现三种类型的测试:单元测试、集成测试和端到端测试。正如前面介绍的,对于单元测试,我们使用 Mockito 单独地执行 SUT。我们决定使用包含不同 JUnit5 测试的 Java 类对应用程序的两个主要组件(`CatService``CookiesServices`进行单元测试。
考虑第一个测试(称为`RateCatsTest`)。从代码中可以看出,在这个类中,我们将类`CatService`定义为 SUT(使用注释`@InjectMocks`),将类`CatRepository`(通过依赖项注入由`CatService`使用)定义为文档(使用注释`@Mock`)。此类的第一个测试(`testCorrectRangeOfStars`是参数化 JUnit 5 测试的一个示例。本测试的目的是评估`CatService`(方法`rateCate`中的费率方法)。为了选择此测试的测试数据(输入),我们遵循黑盒策略,因此我们使用需求定义的信息。具体而言,*FR3*说明了用于猫评级机制的恒星范围。根据边界分析方法,我们选择输入范围的边,即 0.5 和 5。第二个测试用例(`testCorrectRangeOfStars`)也测试了相同的方法(`rateCat`),但这一次测试评估了超出范围的输入执行 SUT 时的 SUT 响应(负面测试场景)。然后,在这个类中又执行了两个测试,这次的目的是评估*FR4*(也就是说,使用注释对 CAT 进行评分)。请注意,我们使用 JUnit 5`@Tag`注释来识别每个测试及其相应的需求:
考虑第一个测试(称为`RateCatsTest`)。从代码中可以看出,在这个类中,我们将类`CatService`定义为 SUT(使用注释`@InjectMocks`),将类`CatRepository`(通过依赖项注入由`CatService`使用)定义为文档(使用注释`@Mock`)。此类的第一个测试(`testCorrectRangeOfStars`是参数化 JUnit 5 测试的一个示例。本测试的目的是评估`CatService`(方法`rateCate`中的费率方法)。为了选择此测试的测试数据(输入),我们遵循黑盒策略,因此我们使用需求定义的信息。具体而言,`FR3`说明了用于猫评级机制的恒星范围。根据边界分析方法,我们选择输入范围的边,即 0.5 和 5。第二个测试用例(`testCorrectRangeOfStars`)也测试了相同的方法(`rateCat`),但这一次测试评估了超出范围的输入执行 SUT 时的 SUT 响应(负面测试场景)。然后,在这个类中又执行了两个测试,这次的目的是评估`FR4`(也就是说,使用注释对 CAT 进行评分)。请注意,我们使用 JUnit 5`@Tag`注释来识别每个测试及其相应的需求:
```java
package io.github.bonigarcia.test.unit;
......@@ -508,7 +508,7 @@ class RateCatsTest {
}
```
接下来,单元测试评估 cookies 服务(*FR5*。为了达到这个目的,下面的测试使用类`CookiesService`作为 SUT,这次我们将模拟标准 Java 对象,它处理 HTTP Cookies,即`javax.servlet.http.HttpServletResponse`。查看这个测试类的源代码,我们可以看到第一个测试方法(称为`testUpdateCookies`)使用了服务方法`updateCookies`,验证 cookie 的格式是否符合预期。接下来的两个测试(`testCheckCatInCookies``testCheckCatInEmptyCookies`使用肯定策略(即输入 cat 对应于 cookie 的格式)和否定策略(相反的情况)评估服务的方法`isCatInCookies`。最后,最后两个测试(`testUpdateOpinionsWithCookies``testUpdateOpinionsWithEmptyCookies`按照相同的方法使用 SUT 的`updateOpinionsWithCookiesValue`方法,即使用有效且空的 cookie 检查 SUT 的响应。所有这些测试都是按照白盒策略实施的,因为其测试数据和逻辑完全依赖于 SUT 的特定内部逻辑(在本例中,cookie 是如何格式化和管理的)。
接下来,单元测试评估 cookies 服务(`FR5`。为了达到这个目的,下面的测试使用类`CookiesService`作为 SUT,这次我们将模拟标准 Java 对象,它处理 HTTP Cookies,即`javax.servlet.http.HttpServletResponse`。查看这个测试类的源代码,我们可以看到第一个测试方法(称为`testUpdateCookies`)使用了服务方法`updateCookies`,验证 cookie 的格式是否符合预期。接下来的两个测试(`testCheckCatInCookies``testCheckCatInEmptyCookies`使用肯定策略(即输入 cat 对应于 cookie 的格式)和否定策略(相反的情况)评估服务的方法`isCatInCookies`。最后,最后两个测试(`testUpdateOpinionsWithCookies``testUpdateOpinionsWithEmptyCookies`按照相同的方法使用 SUT 的`updateOpinionsWithCookiesValue`方法,即使用有效且空的 cookie 检查 SUT 的响应。所有这些测试都是按照白盒策略实施的,因为其测试数据和逻辑完全依赖于 SUT 的特定内部逻辑(在本例中,cookie 是如何格式化和管理的)。
该测试不遵循纯白盒方法,因为其目标是在 SUT 内练习所有可能的路径。可以将其视为白盒,因为它的设计直接与实现相关联,而不是与需求相关联。
......@@ -601,7 +601,7 @@ class CookiesTest {
```java
package io.github.bonigarcia.test.integration;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*get*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
......@@ -629,7 +629,7 @@ class WebContextTest {
@Test
@DisplayName("Check home page (GET /)")
void testHomePage() throws Exception {
mockMvc.perform(*get*("/")).andExpect(status().isOk())
mockMvc.perform(get("/")).andExpect(status().isOk())
.andExpect(content().contentType("text/html;charset=UTF-8"));
}
......@@ -769,8 +769,8 @@ GitHub 为我的猫的应用率颁发徽章!
在本章中,我们回顾了关于测试活动的管理方面的几个问题。首先,我们了解到,根据测试方法,测试可以在软件开发过程(软件生命周期)的不同部分进行:BDD(在需求分析之前定义验收测试)、TDD(在系统设计之前定义测试)、TFD(在系统设计之后实施测试)和 TLD(测试在系统实施后实施)。
CI 是一种越来越多地用于软件开发的过程。它包括代码库的自动构建和测试。此过程通常由源代码存储库(如 GitHub、GitLab 或 Bitbucket)中的新提交触发。CI 扩展到连续交付(当发布到开发环境时)和连续部署(当连续部署到生产环境时)。我们回顾了当今最常用的两个构建服务器:Jenkins(*CI 即服务*和 Travis(内部部署)。
CI 是一种越来越多地用于软件开发的过程。它包括代码库的自动构建和测试。此过程通常由源代码存储库(如 GitHub、GitLab 或 Bitbucket)中的新提交触发。CI 扩展到连续交付(当发布到开发环境时)和连续部署(当连续部署到生产环境时)。我们回顾了当今最常用的两个构建服务器:Jenkins(*CI 即服务*和 Travis(内部部署)。
还有一些其他工具可用于改进测试管理,例如报告工具(如 Maven Surefire Report 或 Allure)或缺陷跟踪系统(如 JIRA、Bugzilla、Redmine、MantisBT 和 GitHub 问题)。自动化静态分析是对测试的一个很好的补充,例如,使用诸如 Checkstyle、FindBugs、PMD 或 SonarQube 之类的过梁,以及诸如 Collaborator、Crucible、Gerrit 和 GitHub 拉式请求审查之类的同行审查工具。
为了结束本书,本章的最后一节介绍了一个完整的 web 应用程序(名为*Rate my cat!*)及其相应的 JUnit 5 测试(单元测试、集成测试和端到端测试)。它包括一个 web 应用程序,该应用程序使用本书中介绍的不同技术开发和评估,即 Spring、Mockito、Selenium、Hamcrest、Travis CI、Codecov 和 SonarCloud。*
\ No newline at end of file
为了结束本书,本章的最后一节介绍了一个完整的 web 应用程序(名为`Rate my cat`)及其相应的 JUnit 5 测试(单元测试、集成测试和端到端测试)。它包括一个 web 应用程序,该应用程序使用本书中介绍的不同技术开发和评估,即 Spring、Mockito、Selenium、Hamcrest、Travis CI、Codecov 和 SonarCloud。*
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册