0


【JavaScripts从入门到入神】 Mocha 进行自动化测试|Polyfill 和转译器

在这里插入图片描述

🐧主页详情:Choice~的个人主页
📢作者简介:🏅物联网领域创作者🏅 and 🏅阿里专家博主🏅 and 🏅华为云享专家🏅
✍️人生格言:最慢的步伐不是跬步,而是徘徊;最快的脚步不是冲刺,而是坚持。
🧑‍💻人生目标:成为一名合格的程序员,做未完成的梦:实现财富自由。
🚩技术方向:NULL
👻如果觉得博主的文章还不错的话,请三连支持一下博主哦
💬给大家介绍一个我一直在用的求职刷题收割offe👉点击进入网站

文章目录

使用 Mocha 进行自动化测试

自动化测试将被用于进一步的任务中,并且还将被广泛应用在实际项目中。

我们为什么需要测试?

当我们在写一个函数时,我们通常可以想象出它应该做什么:哪些参数会给出哪些结果。

在开发期间,我们可以通过运行程序来检查它并将结果与预期进行比较。例如,我们可以在控制台中这么做。

如果出了问题 —— 那么我们会修复代码,然后再一次运行并检查结果 —— 直到它工作为止。

但这样的手动“重新运行”是不完美的。

当通过手动重新运行来测试代码时,很容易漏掉一些东西。

例如,我们要创建一个函数

f

。写一些代码,然后测试:

f(1)

可以执行,但是

f(2)

不执行。我们修复了一下代码,现在

f(2)

可以执行了。看起来已经搞定了?但是我们忘了重新测试

f(1)

。这样有可能会导致出现错误。

这是非常典型的。当我们在开发一些东西时,我们会保留很多可能需要的用例。但是不要想着程序员在每一次代码修改后都去检查所有的案例。所以这就很容易造成修复了一个问题却造成另一个问题的情况。

自动化测试意味着测试是独立于代码的。它们以各种方式运行我们的函数,并将结果与预期结果进行比较。

行为驱动开发(BDD)

我们来使用一种名为 行为驱动开发 或简言为 BDD 的技术。

BDD 包含了三部分内容:测试、文档和示例。

为了理解 BDD,我们将研究一个实际的开发案例。

开发 “pow”:规范

我们想要创建一个函数

pow(x, n)

来计算

x

n

次幂(

n

为整数)。我们假设

n≥0

这个任务只是一个例子:JavaScript 中有一个

**

运算符可以用于幂运算。但是在这里我们专注于可以应用于更复杂任务的开发流程上。

在创建函数

pow

的代码之前,我们可以想象函数应该做什么并且描述出来。

这样的描述被称作 规范(specification, spec),包含用例的描述以及针对它们的测试,如下所示:

describe("pow",function(){it("raises to n-th power",function(){
    assert.equal(pow(2,3),8);});});

正如你所看到的,一个规范包含三个主要的模块:

  • describe("title", function() { ... })表示我们正在描述的功能是什么。在我们的例子中我们正在描述函数 pow。用于组织“工人(workers)” —— it 代码块。
  • it("use case description", function() { ... })``````it 里面的描述部分,我们以一种 易于理解 的方式描述特定的用例,第二个参数是用于对其进行测试的函数。
  • assert.equal(value1, value2)``````it 块中的代码,如果实现是正确的,它应该在执行的时候不产生任何错误。assert.* 函数用于检查 pow 函数是否按照预期工作。在这里我们使用了其中之一 —— assert.equal,它会对参数进行比较,如果它们不相等则会抛出一个错误。这里它检查了 pow(2, 3) 的值是否等于 8。还有其他类型的比较和检查,我们将在后面介绍到。

规范可以被执行,它将运行在

it

块中指定的测试。我们稍后会看到。

开发流程

开发流程通常看起来像这样:

  1. 编写初始规范,测试最基本的功能。
  2. 创建一个最初始的实现。
  3. 检查它是否工作,我们运行测试框架 Mocha(很快会有更多细节)来运行测试。当功能未完成时,将显示错误。我们持续修正直到一切都能工作。
  4. 现在我们有一个带有测试的能工作的初步实现。
  5. 我们增加更多的用例到规范中,或许目前的程序实现还不支持。无法通过测试。
  6. 回到第 3 步,更新程序直到测试不会抛出错误。
  7. 重复第 3 步到第 6 步,直到功能完善。

如此来看,开发就是不断地 迭代。我们写规范,实现它,确保测试通过,然后写更多的测试,确保它们工作等等。最后,我们有了一个能工作的实现和针对它的测试。

让我们在我们的开发案例中看看这个开发流程吧。

在我们的案例中,第一步已经完成了:我们有一个针对

pow

的初始规范。因此让我们来实现它吧。但在此之前,让我们用一些 JavaScript 库来运行测试,就是看看测试是通过了还是失败了。

行为规范

在本教程中,我们将使用以下 JavaScript 库进行测试:

  • Mocha —— 核心框架:提供了包括通用型测试函数 describeit,以及用于运行测试的主函数。
  • Chai —— 提供很多断言(assertion)支持的库。它提供了很多不同的断言,现在我们只需要用 assert.equal
  • Sinon —— 用于监视函数、模拟内建函数和其他函数的库,我们在后面才会用到它。

这些库都既适用于浏览器端,也适用于服务器端。这里我们将使用浏览器端的变体。

包含这些框架和

pow

规范的完整的 HTML 页面:

<!DOCTYPE html><html><head><!-- add mocha css, to show results --><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css"><!-- add mocha framework code --><script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js"></script><script>
    mocha.setup('bdd');// minimal setup</script><!-- add chai --><script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></script><script>// chai has a lot of stuff, let's make assert globallet assert = chai.assert;</script></head><body><script>functionpow(x, n){/* function code is to be written, empty now */}</script><!-- the script withtests(describe, it...)--><script src="test.js"></script><!-- the element with id="mocha" will contain test results --><div id="mocha"></div><!-- run tests!--><script>
    mocha.run();</script></body></html>

该页面可分为五个部分:

  1. <head> —— 添加用于测试的第三方库和样式文件。
  2. <script> 包含测试函数,在我们的例子中 —— 和 pow 相关的代码。
  3. 测试代码 —— 在我们的案例中是名为 test.js 的脚本,它包含上面 describe("pow", ...) 的那些代码。
  4. HTML 元素 <div id="mocha"> 将被 Mocha 用来输出结果。
  5. 可以使用 mocha.run() 命令来开始测试。

结果:

到目前为止,测试失败了,出现了一个错误。这是合乎逻辑的:我们的

pow

是一个空函数,因此

pow(2,3)

返回了

undefined

而不是

8

未来,我们会注意到有更高级的测试工具,像是 karma 或其他的,使自动运行许多不同的测试变得更容易。

初始实现

为了可以通过测试,让我们写一个

pow

的简单实现:

functionpow(){return8;// :) 我们作弊啦!}

哇哦,现在它可以工作了。

image-20220819101731614

改进规范

我们所做的这些绝对是作弊。函数是不起作用的:尝试计算

pow(3,4)

的话就会得到一个不正确的结果,但是测试却通过了。

……但是这种情况却是在实际中相当典型例子。测试通过了,但是函数却是错误的。我们的规范是不完善的。我们需要给它添加更多的测试用例。

这里我们又添加了一个测试来检查

pow(3, 4) = 81

我们可以选择两种方式中的任意一种来组织测试代码:

  1. 第一种 —— 在同一个 it 中再添加一个 assertdescribe("pow",function(){it("raises to n-th power",function(){ assert.equal(pow(2,3),8); assert.equal(pow(3,4),81);});});
  2. 第二种 —— 写两个测试:describe("pow",function(){it("2 raised to power 3 is 8",function(){ assert.equal(pow(2,3),8);});it("3 raised to power 4 is 81",function(){ assert.equal(pow(3,4),81);});});

主要的区别是,当

assert

触发一个错误时,

it

代码块会立即终止。因此,在第一种方式中,如果第一个

assert

失败了,我们将永远不会看到第二个

assert

的结果。

保持测试之间独立,有助于我们获知代码中正在发生什么,因此第二种方式更好一点。

除此之外,还有一个规范值得遵循。

一个测试检查一个东西。

如果我们在看测试代码的时候,发现在其中有两个相互独立的检查 —— 最好将它拆分成两个更简单的检查。

因此让我们继续使用第二种方式。

结果:

image-20220819101750618

改进实现

让我们写一些更加实际的代码来通过测试吧:

functionpow(x, n){let result =1;for(let i =0; i < n; i++){
    result *= x;}return result;}

为了确保函数可以很好地工作,我们来使用更多值测试它吧。除了手动地编写

it

代码块,我们可以使用

for

循环来生成它们:

describe("pow",function(){functionmakeTest(x){let expected = x * x * x;it(`${x} in the power 3 is ${expected}`,function(){
      assert.equal(pow(x,3), expected);});}for(let x =1; x <=5; x++){makeTest(x);}});

结果:image-20220819101801706

嵌套描述

我们继续添加更多的测试。但在此之前,我们需要注意到辅助函数

makeTest

for

应该被组合到一起。我们在其他测试中不需要

makeTest

,只有在

for

循环中需要它:它们共同的任务就是检查

pow

是如何自乘至给定的幂次方。

使用嵌套的

describe

来进行分组:

describe("pow",function(){describe("raises x to power 3",function(){functionmakeTest(x){let expected = x * x * x;it(`${x} in the power 3 is ${expected}`,function(){
        assert.equal(pow(x,3), expected);});}for(let x =1; x <=5; x++){makeTest(x);}});// ……可以在这里写更多的测试代码,describe 和 it 都可以添加在这。});

嵌套的

describe

定义了一个新的 “subgroup” 测试。在输出中我们可以看到带有标题的缩进:image-20220819101811877

将来,我们可以在顶级域中使用

it

describe

的辅助函数添加更多的

it

describe

,它们不会看到

makeTest

ℹ️**

before/after

beforeEach/afterEach

**

我们可以设置

before/after

函数来在运行测试之前/之后执行。也可以使用

beforeEach/afterEach

函数来设置在执行 每一个

it

之前/之后执行。

例如:

describe("test",function(){before(()=>alert("Testing started – before all tests"));after(()=>alert("Testing finished – after all tests"));beforeEach(()=>alert("Before a test – enter a test"));afterEach(()=>alert("After a test – exit a test"));it('test 1',()=>alert(1));it('test 2',()=>alert(2));});

运行顺序将为:

Testing started – before all tests (before)
Before a test – enter a test (beforeEach)
1
After a test – exit a test   (afterEach)
Before a test – enter a test (beforeEach)
2
After a test – exit a test   (afterEach)
Testing finished – after all tests (after)

Open the example in the sandbox.

通常,

beforeEach/afterEach

before/after

被用于执行初始化,清零计数器或做一些介于每个测试(或测试组)之间的事情。

延伸规范

pow

的基础功能已经完成了。第一次迭代开发完成啦。当我们庆祝和喝完香槟之后,让我们继续改进它吧。

正如前面所说,函数

pow(x, n)

适用于正整数

n

JavaScript 函数通常会返回

NaN

以表示一个数学错误。接下来我们对无效的

n

值执行相同的操作。

让我们首先将这个行为加到规范中(!):

describe("pow",function(){// ...it("for negative n the result is NaN",function(){
    assert.isNaN(pow(2,-1));});it("for non-integer n the result is NaN",function(){
    assert.isNaN(pow(2,1.5));});});

新测试的结果:

image-20220819101916873

新加的测试失败了,因为我们的实现方式是不支持它们的。这就是 BDD 的做法:我们首先写一些暂时无法通过的测试,然后去实现它们。

ℹ️Other assertions

请注意断言语句

assert.isNaN

:它用来检查

NaN

在 Chai 中也有其他的断言,例如:

  • assert.equal(value1, value2) —— 检查相等 value1 == value2
  • assert.strictEqual(value1, value2) —— 检查严格相等 value1 === value2
  • assert.notEqualassert.notStrictEqual —— 执行和上面相反的检查。
  • assert.isTrue(value) —— 检查 value === true
  • assert.isFalse(value) —— 检查 value === false
  • ……完整的列表请见 docs

因此我们应该给

pow

再加几行:

functionpow(x, n){if(n <0)returnNaN;if(Math.round(n)!= n)returnNaN;let result =1;for(let i =0; i < n; i++){
    result *= x;}return result;}

现在它可以工作了,所有的测试也都通过了:

image-20220819101923907

Open the full final example in the sandbox.

总结

在 BDD 中,规范先行,实现在后。最后我们同时拥有了规范和代码。

规范有三种使用方式:

  1. 作为 测试 —— 保证代码正确工作。
  2. 作为 文档 —— describeit 的标题告诉我们函数做了什么。
  3. 作为 案例 —— 测试实际工作的例子展示了一个函数可以被怎样使用。

有了规范,我们可以安全地改进、修改甚至重写函数,并确保它仍然正确地工作。

这在一个函数会被用在多个地方的大型项目中尤其重要。当我们改变这样一个函数时,没有办法手动检查每个使用它们的地方是否仍旧正确。

如果没有测试,一般有两个办法:

  1. 展示修改,无论修改了什么。然后我们的用户遇到了 bug,这应该是我们没有手动完成某些检查。
  2. 如果对出错的惩罚比较严重,并且没有测试,那么大家会很害怕修改这样的函数,然后这些代码就会越来越陈旧,没有人会想接触它。这很不利于发展。

自动化测试则有助于避免这样的问题!

如果这个项目被测试代码覆盖了,就不会出现这种问题。在任何修改之后,我们都可以运行测试,并在几秒钟内看到大量的检查。

另外,一个经过良好测试的代码通常都有更好的架构。

当然,这是因为覆盖了自动化测试的代码更容易修改和改进。但还有另一个原因。

要编写测试,代码的组织方式应确保每个函数都有一个清晰描述的任务、定义良好的输入和输出。这意味着从一开始就有一个好的架构。

在实际开发中有时候可能并不容易,有时很难在写实际代码之前编写规范,因为还不清楚它应该如何表现。但一般来说,编写测试使得开发更快更稳定。

在本教程的后面部分,你将遇到许多包含了测试的任务。所以你会看到更多的实际例子。

编写测试需要良好的 JavaScript 知识。但我们刚刚开始学习它。因此,为了解决所有问题,到目前为止,你不需要编写测试,但是你应该已经能够阅读测试了,即使它们比本章中的内容稍微复杂一些。

✅任务

测试代码中有什么错误?

重要程度5️⃣

下面这个

pow

的测试代码有什么错误?

it("Raises x to the power n",function(){let x =5;let result = x;
  assert.equal(pow(x,1), result);

  result *= x;
  assert.equal(pow(x,2), result);

  result *= x;
  assert.equal(pow(x,3), result);});

附:从语法上来说这些测试代码是正确且通过的。

解决方案

这些测试代码展示了开发人员在编写测试代码时遇到的一些疑惑。

我们这里实际上有三条测试,但是用了一个函数来放置 3 个断言语句。

有时用这种方式编写会更容易,但是如果发生错误,那么到底什么出错了就很不明显。

如果错误发生在一个复杂的执行流的中间,那么我们就必须找出那个点的数据。我们必须 调试测试

将测试分成多个具有明确输入和输出的

it

代码块会更好。

像是这样:

describe("Raises x to power n",function(){it("5 in the power of 1 equals 5",function(){
 assert.equal(pow(5,1),5);});it("5 in the power of 2 equals 25",function(){
 assert.equal(pow(5,2),25);});it("5 in the power of 3 equals 125",function(){
 assert.equal(pow(5,3),125);});});

我们使用

describe

和一组

it

代码块替换掉了单个的

it

。现在,如果某个测试失败了,我们可以清楚地看到数据是什么。

此外,我们可以通过编写

it.only

而不是

it

来隔离单个测试,并以独立模式运行它:

describe("Raises x to power n",function(){it("5 in the power of 1 equals 5",function(){
 assert.equal(pow(5,1),5);});// Mocha 将只运行这个代码块
it.only("5 in the power of 2 equals 25",function(){
 assert.equal(pow(5,2),25);});it("5 in the power of 3 equals 125",function(){
 assert.equal(pow(5,3),125);});});

Polyfill 和转译器

JavaScript 语言在稳步发展。也会定期出现一些对语言的新提议,它们会被分析讨论,如果认为有价值,就会被加入到 https://tc39.github.io/ecma262/ 的列表中,然后被加到 规范 中。

JavaScript 引擎背后的团队关于首先要实现什么有着他们自己想法。他们可能会决定执行草案中的建议,并推迟已经在规范中的内容,因为它们不太有趣或者难以实现。

因此,一个 JavaScript 引擎只能实现标准中的一部分是很常见的情况。

查看语言特性的当前支持状态的一个很好的页面是 https://kangax.github.io/compat-table/es6/(它很大,我们现在还有很多东西要学)。

作为程序员,我们希望使用最新的特性。好东西越多越好!

另一方面,如何让我们现代的代码在还不支持最新特性的旧引擎上工作?

有两个工作可以做到这一点:

  1. 转译器(Transpilers)。
  2. 垫片(Polyfills)。

通过本文,我们一起了解它们的工作原理以及它们在 Web 开发中的位置。

转译器(Transpilers)

转译器 是一种可以将源码转译成另一种源码的特殊的软件。它可以解析(“阅读和理解”)现代代码,并使用旧的语法结构对其进行重写,进而使其也可以在旧的引擎中工作。

例如,在 ES2020 之前没有“空值合并运算符”

??

。所以,如果访问者使用过时了的浏览器访问我们的网页,那么该浏览器可能就不明白

height = height ?? 100

这段代码的含义。

转译器会分析我们的代码,并将

height ?? 100

重写为

(height !== undefined && height !== null) ? height : 100

// 在运行转译器之前
height = height ??100;// 在运行转译器之后
height =(height !==undefined&& height !==null)? height :100;

现在,重写了的代码适用于更旧版本的 JavaScript 引擎。

通常,开发者会在自己的计算机上运行转译器,然后将转译后的代码部署到服务器。

说到名字,Babel 是最著名的转译器之一。

现代项目构建系统,例如 webpack,提供了在每次代码更改时自动运行转译器的方法,因此很容易将代码转译集成到开发过程中。

Polyfills

新的语言特性可能不仅包括语法结构和运算符,还可能包括内建函数。

例如,

Math.trunc(n)

是一个“截断”数字小数部分的函数,例如

Math.trunc(1.23)

返回

1

在一些(非常过时的)JavaScript 引擎中没有

Math.trunc

函数,所以这样的代码会执行失败。

由于我们谈论的是新函数,而不是语法更改,因此无需在此处转译任何内容。我们只需要声明缺失的函数。

更新/添加新函数的脚本被称为“polyfill”。它“填补”了空白并添加了缺失的实现。

对于这种特殊情况,

Math.trunc

的 polyfill 是一个实现它的脚本,如下所示:

if(!Math.trunc){// 如果没有这个函数// 实现它
  Math.trunc=function(number){// Math.ceil 和 Math.floor 甚至存在于上古年代的 JavaScript 引擎中// 在本教程的后续章节中会讲到它们return number <0? Math.ceil(number): Math.floor(number);};}

JavaScript 是一种高度动态的语言,脚本可以添加/修改任何函数,甚至包括内建函数。

两个有趣的 polyfill 库:

  • core js 支持了很多特性,允许只包含需要的特性。
  • polyfill.io 提供带有 polyfill 的脚本的服务,具体取决于特性和用户的浏览器。

总结

在本章中,我们希望激励你学习现代甚至“前沿”的语言特性,即使 JavaScript 引擎还没有很好地支持它们。

只是不要忘记使用转译器(如果使用现代语法或运算符)和 polyfill(添加可能缺少的特性)。它们将确保代码能正常工作。

例如,以后熟悉了 JavaScript,你就可以搭建一个基于 webpack 和 babel-loader 插件的代码构建系统。

展示对各种特征的当前支持情况的工具:

P.S. 谷歌的 Chrome 浏览器通常是对最新的语言特性的支持情况最好的浏览器,如果教程的示例运行失败,请尝试使用 Chrome 浏览器。不过,教程中的大多数示例都适用于任意的现代浏览器。

JavaScript练习

标签: 驱动开发

本文转载自: https://blog.csdn.net/weixin_51568389/article/details/126419531
版权归原作者 一名不会打字的程序员 所有, 如有侵权,请联系我们删除。

“【JavaScripts从入门到入神】 Mocha 进行自动化测试|Polyfill 和转译器”的评论:

还没有评论