掌握⽩盒测试和⿊盒测试的常⻅⽅法, 并能够进⾏优缺点⽐较
能解释并区别⽩盒测试三种不同的⽅法: 语句覆盖、分⽀覆盖和路径覆盖
给出⼀个场景, 判断应该使⽤哪种测试⽅法, 如何去写(*)
- 给出功能需求, 则要求写功能测试⽤例
- 给出设计图, 则要求写集成测试⽤例, Stub and Driver
- 给出⽅法的描述, 则要求写单元测试⽤例, Mock Object
- JUnit基本使⽤⽅法
黑盒测试
基于规格的技术. 把测试对象看做一个黑盒, 完全基于输入和输出数据判定测试对象的正确性. 输入和输出数据根据测试对象的规格说明设计.
方法:
- 等价类划分: 把所有可能的输入数据划分成若干子集, 从每一个子集中选取少数具有代表性的数据作为测试用例. 该子集合中各个输入数据对于揭露程序中的错误都是等效的, 测试某等价类的代表值就等于对这一类其他值的测试.
- 有效等价类: 对于程序的规格说明来说是合理的, 有意义的输入数据构成的集合, 用于验证程序是否实现了规格说明中规定的功能和性能.
- 无效等价类: 对于不合理的数据验证实现的可靠性.

- 边界值分析: 对等价类划分方法的补充. 经验表明错误最容易发生在各等价类的边界上, 而不是等价类内部.

- 决策表
- 为复杂逻辑判断设计测试用例, 由条件声明, 行动声明, 规则选项和行动选项四个象限组成.

- 状态转换
- 专门针对复杂测试对象, 这类对象对输入数据的反应是多样的, 且依赖于自身的状态. 通常先要为测试对象建立状态图, 描述测试对象的状态集合, 确认集合和输入导致的状态转换集合.
白盒测试
基于代码的技术. 将测试对象看做透明的, 不关心规格, 而是根据对象内部的程序结构设计测试用例.
方法:

- 语句覆盖: 确保被测试对象的每一行程序代码都至少被执行一次, 与条件覆盖和路径覆盖相比较弱.

- 条件覆盖: 确保程序中每个判断的每个结果都至少满足一次, 比语句覆盖强, 但是仍然不能保证覆盖所有的执行路径.

- 路径覆盖: 标准是确保程序中每条独立的执行路径都至少执行一次.


不同的白盒测试方法实际上可以看作是不同的测试等级, 依据选定的等级来拟定测试数据以满足等级要求.
类比编译原理: 流不敏感/流敏感/路径敏感分析.
黑盒测试 (Black-box testing) 和白盒测试 (White-box testing) 是软件测试中两种最基本且常用的方法. 它们从不同的角度来验证软件的正确性和质量.
黑盒测试 (Black-box Testing)
定义:
黑盒测试, 又称功能测试、数据驱动测试或基于规格说明的测试. 它将待测试的软件视为一个“黑盒子”, 测试人员无需了解其内部代码结构、实现细节和编程语言. 测试人员只关注软件的输入、输出以及外部功能表现, 依据用户需求、功能规格书等文档来设计测试用例, 验证软件是否按照预期功能运行.
优点:
- 从用户角度出发: 模拟最终用户的实际使用场景, 更容易发现用户可能会遇到的问题, 确保用户体验.
- 无需代码知识: 测试人员不需要了解程序内部代码和实现细节, 降低了测试人员的门槛, 非开发人员也能胜任.
- 独立性高: 测试与软件的内部实现无关, 减少了开发人员的偏见, 可以进行更公正的测试.
- 易于自动化: 许多功能性的测试场景可以通过自动化工具实现.
- 覆盖面广: 可以对软件的系统、功能、接口、性能、安全性、兼容性、可用性等方面进行全面的检测.
缺点:
- 测试覆盖率有限: 无法确保所有代码路径都被覆盖到, 可能遗漏一些深层的逻辑错误或隐藏的缺陷.
- 难以定位问题: 当发现问题时, 由于不了解内部实现, 很难直接定位到具体的代码行或模块, 排查问题可能需要更多时间.
- 测试用例设计挑战: 如何设计全面且有效的测试用例来覆盖所有可能的输入和输出情况是一个挑战, 尤其是对于复杂的系统.
- 无法发现遗漏的功能: 黑盒测试是基于需求规格进行的, 如果需求本身就有遗漏, 黑盒测试就无法发现这些未实现的功能.
白盒测试 (White-box Testing)
定义:
白盒测试, 又称结构测试、逻辑驱动测试或透明盒测试. 它将待测试的软件视为一个“透明的盒子”, 测试人员需要深入了解程序的内部结构、代码、算法、数据流、控制流、条件分支、循环结构以及异常处理机制等. 测试用例的设计是基于程序的内部逻辑结构, 旨在验证代码的正确性和执行路径的合理性, 确保所有内部组件都经过检查.
优点:
- 代码覆盖率高: 可以针对代码的每一行、每一个分支、每一个路径进行测试, 确保所有内部逻辑都被验证到, 发现深层缺陷.
- 有助于代码优化: 通过测试可以发现代码中冗余、低效或存在问题的部分, 有助于代码重构和优化.
- 利于问题定位: 由于了解代码内部, 当发现问题时, 可以快速定位到具体的代码段, 提高排查效率.
- 发现内部逻辑错误: 能够发现因程序内部逻辑错误、死循环、不正确的条件判断等引起的缺陷.
- 适用于单元测试: 在开发阶段进行, 可以有效提高代码质量.
缺点:
- 需要专业的编程知识: 测试人员必须具备深厚的编程知识和对被测代码的深入理解, 测试门槛高.
- 测试成本高: 测试用例的设计和执行通常需要投入更多的时间和资源, 尤其是对于大型复杂系统.
- 无法发现外部接口问题: 侧重于内部实现, 可能无法发现与其他系统或外部接口之间的集成问题.
- 可能受开发人员思维限制: 测试人员容易受到开发人员思维定势的影响, 可能忽略一些用户角度的问题.
- 不适用于非功能性测试: 不适用于性能、可用性等非功能性测试.
比较
特征 | 黑盒测试 (Black-box Testing) | 白盒测试 (White-box Testing) |
关注点 | 软件功能、外部行为、用户需求 | 软件内部逻辑结构、代码、算法 |
测试人员知识 | 无需了解代码, 关注功能和用户体验 | 需具备编程知识, 了解代码和内部实现 |
测试阶段 | 主要用于集成测试、系统测试、验收测试 | 主要用于单元测试、集成测试早期阶段 |
测试依据 | 需求规格说明书、用户手册、功能设计文档 | 源代码、详细设计文档、程序流程图 |
测试覆盖 | 功能覆盖、需求覆盖 | 代码覆盖(语句覆盖、分支覆盖、路径覆盖等) |
发现缺陷类型 | 功能错误、界面错误、性能问题、兼容性问题、可用性问题 | 逻辑错误、路径错误、数据流错误、死循环、代码冗余 |
优缺点 | 优点: 简单易行, 从用户角度出发, 独立性高, 易于自动化.
缺点: 覆盖率有限, 难以定位问题, 无法发现遗漏功能. | 优点: 覆盖率高, 能发现深层缺陷, 有助于代码优化, 易于定位问题.
缺点: 门槛高, 成本高, 无法发现外部接口问题, 可能受开发人员思维限制. |
总结
黑盒测试和白盒测试是软件测试中不可或缺的两种方法. 它们各有侧重, 互为补充.
- 黑盒测试侧重于从用户的角度验证软件的功能和外部行为, 确保软件符合用户需求.
- 白盒测试侧重于从开发者的角度验证软件的内部逻辑和代码质量, 确保程序运行正确.
在实际的软件开发过程中, 通常会结合使用黑盒测试和白盒测试, 以及灰盒测试(介于黑盒和白盒之间, 测试人员对系统内部有部分了解), 以达到更全面的测试覆盖, 提高软件质量. 例如, 在单元测试阶段主要使用白盒测试, 而在集成测试和系统测试阶段则更多地采用黑盒测试. 通过这种多维度的测试方法, 可以最大限度地发现和修复软件缺陷, 从而交付高质量的JUnit软件产品.
JUnit简介
JUnit是一个流行的开源测试框架, 专门用于Java编程语言. 它主要用于编写和运行单元测试. 单元测试是对应用程序的最小可测试部分(通常是一个方法或一个类)进行测试, 以验证它们是否按预期工作.
JUnit 的主要特点和目的:
- 自动化测试: 允许开发者编写可重复执行的测试代码, 自动化地检查程序的功能.
- 快速反馈: 在开发早期就能发现代码中的错误和缺陷, 从而减少后期修复的成本.
- 改进代码质量: 通过编写测试, 可以更好地理解代码的功能和边界情况, 从而促进编写更清晰、更健壮的代码.
- 支持回归测试: 当代码修改后, 可以运行已有的测试, 确保修改没有引入新的问题或破坏原有功能.
- 行为驱动开发 (BDD) 的基础: 许多 BDD 框架(如 Cucumber)都基于 JUnit.
JUnit 的基础用法:
要使用 JUnit, 你通常需要以下几个步骤:
- 添加 JUnit 依赖: XMLGradle
如果你使用 Maven 或 Gradle, 需要在 pom.xml (Maven) 或 build.gradle (Gradle) 中添加 JUnit 的依赖.
Maven 示例:
Gradle 示例:
- 创建测试类:
通常, 测试类与被测试的类在同一个包下, 但放在不同的源文件夹中(例如, src/main/java 对应 src/test/java). 测试类的命名习惯是 被测试类名Test, 例如, 如果你有一个 Calculator 类, 那么测试类可以命名为 CalculatorTest.
- 编写测试方法:
- 被
@Test
注解标记. public
类型.void
返回值.- 没有参数.
- Arrange (准备): 初始化被测试的对象和数据.
- Act (执行): 调用被测试的方法.
- Assert (断言): 使用 JUnit 提供的断言方法来验证结果是否符合预期.
assertEquals(expected, actual)
: 判断两个值是否相等.assertTrue(condition)
: 判断条件是否为真.assertFalse(condition)
: 判断条件是否为假.assertNull(object)
: 判断对象是否为 null.assertNotNull(object)
: 判断对象是否不为 null.assertThrows(exceptionType, executable)
: 判断是否抛出了特定的异常.
在测试类中, 每个测试方法都代表一个独立的测试用例. 测试方法必须满足以下条件:
在测试方法中, 你通常会执行以下操作:
常用的断言方法:
示例:
假设我们有一个简单的
Calculator
类: 现在, 我们来编写
Calculator
类的 JUnit 测试: 运行测试:
- 在 IDE 中: 大多数现代 Java IDE (如 IntelliJ IDEA, Eclipse) 都内置了对 JUnit 的支持. 你可以直接右键点击测试类或测试方法, 选择“Run 'CalculatorTest'” 或 “Run 'testAdd()'”.
- 使用 Maven/Gradle: 在项目根目录下, 运行 Maven 命令
mvn test
或 Gradle 命令gradle test
. 它们会自动发现并运行所有的测试.
JUnit 的高级特性(简要提及):
- 参数化测试 (
@ParameterizedTest
): 允许使用不同的参数多次运行同一个测试方法.
- 嵌套测试 (
@Nested
): 组织和分组相关的测试.
- 动态测试 (
@TestFactory
): 在运行时动态生成测试.
- 测试生命周期回调 (
@BeforeAll
,@AfterAll
): 在所有测试方法之前/之后执行一次.
- 标签和过滤 (
@Tag
): 对测试进行分类和选择性运行.
- 超时测试 (
@Timeout
): 限制测试方法的执行时间.
- 禁用测试 (
@Disabled
): 临时禁用某个测试或测试类.
通过这些基础知识, 你就可以开始使用 JUnit 来编写和运行你的第一个单元测试了!