40

[Jest]单元测试初学者指南 - Part1 - 函数测试

 5 years ago
source link: http://www.w3ctech.com/topic/2172?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

原文: Unit Testing Beginner's Guide - Part 1 - Testing Functions

作者:jstweetster

示例代码

你已经决定开始对代码进行单元测试,但是不知道从哪里开始或者围绕该代码的最佳实践是什么。

在这个系列中,我计划在从基本原理开始,并使用您可能直到现在才知道的高级技术,在单元测试的领域中引导你。

开始之前,你需要安装如下依赖:

  • NodeJS - 你也许已经安装啦
  • Jest - 这是我们将会使用的单元测试库

请注意下面事项:

  1. 确保你已经安装了NodeJS: node -v 。确保版本号 >= 6.x,如果不是请安装
  2. 创建名为 unit-testing-functions 目录
  3. 切换到该目录 cd unit-testing-functions 并且初始化该JavaScript项目: npm init--yes
  4. 现在在该目录你应该有一个 package.json 文件
  5. 安装Jest: nom i jest --save-dev
  6. 你现在可以验证Jest是否安装成功通过运行: ./node_modules/.bin/jest -v

一切准备就绪,我们开始进入单元测试啦。

我们将从简单的函数开始,并且,随着我们进行一系列的单元测试,我们将会继续探索更复杂的数据结构和设置。

我们将会测试的代码

让我们开始定义一个简单的函数:

unit-testing-functions 项目中创建一个 sum.js 文件。定义如下函数:

module.export = function sum (a, b) {
    return a + b;
}

这个函数我们将会进行测试。 单元测试背后的想法是提供尽可能多的输入类型,以涵盖所有条件分支。

现在,没有任何条件分支,但我们应该改变我们对函数的输入,以确保它继续正确运行,即使代码在将来被更改。

理解测试文件

每一个你写的代码文件都应该有一个对应的 Spec 文件,它通常在代码文件旁边。例如: touch sum.spec.js 或者手动创建。

在spec文件中,我们将会写测试方法

Jest和其他测试框架将测试组织到测试用例中,为了简单管理和记录,每个测试用例包含多个单独的测试。

让我们添加我们的第一个测试(在 sum.spec.js 中):

const sum = require('./sum.js');
describe('sum suite', function () {
  test('should add 2 positive numbers together and return the result', function () {
    expect(sum(1, 2)).toBe(3);
  });
});

如果这看起来令人生畏或不清楚,请不要担心,它会在少数情况下有意义。

那么,这里发生了什么?

const sum = require('./sum.js');

我们引入要测试的函数。我们使用 module.exports 从模块中暴露函数,并且使用 require 引入到要测试的文件中。这是因为Jest在能识别这些结构的NodeJS上运行我们的测试。

此代码不是在浏览器中运行,不使用像Webpack这样的模块打包器,但另一篇文章将会介绍。

接下来,我们定义了测试单元,它将包含所有有关 sum 函数的测试方法。

describe('sum suite', function () {
    // define here the individual tests
})

最后我们添加我们第一个测试(我们将会在上面的测试单元中添加更多的测试):

test('should add 2 positive numbers together and return the result', function () {
  expect(sum(1, 2)).toBe(3);
});

下面的代码可能也不是很清楚

expect(sum(1, 2)).toBe(3)

这是任何单元测试的构建块,被称作为“断言(assertion)”。断言基本上是一种表达对事物应该如何表现的期望的方式。在我们的示例中,我们期望调用 sum(1, 2) 应该返回的结果是 3

toBe 被称作为“匹配器(matcher)”。在 Jest 里有很多的匹配器,每一个匹配器都有助于验证一个特定的方面:比如测试对象是否相等等。

那么, expect 从哪里来的呢?我们也没有从任何地方导入进来。

事实证明,Jest作为全局变量,提供了 describetestexpect 和一些其他函数,因此你无需导入他们。你可以到 官方文档 查看完整列表

该到我们运行我们的第一个单元测试啦

你可以在我们的项目根目录中直接调用Jest来运行单元测试 ./node_modules/.bin/jest

更好,更跨平台的方式是定义一个NPM脚本来运行这些测试。例如,打开 package.json 文件并且编辑下面的部分:

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
 }

改为:

"scripts": {
    "test": "jest"
 }

运行 npm run test ,你将会看到成功的输出:

➜  unit-testing-functions npm run test

> [email protected] test /Users/zouyuwei/code/web/_SELF/unit-testing-functions
> jest

 PASS  __tests__/sum.spec.js
  sum suite
    ✓ should add 2 positive numbers together and return the result (8ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.74s
Ran all test suites.

很好,你的第一个测试通过了!

现在,快进几周或者几个月,假设你的同事正在研究 sum 函数并且决定更改它的实现方法如下所示:

module.exports = function sum(a, b) {
    return a - b;
}

请更改它(这仅仅只是为了演示)。现在你的同事在提交这些改变之前运行单元测试。输出将会如下所示:

➜  unit-testing-functions npm run test

> [email protected] test /Users/zouyuwei/code/web/_SELF/unit-testing-functions
> jest

 FAIL  __tests__/sum.spec.js
  sum suite
    ✕ should add 2 positive numbers together and return the result (30ms)

  ● sum suite › should add 2 positive numbers together and return the result

    expect(received).toBe(expected) // Object.is equality

    Expected: 3
    Received: -1

      2 | describe('sum suite', function () {
      3 |   test('should add 2 positive numbers together and return the result', function () {
    > 4 |     expect(sum(1, 2)).toBe(3);
        |                       ^
      5 |   });
      6 | });

      at Object.toBe (__tests__/sum.spec.js:4:23)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        3.303s
Ran all test suites.

通过查看上面的输出信息,能够很容易的得出结论:在 __tests__/sum.spec.js 文件的第四行出错了,如跟踪堆栈所示 at Object.toBe (__tests__/sum.spec.js:4:23) 。因此可以得出结论 expect(sum(1,2)).toBe(3); 这一行出错了。通过检查控制台的输出,我们能够看到,期望的值是 ‘3’,而接收的值是 ‘-1’。

因此,单元测试既是防止回归的一种方式,也是一种活文档。

最后,请更改 a - ba + b

扩大单元测试覆盖率

我们进行了第一次测试,并且它涵盖了sum函数中的所有分支,有许多场景我们还没有测试过。

考虑一下测试中的功能,不仅要考虑今天的实现,还要考虑它如何随着时间的推移而发展。我们希望捕获函数停止工作的情况,即使有人修改了它的实现,并添加了额外的检查和分支。

因此,让我们通过创建额外的单元测试来扩展测试范围。在 sum.spec.js 文件中添加如下代码:

const sum = require('../sum.js');
describe('sum suite', function () {
  test('should add 2 positive numbers together and return the result', function () {
    expect(sum(1, 2)).toBe(3);
  });

  test('Should add 2 negative numbers together and return the result', function() {
    expect(sum(-1, -2)).toBe(-3);
  });

  test('Should add 1 positive and 1 negative numbers together and return the result', function() {
    expect(sum(-1, 2)).toBe(1);
  });

  test('Should add 1 positive and 0 together and return the result', function() {
    expect(sum(0, 2)).toBe(2);
  });

  test('Should add 1 negative and 0 together and return the result', function() {
    expect(sum(0, -2)).toBe(-2);
  });
});

除了最初的测试用例之外,我们刚添加了4个测试用例。注意我们如何改变函数的输入以及我们如何尝试击中边缘情况(例如,通过添加0)。

运行单元测试,你将会看到:

➜  unit-testing-functions npm run test

> [email protected] test /Users/zouyuwei/code/web/_SELF/unit-testing-functions
> jest

 PASS  __tests__/sum.spec.js
  sum suite
    ✓ should add 2 positive numbers together and return the result (7ms)
    ✓ Should add 2 negative numbers together and return the result (1ms)
    ✓ Should add 1 positive and 1 negative numbers together and return the result (1ms)
    ✓ Should add 1 positive and 0 together and return the result (1ms)
    ✓ Should add 1 negative and 0 together and return the result

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        2.575s
Ran all test suites.

处理单元测试功能中的异常

虽然我们在扩展单元测试覆盖率方面做得很好,但测试可以为我们做更多的事情。

如果我们认为我们还没有涉及的其他方案真的很好,你能想出一些目前代码处理不当的方法吗?

如何传递除数字以外的输入?

填写如下测试用例:

test('Should raise an error if one of the inputs is not a number', function() {
        expect(() => sum('0', -2)).toThrowError('Both arguments must be numbers');
 });

首先,我们用一个匿名函数包装我们要测试的代码: () => sum('0', -2)

这是必需的,因为在测试一段代码时抛出的任何未捕获的异常都会触发测试失败。

在这个例子当中,当参数不是数据的时候,我们期望 sum 函数抛出异常,但是我们不希望这是被认为是测试失败的: 相反的,这是预期的行为,应该被认为是通过的测试用例。

因此,我们将其包装在一个匿名函数中,并引入一个新的匹配器: toThrowError

运行测试,观察如下:

FAIL  __tests__/sum.spec.js
  sum suite
    ✕ Should raise an error if one of the inputs is not a number (18ms)

  ● sum suite › Should raise an error if one of the inputs is not a number

    expect(function).toThrowError(string)

    Expected the function to throw an error matching:
      "Both arguments must be numbers"
    But it didn't throw anything.

      22 |
      23 |   test('Should raise an error if one of the inputs is not a number', function() {
    > 24 |     expect(() => sum("0", -2)).toThrowError('Both arguments must be numbers');
         |                                ^
      25 |   });
      26 | });

      at Object.toThrowError (__tests__/sum.spec.js:24:32)

此时要抵制修改测试代码的诱惑。

这个测试很清楚的说明了该函数实现上的问题:

  • 它期望函数返回 to throw an error matching:“Both arguments must be numbers 。实际上它没有抛出任何异常。
  • 要查看它正在讨论的函数以及用于调用它的参数,请遵循堆栈跟踪: at Object.toThrowError (__tests__/sum.spec.js:24:32) 。在指定的行和列上,您可以看到对应的断言: expect(() => sum("0", -2)).toThrowError('Both arguments must be numbers');

所以我们的单元测试刚刚发现了一个bug,该是修复的时候了!

更改 sum.js 的代码,以考虑错误的输入类型,并在这种情况下抛出适当的异常:

module.exports = function sum (a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new Error('Both arguments must be numbers');
  }
  return a + b;
}

再次运行测试脚本,观察所有的测试都通过了。好样的!

请注意:我们首先添加了一个单元测试,然后再进入并添加代码,这表明 sum 函数在某些条件下无法正常运行。

我们看到了测试FAILING,我们添加了修复bug的代码并观看了测试PASSING。

在开发新代码/修复现有代码时,您应始终遵循此过程。

提高生产力

到目前为止,您可能已经注意到,每次添加代码或更新单元测试时,我们都必须不断重新运行单元测试。

这很快就会变得烦人并妨碍实际的开发工作流程。幸运的是,大多数测试运行程序允许设置文件监视模式,当磁盘上的文件发生更改时,它会重新运行单元测试。

修改 package.json 文件,将 “script” 部分改为:

"script": {
    "test": "jest --watch"
}

运行单元测试: npm run test

观察现在测试运行器没有退出而是等待命令。

改变 sum.js 或者 sum.spec.js 文件,会看到测试正在重新运行。

单元测试函数 - 最好实践总结

.spec
test
test

这就是我们对单元测试的介绍。

YvY3yq2.jpg!web

扫码关注w3ctech微信公众号


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK