代码质量保障第2讲:单元测试 - 浅谈单元测试

简介: 代码质量保障第2讲:单元测试 - 浅谈单元测试

1、什么是单元测试?

来自百度百科。

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

2、为什么要写单元测试?

使用单元测试可以有效地降低程序出错的机率,提供准确的文档,并帮助我们改进设计方案等等。

以下列举了一些我为什么使用单元测试的好处:

  • 允许你对代码做出任何改变,因为你了解单元测试会在你的预期之中;
  • 单元测试可以有效地降低程序出现BUG的机率;
  • 帮助你更深入地理解代码–因为在写单元测试的时候,你需要明确程序所有的执行流程及对应的执行结果等等;
  • 允许在任何时候代码重构,而不必担心破坏现有的代码。这使得我们编写程序更灵活;
  • 确保你的代码的健壮性,因为所有的测试都是通过了的;
  • 文档记录。单元测试就是一种无价的文档,它是展示函数或类如何使用的最佳文档,这份文档是可编译、可运行的、并且它保持最新,永远与代码同步。
  • 具有回归性。自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地地快速运行测试,而不是将代码部署到设备之后,然后再手动地覆盖各种执行路径,这样的行为效率低下,浪费时间。

3、什么时候写单元测试?

写单元测试的时机不外乎三种情况:

  • 一是在具体实现代码之前,这是测试驱动开发(TDD)所提倡的;
  • 二是与具体实现代码同步进行。先写少量功能代码,紧接着写单元测试(重复这两个过程,直到完成功能代码开发)。其实这种方案跟第一种已经很接近,基本上功能代码开发完,单元测试也差不多完成了;
  • 三是编写完功能代码再写单元测试。我的实践经验告诉我,事后编写的单元测试“粒度”都比较粗。对同样的功能代码,采取前两种方案的结果可能是用10个“小”的单测来覆盖,每个单测比较简单易懂,可读性可维护性都比较好(重构时单测的改动不大);而第三种方案写的单测,往往是用1个“大”的单测来覆盖,这个单测逻辑就比较复杂,因为它要测的东西很多,可读性可维护性就比较差。
  • 目前采用的这种方案

建议:我个人是比较推荐单元测试与具体实现代码同步进行这个方案的。只有对需求有一定的理解后才能知道什么是代码的正确性,才能写出有效的单元测试来验证正确性,而能写出一些功能代码则说明对需求有一定理解了。

4、单元测试要写多细?

单元测试不是越多越好,而是越有效越好!进一步解读就是哪些代码需要有单元测试覆盖:

  • 逻辑复杂的
  • 容易出错的
  • 不易理解的,即使是自己过段时间也会遗忘的,看不懂自己的代码,单元测试代码有助于理解代码的功能和需求
  • 公共代码。比如自定义的所有http请求都会经过的拦截器;工具类等。
  • 核心业务代码。一个产品里最核心最有业务价值的代码应该要有较高的单元测试覆盖率。

5、有哪些单元测试相关的概念?

5.1、被测系统

被测系统(System under test, SUT)表示正在被测试的系统,目的是测试系统能否正确操作。根据测试类型的不同,SUT 指代的内容也不同,例如 SUT 可以是一个类甚至是一整个系统.

5.2、测试依赖组件(DOC)

被测系统所依赖的组件,例如进程 UserService 的单元测试时,UserService 会依赖 UserDao,因此 UserDao 就是 DOC.

5.3、测试替身(Test Double)

一个实际的系统会依赖多个外部对象, 但是在进行单元测试时,我们会用一些功能较为简单的并且其行为和实际对象类似的假对象来作为 SUT 的依赖对象,以此来降低单元测试的复杂性和可实现性。在这里,这些假对象就被称为 测试替身(Test Double)。测试替身有如下 5 种类型:

  • Test stub

为 SUT 提供数据的假对象,我们举一个例子来展示什么是 Test stub.

假设我们的一个模块需要从 HTTP 接口中获取商品价格数据, 这个获取数据的接口被封装为 getPrice 方法. 在对这个模块进行测试时, 我们显然不太可能专门开一个 HTTP 服务器来提供此接口, 而是提供一个带有 getPrice 方法的假对象, 从这个假对象中获取数据. 在这个例子中, 提供数据的假对象就叫做 Test stub.

  • Fake object

实现了简单功能的一个假对象。Fake object 和 Test stub 的主要区别就是 Test stub 侧重于用于提供数据的假对象,而 Fake object 没有这层含义.

使用 Fake object 的最主要的原因就是在测试时某些组件不可用或运行速度太慢,因而使用 Fake object 来代替它们.

  • Mock object

用于模拟实际的对象, 并且能够校验对这个 Mock object 的方法调用是否符合预期。

实际上,Mock object 是 Test stub 或 Fake object 一种,但是 Mock object 有 Test stub/Fake object 没有的特性,Mock object 可以很灵活地配置所调用的方法所产生的行为,并且它可以追踪方法调用,例如一个 Mock Object 方法调用时传递了哪些参数,方法调用了几次等。

  • Dummy object

在测试中并不使用的,但是为了测试代码能够正常编译/运行而添加的对象。例如我们调用一个 Test Double 对象的一个方法,这个方法需要传递几个参数,但是其中某个参数无论是什么值都不会影响测试的结果,那么这个参数就是一个 Dummy object。Dummy object 可以是一个空引用,一个空对象或者是一个常量等。

简单的说,Dummy object 就是那些没有使用到的,仅仅是为了填充参数列表的对象。

  • Test Spy

可以包装一个真实的 Java 对象,并返回一个包装后的新对象。若没有特别配置的话,对这个新对象的所有方法调用,都会委派给实际的 Java 对象。

mock 和 spy 的区别是:mock 是无中生有地生出一个完全虚拟的对象,它的所有方法都是虚拟的;而 spy 是在现有类的基础上包装了一个对象,即如果我们没有重写 spy 的方法,那么这些方法的实现其实都是调用的被包装的对象的方法。

5.4、Test fixture

所谓 test fixture,就是运行测试程序所需要的先决条件(precondition)。即对被测对象进行测试时所需要的一切东西(The test fixture is everything we need to have in place to exercise the SUT)。这个 东西 不单单指的是数据,同时包括对被测对象的配置,被测对象所需要的依赖对象等。JUnit4 之前是通过 setUp, TearDown 方法完成, 在 JUnit4这, 我们可以使用@Before 代替 setUp 方法, @After 代替 tearDown 方法。

注意,@Before 在每个测试方法运行前都会被调用,@After 在每个测试方法运行后都会被调用。

因为 @Before 和 @After 会在每个测试方法前后都会被调用,而有时我们仅仅需要在测试前进行一次初始化,这样的情况下,可以使用@BeforeClass 和@AfterClass 注解。

5.5、测试用例(Test case)

在 JUnit 3中, 测试方法都必须以 test 为前缀,且必须是 public void 的,JUnit 4之后,就没有这个限制了,只要在每个测试方法标注 @Test 注解,方法签名可以是任意的。

5.6、测试套件

通过 TestSuit 对象将多个测试用例组装成一个测试套件,测试套件批量运行。

通过@RunWith 和@SuteClass 两个注解,我们可以创建一个测试套件。通过@RunWith 指定一个特殊的运行器,即 Suite.class 套件运行器,并通过@SuiteClasses 注解,将需要进行测试的类列表作作为参数传入。

6、参考文章

相关文章
|
2天前
|
测试技术
测试基础 Junit单元测试框架
测试基础 Junit单元测试框架
10 2
测试基础 Junit单元测试框架
|
10天前
|
安全 测试技术 Go
Golang深入浅出之-Go语言单元测试与基准测试:testing包详解
【4月更文挑战第27天】Go语言的`testing`包是单元测试和基准测试的核心,简化了测试流程并鼓励编写高质量测试代码。本文介绍了测试文件命名规范、常用断言方法,以及如何进行基准测试。同时,讨论了测试中常见的问题,如状态干扰、并发同步、依赖外部服务和测试覆盖率低,并提出了相应的避免策略,包括使用`t.Cleanup`、`t.Parallel()`、模拟对象和检查覆盖率。良好的测试实践能提升代码质量和项目稳定性。
16 1
|
10天前
|
监控 JavaScript 前端开发
【TypeScript技术专栏】TypeScript的单元测试与集成测试
【4月更文挑战第30天】本文讨论了在TypeScript项目中实施单元测试和集成测试的重要性。单元测试专注于验证单个函数、类或模块的行为,而集成测试关注不同组件的协作。选用合适的测试框架(如Jest、Mocha),配置测试环境,编写测试用例,并利用模拟和存根进行隔离是关键。集成测试则涉及组件间的交互,需定义测试范围,设置测试数据并解决可能出现的集成问题。将这些测试整合到CI/CD流程中,能确保代码质量和快速响应变化。
|
13天前
|
Java 测试技术 Android开发
Java 测试和调试:提高代码质量的实用策略
【4月更文挑战第27天】测试和调试是软件开发中确保应用稳定、高效且可靠的关键步骤。对于 Java 开发者来说,掌握有效的测试和调试技巧可以大大提高代码质量和减少生产环境下的问题。
20 2
|
13天前
|
IDE 测试技术 持续交付
【专栏】利用Python自动化测试与单元测试框架提升代码质量与效率
【4月更文挑战第27天】本文探讨了Python自动化测试与单元测试框架在提升代码质量与效率中的作用。Selenium、Appium用于Web和移动应用自动化测试,pytest提供强大、易扩展的测试支持。unittest是Python标准的单元测试框架,支持结构化测试用例和丰富的断言。实践中,应制定测试计划,编写高质量测试用例,实行持续集成与测试,并充分利用测试报告。这些工具和策略能有效保障代码质量和提升开发效率。
|
15天前
|
测试技术 数据库 UED
【白盒测试】单元测试的理论基础及用例设计技术(6种)详解
【白盒测试】单元测试的理论基础及用例设计技术(6种)详解
11 1
|
17天前
|
资源调度 JavaScript 测试技术
Vue的集成测试:使用VueTestUtils进行单元测试的技术博文
【4月更文挑战第24天】本文介绍了如何使用VueTestUtils进行Vue.js项目的集成测试。首先,需安装VueTestUtils和vue-template-compiler。接着,展示了如何编写测试用例,包括使用`mount`和`shallowMount`方法挂载组件,以及通过`wrapper`操作和断言组件行为。文章还讨论了单元测试与集成测试的区别,并提到了模拟依赖、交互、组件状态管理和断言的策略。最后,强调了测试的可读性和可维护性对代码质量的重要性。通过VueTestUtils,开发者能更高效地进行Vue组件的测试。
|
18天前
|
资源调度 JavaScript 测试技术
单元测试:编写和运行Vue组件的单元测试
【4月更文挑战第23天】本文探讨了为Vue组件编写单元测试的重要性,以及如何设置测试环境、编写和运行测试。通过使用Jest或Mocha作为测试框架,结合Vue Test Utils,可以独立测试组件的功能,如渲染、事件处理和状态管理。编写测试用例时,应注意覆盖各种行为,并使用断言验证组件状态。运行测试并观察结果,确保测试独立性和高覆盖率。单元测试是保证代码质量和维护性的关键,应随着项目发展持续更新测试用例。
|
Java 测试技术
Java 中的单元测试和集成测试策略
【4月更文挑战第19天】本文探讨了Java开发中的单元测试和集成测试。单元测试专注于单一类或方法的功能验证,使用测试框架如JUnit,强调独立性、高覆盖率和及时更新测试用例。集成测试则验证模块间交互,通过逐步集成或模拟对象来检测系统整体功能。两者相辅相成,确保软件质量和降低修复成本。
|
24天前
|
测试技术 Python
Python 的自动化测试:什么是单元测试和集成测试?在 Python 中如何进行自动化测试?
【4月更文挑战第17天】本文介绍了软件测试中的单元测试和集成测试。单元测试针对单个函数或方法,确保其功能正确;集成测试则检验多个单元交互是否正常。Python 自带的 unittest 模块提供自动化测试框架,示例代码展示了如何创建测试类及测试方法,通过断言验证字符串方法的行为。
11 1