软件单元测试入门

软件测试是保证软件质量的重要手段之一。现在,无论是大公司,还是中小型公司,都已经非常重视软件测试,越来越多的公司也开始建立独立的测试团队。 然而,在很多中小型公司,仍然面临一个窘境:虽然建立了专门测试团队对软件进行测试,但是软件在发布后仍然会出现问题。系统测试针对的是已经开发完成的软件系统,这时候整个软件系统已经很复杂,而系统的许多内部状态是不可见的。 在这种情况下,测试人员只能根据对需求的理解对软件进行测试, 很难进行深层次的测试。 目前解决的办法是推行单元测试,在推行的过程中却困难重重,其主要的原因如下:

  • 单元测试本身需要花费比较多的时间:单元测试要想达到比较好的效果,开发测试代码的时间与开发软件代码的时间相当。目前由于项目周期比较短,本来编码时间就已经不够了,在实际项目中就去掉了单元测试。其次,如果首次推行单元测试,由于相关理论和实践经验欠缺,单元测试将花费更多的时间,很多都处于摸索状态,所以单元测试的效果未必理想
  • 软件需求经常变化,导致代码经常需要重构:当代码重构之后,之前编写的测试代码往往不能再使用,必须重新编写测试代码。在这种情况下, 单元测试反而成了无用功。
  • 单元测试对软件设计要求较高:很多开发者在拿到需求后直接开始编写代码,而没有对软件进行精心的设计。在这种情况下代码的耦合性太高,界面与逻辑耦合在一起,逻辑与硬件控制代码耦合在一起,单元测试无法开展。
  • 没有单元测试的理论基础以及实践经验:在进行单元测试时不知道如何开始。不清楚如何设计测试用例,也不清楚如何编写测试代码

1. 软件测试的定义

《GB/T15532 计算机软件测试规范》 中对软件测试给出了定义:软件测试的目的是验证软件是否满足软件开发合同或项目开发计划、系统/子系统设计文档、软件需求规格说明、软件设计说明和软件产品说明等规定的软件质量要求;通过测试发现软件缺陷;为软件产品的质量评价提供依据 。

1.1 软件测试的重要性

软件质量成本是为保证软件质量所进行的活动产生的成本以及因为质量问题给我们带来的损失。 质量成本可以包括预防成本、检测成本和失败成本三个部分

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3NY0HQGe-1605689104994)(1a8d2582fb8e4d4ea7b4d8d38daadb5a.png)]

  • 预防成本: 为了预防缺陷的产生所进行的一系列的活动产生的费用称为预防成本。需求评审、设计评审、代码评审、技术预研、过程改进、人员培养等等这些活动都是为了预防问题的产生,这部分成本属于预防成本的范围。
  • 检测成本: 为了发现以及修复缺陷进行的一系列的活动产生的费用称为检测成本。测试过程的人力成本、购买测试设备费用、破坏性测试中损坏的设备都属于检测成本。
  • 失败成本: 失败成本是未能及时发现缺陷造成的损失。由于设计缺陷导致制造过程中造成产品报废,客户使用过程中出现问题导致的返修、退货以及索赔,由于质量问题造成的企业形象的损失,在使用过程中造成的人员伤亡及财产损失,这些都属于失败成本。

通常情况下,预防成本和检测成本都是可以预知的,而失败成本却是无法估量的。在产品开发过程中,失败成本往往不是一种实实在在的成本,而是一种风险,这种风险也是最容易被忽略的。预防成本和检测成本是能够看到,能够计算的成本,当需要压缩成本时,预防成本和检测成本往往是被压缩的对象。然而,当我们在压缩预防成本和检测成本时,产品失败的风险却随之上升,当风险成为现实,才悔之晚矣 。

1.2 软件测试的分类

1.2.1 按不同阶段划分

按不同阶段划分,可以将软件测试分为单元测试、集成测试、确认测试、系统测试和验收测试。

  • 单元测试 :单元测试是针对软件设计的最小单位进行测试,这里的最小单位可以是模块,或面向对象编程中的类。目的是检查每个程序单元能否正确实现详细设计说明中的模块功能、性能、接口和设计约束等要求,发现各模块内部可能存在的各种错误 。
  • 集成测试: 集成测试又称为组装测试。在单元测试的基础上,将各个程序单元进行有序、递增的组合测试。目的是验证软件单元之间、软件单元和已集成的软件系统之间的接口关系,并验证已集成的软件系统是否符合设计要求。
  • 确认测试: 确认测试是对已完成集成的软件系统进行测试。目的是验证软件系统本身是否与需求规格说明书中的要求一致。
  • 系统测试 :系统测试是在真实或模拟系统运行的环境下对集成了硬件和软件的系统进行测试。目的是检验系统在真实工作环境下的运行情况,以验证完整的软硬件系统能否实现用户的实际需求。
  • 验收测试 :验收测试是按照项目任务书或合同、供需双方约定的验收依据文档对整个系统进行测试以确定系统是否达到验收标准。验收测试的结论作为需要方是否接受该软件的主要依据。

1.2.2 按是否需要了解内部结构划分

  • 黑盒测试:黑盒测试又称为数据驱动测试。在不了解软件的内部结构情况下,根据软件需求说明书中的要求设计测试用例,输入测试数据并验证输出结果,以验证软件表现是否与需求规格说明书中的要求一致。
  • 白盒测试:白盒测试又称为逻辑驱动测试。对软件的结果进行分析,并设计测试用例,对软件的结构和执行路径进行检查,以验证软件是否能够按照设计说明书中的描述正常执行。
  • 灰盒测试:灰盒测试也是一种数据驱动测试。与黑盒测试不同的是,根据需求规格说明书设计测试用例后,通过了解软件的内部结构补充测试用例,以提高测试的覆盖率。

1.2.3 按是否需要运行程序划分

  • 静态测试:静态测试是在不运行软件的情况下对软件进行测试。通过对程序代码和文档进行检查,
    以发现可能存在的错误。
  • 动态测试:动态测试是在运行软件的情况下对软件进行测试。通过输入数据并检查输出结果是否
    正确。

2. 单元测试概述

进行单元测试之前,开发者需要了解什么是单元测试,单元测试为什么会那么重要。当一个公司要推行单元测试时,大多数开发者内心是抗拒的。需求变化快,代码设计不好,不知道从哪里开始, 都可以成为不进行单元测试的理由。而实际上终极的原因只有一个,那就是开发者还不知道单元测试的好处。要想知道单元测试有什么好处,唯一的办法就是去尝试。

2.1 什么是单元测试

单元测试是针对软件设计的最小单位进行测试。单元测试的“单元”在《GB/T15532 计算机软件测试规范》中的解释为“可独立编译或汇编的程序模块”。在实际操作中,可以认为承担一个单一职责的功能模块可以称为一个单元。

  • 在 C++中,通常情况下一个类会承担一个单一的职责, 那么按类来划分单元是相对比较合理的。
  • 在 C 语言中, 通常情况下一个文件中的代码会承担单一的职责, 那么按文件来划分单元是相对比合理的。

当然这也不是绝对的,在实际测试过程中还可以根据实际情况进行调整。 读者只需要掌握单元划分的基本原则:一个单元不能承担太多的职责;一个单元不能依赖太多其他的单元 。

2.2 单元测试的重要性

在实际开发的过程中,即使没有出现很大的事故, 通常也会遇到以下的麻烦:

  • 在代码编写完成后,往往程序无法直接运行,或者是一运行就出错。 开发者需要对程序进行调试,由于这时软件已经很复杂,通常需要调试很长时间才能让程序运行起来。
  • 在经历了漫长的调试过程后, 程序终于能够运行起来。然而在提交到测试部进行测试时,发现大量缺陷。修改这些问题花费开发者很多时间,同时测试部同事也不得不花很多时间去验证这些缺陷。
  • 由于系统比较复杂,由于很多状态的不确定性,出现问题比较随机,测试部发现很多无法重现的缺陷,根本不知道如何修复,产品上线后总是担心这缺陷会再次出现。
  • 在修改缺陷的过程中,常常会产生新的缺陷。而测试人员往往不能有效的发现这些新产生的缺陷,如果要发现这些缺陷,必须对整个系统重新测试一遍,浪费大量的人力物力。
  • 没有单元测试要求,开发者对代码的设计较随意,给代码维护造成很大的麻烦,或者根据无法维护。所以大多数开发者在维护其他人的代码时都有想重新写的冲动。

进行单元测试看起来是方便了,又节约了时间,而实际上造成很多问题。接下来我们来看单元测试如何解决这些问题

  • 由于单元测试是编码过程中同步进行的,可以保证代码是随时可运行的状态,在代码编写完成后不需要调试就可以直接运行,或者只需要很少的时间进行调试。
  • 由于大部分缺陷在单元测试阶段已经被发现,在系统测试阶段发现的缺陷会大大的减少,测试效率提高的同时开发者花费在修复缺陷在的时间也相应的减少了。另外测试人员工作量减轻后会有更多的时间进行深入的测试,有助于发现更多深层次的缺陷。
  • 由于单元测试是针对各个具体的软件单元,状态的不确定性会大大减少,重现缺陷的机会会大大的提高, 开发者修复问题会更加容易。
  • 由于单元测试是针对各个具体的软件单元,一旦发现缺陷就可以确定是当前的软件单元有问题,这样缺陷就很容易定位。
  • 由于单元测试可以自动进行回归测试,当修改过程中产生新的缺陷时,能够立马被发现并得到有效的修复。
  • 由于大部分缺陷在单元测试中被发现,系统测试中发现的缺陷将大大减少。同时测试的不确定性也将大大的减少, 评估的测试时间会更加准确。
  • 单元测试对代码会有一定的要求,设计不好的代码无法开展单元测试,这样就会迫使开发者对代码进行更好的设计 。

2.3 单元测试如何做

2.3.1 加强需求分析

当需求变化较频繁时,将会给单元测试带来很大的困难。当软件需求变化时,软件的很多代码需要重写,这样一来之前编写的单元测试代码也就成了无用功。 长此以往,开发者就会对单元测试失去信心,认为单元测试就是在浪费时间。

弄清楚以下几个问题有助于开发者进行详细的需求分析:

  • 产品的目标用户是谁;
  • 目标用户会如何使用产品;
  • 产品为目标用户提供哪些具体的功能;
  • 针对每 一个具体的功能,输入、输出、使用流程分别是什么。

2.3.2 可测试性设计

不好的设计会给单元测试带来极大的困难。通常情况下, UI 和与硬件相关的部分代码是无法进行单元测试的,所以需要将这部分代码与处理逻辑完全分离开来。另外如果逻辑代码各模块之间耦合性太高,也会给单元测试带来极大的困难,所以开发者在设计时需要尽量的解耦。

2.3.3 测试代码随时与软件代码保持同步

在项目初期,单元测试确实能够给项目提供很大的帮助,但是在迭代过程中,单元测试的作用越来越小,以至于最后失去作用。究其原因,在项目迭代过程中,并没有同步的修改测试代码,以至于最后,单元测试无法使用。

在项目开发过程中, 需要保证单元测试代码的编写和软件的开发过程是同步进行的,当软件的代码发生改变时,单元测试代码也要对应的修改,保证单元测试代码与软件代码随时保持一致,这样才能够使单元测试起到实际的使用。

2.3.4 单元测试技术要求

对单元测试做出了如下要求:

  • 对软件设计文档规定的软件单元的功能、性能、接口等应逐项进行测试;
  • 每个软件特性应至少被一个正常测试用例和一个被认可以异常测试用例覆盖;
  • 测试用例的输入应至少包含有效等价类、无效等价类和边界数据值;
  • 在对软件进行动态测试之前,一般应对软件单元的源代码进行静态测试;
  • 语句覆盖率达到 100%;
  • 分支覆盖率达到 100%;
  • 对输出数据及其格式进行测试

3. 静态测试

静态测试是在不运行软件的情况下对软件的代码及文档进行检查。 动态测试能够发现问题,而静态测试更多的是发现一些潜在的风险。
静态测试可以从编码规则检查、代码结构分析和代码评审三个方面来进行。前两者可以由专业的工具来完成,后者则由人工来完成。 本章将简单介绍如何从这三个方面来实施静态测试。

3.1 静态测试概述

静态测试是在不运行软件的情况下对软件进行测试。通过对程序代码和文档进行检查,以发现可能存在的错误。 动态测试能发现很多问题,而在实际开发过程中,有许多问题是动态测试无法发现的 。

  • 命名不合理,模块划分不合理
  • 代码缩进不合理,代码注释不清晰,代码结构复杂
  • 使用不安全的宏,代码无法跨平台等等

这些问题不会直接导致产品失效,但会使得代码很难理解,很难维护。 同时这些问题在动态测试中基本无法被发现,只能靠静态测试来发现。 主要是从以下三个方向进行

  • 编码规则检查:将在编码过程中的一些注意事项形成规则并使用相关的工具进行检查
  • 代码结构分析:使用工具对代码结构进行分析,避免代码过于复杂;
  • 代码评审 :对代码进行阅读,以发现代码中的一些潜在的错误

3.2 编码规则检查

开发者在编写代码时,需要遵守一定的规则。首先需要遵守语法规则,不符合语法规则的代码无法被编译器识别, 那么编译器就会以编译错误的形式进行提示, 开发者只有修改了这些问题才能编译通过。 还有一类问题,不会导致编译器无法识别,但是会在程序运行过程中产生一些隐患,给代码的后续移植和维护带来困难,这一类错误在编译器中一般以警告的方式存在。

  • cpplint是Google开发的一个C++代码风格检查工具,如果是遵循google code style的,可以使用cpplint作为代码规范的一个检查工具。
  • Cppcheck是c/c++代码的静态分析工具。它提供了独特的代码分析来检测bug,并着重于检测未定义的行为和危险的编码结构。我们的目标是只检测代码中的真正错误(例如,只有很少的误报)。

3.3 代码结构分析

在编写代码时,要求要结构清晰、接口简单。如果代码结构过于复杂,会带来很多问题:代码很难被理解, 不方便编写测试用例, 容易隐藏错误, 出现问题难以定位, 修改代码容易产生新的 Bug 等等。因此, 需要有一些指标来评估代码的复杂度,以方便对过于复杂的代码进行重构。

代码的复杂度通常可通过以下几个指标来评估:

  • 总行数:包括注释以及空行在内的代码行数;
  • 语句数目:有效的语句行数,包括#include、 #define、 #undef 这三个预处理命令在内,括号不包含在内;
  • 分支语句比例:分支语句占总语句数目的比例;
  • 注释比例:注释占总行数的比例;
  • 函数数目:函数的数量;
  • 平均每个函数的语句数;
  • 函数圈复杂度;
  • 函数最大嵌套层数;
  • 类的数量;
  • 平均每个类的函数数量。

SourceMonitor 是 Campwood Software LLC 拥有版权的自由软件,非商业用途可免费使用。 SourceMonitor 有以下特点:

  • 支持对 C、 C++、 C#、 VB.NET、 java、 Delphi、 Visual Basic 以及 HTML 在内的多种语言的源代码的分析;
  • 效率高,每秒钟能够分析 10000 行以上的代码;
  • 可以修改各个度量指标的阈值。

3.4 代码评审

编码规则检查能够发现代码中与已有规则不符合的情况;而代码结构分析可以分析代码的复杂度,从而避免过于复杂的代码的产生。编码规则检查和代码结构分析在很大程度上提升了代码的质量,但也并不能发现所有的问题。要想进一步提升代码的质量, 还需要进行代码评审,其评审流程如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4w45P06P-1605689105001)(E:/share/2)]

  • 需求确认:审查的第一项内容是程序代码是否正确实现了需求文档中的要求,在这一个环节,针对需求文档中每一项要求,开发者需要对代码进行讲解,说明代码是如何实现这些需求的。在开发者讲解过程中,评审人员进行提问,以发现问题。

  • 设计确认:审查的第二项内容是程序是否与设计文档中的要求相符,如果与设计文档不符,要么是代码实现有问题,要么是设计文档本身有问题, 都需要进行相应的修改。 在开发者讲解过程中,评审人中进行提问,以发现问题

  • 代码规范 :在确保程序代码正确实现需求且与设计文档相符后,接下来就可以对照代码规范对代码进行评审了。

  • 讨论环节:在这一环节通常是提出一些问题,然后进行讨论。例如以下的问题就非常值得讨论 ,这一步可以在评审会议之前提出一些观点

    • 代码的效率足够高吗?
    • 代码的安全性足够高吗?
    • 代码方便后续维护吗?
    • 代码方便后续扩展吗?
    • 代码方便在其他项目中复用吗?
    • 代码是否考虑到了所有的异常情况?

3.5 如何进行代码评审

在实施代码评审的过程中, 我们遇到的比较多的是,什么时候开始评审,哪些代码需要评审?

什么时候评审

如果项目开发完成后再进行代码评审,由于代码量太多,不容易抓住重点,同时评审团队也容易疲劳,这样就很难达到预期的效果 。

建议的做法是从项目一开始编码就进行代码评审,每天下班前进行一次评审,针对当天编写或修改的代码。这种方式下每次评审的代码量不会很多,更容易达到效果;另外由于较早的开始进行评审,能够及时发现问题并进行修改,也能够更快的帮助开发者养成良好的编程习惯,尽可能的减少代码的返工率。

评审哪些内容

在进行代码评审的时候,功能代码需要进行代码走查和代码审查,而测试代码需要进行代码审查。

代码评审是项目重要的组成部分,但是执行起来确实比较困难,怎么能防止沦落成走形式呢?我觉得可以有以下几个方式提高效率。

  • 选用合适的工具
  • 形成代码审查清单:有了一个好的清单,除了可以提高你在代码审查过程中发现的缺陷个数,还可以帮助团队成员更好更快的进行代码审查。
  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值