Logo
Section 1 / 5

什么是测试?

了解不同类型的测试、它们各自的目的以及代码覆盖率。

导入

什么是测试?

了解不同类型的测试、它们各自的目的以及代码覆盖率。

概述

软件测试是软件开发中的基础环节。其主要目的是检查我们的软件是否按照预期的方式运行。

以下是我们在程序中编写测试的一些原因:

  • 发现缺陷:我们编写测试以检测缺陷、错误、回归问题等。

  • 提升客户满意度:如果在项目交付客户之前修复了缺陷,客户会对我们的工作更加满意。

  • 文档作用:测试为代码的使用方式提供了一种不同的文档形式。

  • 提高效率:在开发早期就能获得反馈,从而让我们能更专注于最复杂的特性,提升开发效率。

  • 增强知识:通过编写测试,我们也能加深对编程语言的理解。

  • 安全性:经过充分测试的程序更具安全性,能够有效抵御潜在漏洞。

  • 节省成本:缺陷会带来成本,修复的缺陷越多,节省的成本就越多。

测试的历史

首先,我们澄清两个在测试中经常遇到的术语:生产代码 和 测试代码。生产代码是指将被部署并驱动程序运行的代码。而测试代码则是不会被推送到生产环境,但对最终产品仍至关重要的代码。

在编程的早期阶段,只有生产代码,程序员通过编写和部署代码来让程序运行。为了消除缺陷,他们只能依赖调试手段。如今,测试在软件开发中占据了核心地位。有时,测试甚至会在生产代码之前编写,例如在测试驱动开发(TDD)中。此外,测试已成为软件开发生命周期的重要组成部分,并融入持续集成/持续部署(CI/CD)流程,在开发初期就自动运行。

不同类型的测试

在编程中,存在多种不同类型的测试,各自具有独特的用途和差异:

  • 单元测试:仅测试代码中的单个组件,如函数或方法。其目的是验证代码的业务逻辑,与底层实现细节无关。这类测试开发和运行速度最快。

  • 集成测试:测试多个组件协同工作的行为,可能涉及外部资源,如数据库或网络服务。相比单元测试,它们编写和运行较慢,但能提供更有意义的结果。

  • 端到端测试(又称系统测试):覆盖从开始到结束的完整流程。以 Web API 为例,它会发起真实的 HTTP 请求,并断言由服务器返回的实际响应。这类测试编写和运行非常耗时,但反馈价值极高。

  • UI 测试:测试最终用户使用的界面。通常成本高且难以编写和维护。

在本课程的后续内容中,我们将只关注单元测试,因为它们是测试驱动开发的核心支柱。

代码覆盖率

代码覆盖率 是一种衡量指标,用于反映测试所覆盖的代码量。通常以百分比表示,是一种客观评估代码库中哪些部分已被充分测试,哪些还需要加强的方法。

我们不必追求项目中达到 100% 的代码覆盖率。相反,应重点关注那些包含关键业务逻辑的部分,这些部分更需要充分的测试覆盖。

不同类型的语句

我们在生产代码中编写的语句可以分为三类:语法行、逻辑行 和 分支行。下面通过一个例子来更好地理解这三类。

我们将使用经典的 FizzBuzz 问题作为示例,先回顾一下该函数的规则:FizzBuzz 函数接收一个数字,根据以下规则返回字符串:

  • 如果数字能被 3 整除,返回 Fizz。
  • 如果数字能被 5 整除,返回 Buzz。
  • 如果数字同时能被 3 和 5 整除,返回 FizzBuzz。
  • 其他情况,返回传入的原始数字。

以下是用 Go 语言实现的版本:

1func FizzBuzz(input int) string { 2 if input%5 == 0 && input%3 == 0 { 3 return "FizzBuzz" 4 } 5 if input%5 == 0 { 6 return "Buzz" 7 } 8 if input%3 == 0 { 9 return "Fizz" 10 } 11 return strconv.Itoa(input) 12}

现在我们来分析这段代码中的语句分类:

  • 语法行:不包含任何业务逻辑,主要用于声明函数、变量、结构体等。若存在错误,编译器会在编译阶段发现。本例中为 第 1、4、7、10 和 12 行。
  • 逻辑行:包含实际执行的代码语句,如 第 3、6、9 和 11 行。
  • 分支行:改变程序执行路径的语句,如 第 2、5 和 8 行。

代码覆盖率类型

代码覆盖率也有多种类型,主要分为以下几类:

  • 函数覆盖率:指测试中调用的函数数量占生产代码中总函数数量的比例。在 FizzBuzz 示例中,只有一个函数,因此只需一个测试调用该函数即可达到 100% 覆盖率。

  • 语句覆盖率:指被测试覆盖的语句数量占生产代码中总语句数量的比例。

  • 分支覆盖率:不逐行计算,而是将一组相关语句(即一个分支)作为一个整体进行测量。

下面我们观察每次调用 FizzBuzz 后覆盖率的变化。当调用 FizzBuzz(1) 时,被覆盖的代码如下所示:

1func FizzBuzz(input int) string { 2 if input%5 == 0 && input%3 == 0 { 3 return "FizzBuzz" 4 } 5 if input%5 == 0 { 6 return "Buzz" 7 } 8 if input%3 == 0 { 9 return "Fizz" 10 } 11 return strconv.Itoa(input) 12}

我们总结一下各覆盖率值:

调用函数覆盖率语句覆盖率分支覆盖率
FizzBuzz(1)100%50%25%

当调用 FizzBuzz(15) 后,被覆盖的代码如下:

1func FizzBuzz(input int) string { 2 if input%5 == 0 && input%3 == 0 { 3 return "FizzBuzz" 4 } 5 if input%5 == 0 { 6 return "Buzz" 7 } 8 if input%3 == 0 { 9 return "Fizz" 10 } 11 return strconv.Itoa(input) 12}

更新后的覆盖率数据如下:

调用函数覆盖率语句覆盖率分支覆盖率
FizzBuzz(1)100%50%25%
FizzBuzz(1, 15)100%66.67%50%

接下来执行最后两次调用:FizzBuzz(10) 与 FizzBuzz(6),最终结果如下:

调用函数覆盖率语句覆盖率分支覆盖率
FizzBuzz(1)100%50%25%
FizzBuzz(1, 15)100%66.67%50%
FizzBuzz(1, 15, 10)100%83.33%75%
FizzBuzz(1, 15, 10, 6)100%100%100%

现在,各种代码覆盖率的计算方式应该更加清晰了。代码覆盖率是我们构建可靠软件的重要工具。

Go:基础

测试驱动开发(简称 TDD)