爱陪小樱桃

导航

 

标签(空格分隔): 单元测试


单元测试定义:

  • 单元测试是指,对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作,这里的最小可测试单元通常是指函数或者类
  • 单元测试通常由开发工程师完成,一般会伴随开发代码一起递交至代码库。单元测试属于最严格的软件测试手段,是最接近代码底层实现的验证手段,可以在软件开发的早期以最小的成本保证局部代码的质量。另外,单元测试都是以自动化的方式执行,所以在大量回归测试的场景下更能带来高收益。同时,你还会发现,单元测试的实施过程还可以帮助开发工程师改善代码的设计与实现,并能在单元测试代码里提供函数的使用示例,因为单元测试的具体表现形式就是对函数以各种不同输入参数组合进行调用,这些调用方法构成了函数的使用说明。

如何做好单元测试?

  • 要做好单元测试,你首先必须弄清楚单元测试的对象是代码,以及代码的基本特征和产生错误的原因,然后你必须掌握单元测试的基本方法和主要技术手段,比如什么是驱动代码、桩代码和 Mock 代码等

第一,代码的基本特征与产生错误的原因

  • 在开发的过程中,无论什么样的语言都会有一些控制流程分支,循环,函数等等,所有的代码其实都是对数据进行分析的,每次的判断都是一次的分类处理,嵌套的条件判断和循环执行也是如此;
    如果有任何的一个类遗留了,都是会产生漏测,产生缺陷,若果有任何一个分支错误,也会产生缺陷,如果分类正确也没有遗漏,分类的处理逻辑错误也是会产生缺陷的;
  • 可见要做到代码的功能逻辑正确,必须要做到分类正确并且完备无遗漏,同时每个分类的处理逻辑必须正确;
  • 在具体的工作中,开发为了实现功能通常会有以下的考虑过程:
    1.如果实现正确的功能逻辑,会有哪几种正常的输入;
    2.是否需要处理的多种边界输入
    3.各种潜在的非法输入处理
    其实这些都是我们测试的思想的等价类思想;

单元测试用例详细说明:

在实际工作中,你想做好单元测试,就必须对单元测试的用例设计有深入的理解;

  • 实际来讲就是一个“输入数据”和一个“输出数据”的比较过程,要在测试过程中,根据输入,推算出输出,对于单元测试来讲,测试用例的输入数据和预计输出数据,可能远比你想得到的要复杂的多;

单元数据的输入数据都有哪些种类?

  • 如果你想当然的认为只有被测试函数的输入参数是“输入数据”的话,那就大错特错了,这里我总结了几种“输入数据”,希望可以帮助你理解什么才是完整的单元测试“输入数据”:
    1.被测试函数的输入参数;
    2.被测试函数内部需要读取的全局静态变量;
    3.被测试函数内部需要读取的成员变量;
    4.函数内部调用子函数获得的数据;
    5.函数内部调用子函数改写的数据;
    6.嵌入式系统中,在中断调用时改写的数据;

预计输出:

  • 如果没有明确的预计输出,那么测试本身就失去了意义。同样地,“预计输出” 绝对不是只有函数返回值这么简单,还应该包括函数执行完成后所改写的所有数据。
    1.被测试函数的返回值;
    2.被测试函数的输出参数;
    3.被测试函数所改写的成员变量;
    4.被测试函数所改写的全局变量;
    5.被测试函数中进行的文件更新;
    6.被测试函数中进行的数据库更新;
    7.被测试函数中进行的消息队列更新;
    另外,对于预计输出值,你必须严格根据代码的功能逻辑来设定,而不能通过阅读代码来推算预期输出,否则就是“掩耳盗铃”了

驱动代码,桩代码和 Mock 代码

驱动代码,桩代码和Mock代码,是单元测试中最常出现的三个名词。驱动代码是用来调用被测函数的,而桩代码和Mock代码是用来代替被测函数调用的真实代码的。
image.png-100.8kB
1.驱动代码(Driver)指调用被测函数的代码,在单元测试过程中,驱动模块通常包括调用被测函数前的数据准备、调用被测函数以及验证相关结果三个步骤。驱动代码的结构,通常由单元测试的框架决定。
2.桩代码(Stub)是用来代替真实代码的临时代码。 比如,某个函数 A 的内部实现中调用了一个尚未实现的函数 B,为了对函数 A 的逻辑进行测试,那么就需要模拟一个函数 B,这个模拟的函数 B 的实现就是所谓的桩代码。
例如:

image.png-45.2kB
3.被测函数 A 内部调用了函数 B在单元测试阶段,由于函数 B 尚未实现,但是为了不影响对函数 A 自身实现逻辑的测试,你可以用一个假的函数 B 来代替真实的函数 B,那么这个假的函数 B 就是桩函数。为了实现函数 A 的全路径覆盖,你需要控制不同的测试用例中函数 B 的返回值,那么桩函数 B 的伪代码就应该是这个样子的:
当执行第一个测试用例的时候,桩函数 B 应该返回 true,而当执行第二个测试用例的时候,桩函数 B 应该返回 false。
image.png-44.2kB

  • 桩代码的应用首先起到了隔离和补齐的作用,使被测代码能够独立编译、链接,并独立运行。同时,桩代码还具有控制被测函数执行路径的作用。

编写桩代码通常需要遵守以下三个原则:

1.桩函数要具有与原函数完全相同的原形,仅仅是内部实现不同,这样测试代码才能正确链接到桩函数;
2.用于实现隔离和补齐的桩函数比较简单,只需保持原函数的声明,加一个空的实现,目的是通过编译链接;
3.实现控制功能的桩函数是应用最广泛的,要根据测试用例的需要,输出合适的数据作为被测函数的内部输入。

  • Mock 代码和桩代码非常类似,都是用来代替真实代码的临时代码,起到隔离和补齐的作用。但是很多人,甚至是具有多年单元测试经验的开发工程师,也很难说清这二者的区别。在我看来,Mock 代码和桩代码的本质区别是:测试期待结果的验证(Assert and Expectiation)。
    1.对于 Mock 代码来说,我们的关注点是 Mock 方法有没有被调用,以什么样的参数被调用,被调用的次数,以及多个 Mock 函数的先后调用顺序。所以,在使用 Mock 代码的测试中,对于结果的验证(也就是 assert),通常出现在 Mock 函数中。
    2.对于桩代码来说,我们的关注点是利用 Stub 来控制被测函数的执行路径,不会去关注 Stub 是否被调用以及怎么样被调用。所以,你在使用 Stub 的测试中,对于结果的验证(也就是 assert),通常出现在驱动代码中。

如何开展单元测试?

1.并不是所有的代码都要进行单元测试,通常只有底层模块或者核心模块的测试中才会采用单元测试
2.为了能够衡量单元测试的代码覆盖率,通常你还需要引入计算代码覆盖率的工具。不同的语言会有不同的代码覆盖率统计工具,比如 Java 的 JaCoCo,JavaScript 的 Istanbul。
3.最后你需要把单元测试执行、代码覆盖率统计和持续集成流水线做集成,以确保每次代码递交,都会自动触发单元测试,并在单元测试执行过程中自动统计代码覆盖率,最后以“单元测试通过率”和“代码覆盖率”为标准来决定本次代码递交是否能够被接受;

如果你有开发背景,那么入门单元测试是比较容易的。但真正在项目中全面推行单元测试时,你会发现还有一些困难需要克服

1.紧密耦合的代码难以隔离;
2.隔离后编译链接运行困难;
3.代码本身的可测试性较差,通常代码的可测试性和代码规模成正比;
4.无法通过桩代码直接模拟系统底层函数的调用;
5.代码覆盖率越往后越难提高。

posted on 2020-03-28 14:33  cherry小樱桃  阅读(232)  评论(0编辑  收藏  举报