国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

Javascript 使用 Mocha 進行自動化測試

2023-02-17 10:38 更新

自動化測試將被用于進一步的任務中,并且還將被廣泛應用在實際項目中。

我們?yōu)槭裁葱枰獪y試?

當我們在寫一個函數(shù)時,我們通??梢韵胂蟪鏊鼞撟鍪裁矗耗男﹨?shù)會給出哪些結果。

在開發(fā)期間,我們可以通過運行程序來檢查它并將結果與預期進行比較。例如,我們可以在控制臺中這么做。

如果出了問題 —— 那么我們會修復代碼,然后再一次運行并檢查結果 —— 直到它工作為止。

但這樣的手動“重新運行”是不完美的。

當通過手動重新運行來測試代碼時,很容易漏掉一些東西。

例如,我們要創(chuàng)建一個函數(shù) f。寫一些代碼,然后測試:f(1) 可以執(zhí)行,但是 f(2) 不執(zhí)行。我們修復了一下代碼,現(xiàn)在 f(2) 可以執(zhí)行了??雌饋硪呀浉愣??但是我們忘了重新測試 f(1)。這樣有可能會導致出現(xiàn)錯誤。

這是非常典型的。當我們在開發(fā)一些東西時,我們會保留很多可能需要的用例。但是不要想著程序員在每一次代碼修改后都去檢查所有的案例。所以這就很容易造成修復了一個問題卻造成另一個問題的情況。

自動化測試意味著測試是獨立于代碼的。它們以各種方式運行我們的函數(shù),并將結果與預期結果進行比較。

行為驅動開發(fā)(BDD)

我們來使用一種名為 行為驅動開發(fā) 或簡言為 BDD 的技術。

BDD 包含了三部分內容:測試、文檔和示例。

為了理解 BDD,我們將研究一個實際的開發(fā)案例。

開發(fā) “pow”:規(guī)范

我們想要創(chuàng)建一個函數(shù) pow(x, n) 來計算 x 的 n 次冪(n 為整數(shù))。我們假設 n≥0

這個任務只是一個例子:JavaScript 中有一個 ** 運算符可以用于冪運算。但是在這里我們專注于可以應用于更復雜任務的開發(fā)流程上。

在創(chuàng)建函數(shù) pow 的代碼之前,我們可以想象函數(shù)應該做什么并且描述出來。

這樣的描述被稱作 規(guī)范(specification, spec),包含用例的描述以及針對它們的測試,如下所示:

describe("pow", function() {

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

});

正如你所看到的,一個規(guī)范包含三個主要的模塊:

?describe("title", function() { ... })?

表示我們正在描述的功能是什么。在我們的例子中我們正在描述函數(shù) pow。用于組織“工人(workers)” —— it 代碼塊。

?it("use case description", function() { ... })
?

it 里面的描述部分,我們以一種 易于理解 的方式描述特定的用例,第二個參數(shù)是用于對其進行測試的函數(shù)。

?assert.equal(value1, value2)
?

it 塊中的代碼,如果實現(xiàn)是正確的,它應該在執(zhí)行的時候不產生任何錯誤。

assert.* 函數(shù)用于檢查 pow 函數(shù)是否按照預期工作。在這里我們使用了其中之一 —— assert.equal,它會對參數(shù)進行比較,如果它們不相等則會拋出一個錯誤。這里它檢查了 pow(2, 3) 的值是否等于 8。還有其他類型的比較和檢查,我們將在后面介紹到。

規(guī)范可以被執(zhí)行,它將運行在 it 塊中指定的測試。我們稍后會看到。

開發(fā)流程

開發(fā)流程通??雌饋硐襁@樣:

  1. 編寫初始規(guī)范,測試最基本的功能。
  2. 創(chuàng)建一個最初始的實現(xiàn)。
  3. 檢查它是否工作,我們運行測試框架 Mocha(很快會有更多細節(jié))來運行測試。當功能未完成時,將顯示錯誤。我們持續(xù)修正直到一切都能工作。
  4. 現(xiàn)在我們有一個帶有測試的能工作的初步實現(xiàn)。
  5. 我們增加更多的用例到規(guī)范中,或許目前的程序實現(xiàn)還不支持。無法通過測試。
  6. 回到第 3 步,更新程序直到測試不會拋出錯誤。
  7. 重復第 3 步到第 6 步,直到功能完善。

如此來看,開發(fā)就是不斷地 迭代。我們寫規(guī)范,實現(xiàn)它,確保測試通過,然后寫更多的測試,確保它們工作等等。最后,我們有了一個能工作的實現(xiàn)和針對它的測試。

讓我們在我們的開發(fā)案例中看看這個開發(fā)流程吧。

在我們的案例中,第一步已經完成了:我們有一個針對 pow 的初始規(guī)范。因此讓我們來實現(xiàn)它吧。但在此之前,讓我們用一些 JavaScript 庫來運行測試,就是看看測試是通過了還是失敗了。

行為規(guī)范

在本教程中,我們將使用以下 JavaScript 庫進行測試:

  • Mocha —— 核心框架:提供了包括通用型測試函數(shù) ?describe ?和 ?it?,以及用于運行測試的主函數(shù)。
  • Chai —— 提供很多斷言(assertion)支持的庫。它提供了很多不同的斷言,現(xiàn)在我們只需要用 ?assert.equal?。
  • Sinon —— 用于監(jiān)視函數(shù)、模擬內建函數(shù)和其他函數(shù)的庫,我們在后面才會用到它。

這些庫都既適用于瀏覽器端,也適用于服務器端。這里我們將使用瀏覽器端的變體。

包含這些框架和 ?pow ?規(guī)范的完整的 HTML 頁面:

<!DOCTYPE html>
<html>
<head>
  <!-- add mocha css, to show results -->
  <link rel="stylesheet"  rel="external nofollow" target="_blank" >
  <!-- add mocha framework code -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js" rel="external nofollow" ></script>
  <script>
    mocha.setup('bdd'); // minimal setup
  </script>
  <!-- add chai -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js" rel="external nofollow" ></script>
  <script>
    // chai has a lot of stuff, let's make assert global
    let assert = chai.assert;
  </script>
</head>

<body>

  <script>
    function pow(x, n) {
      /* function code is to be written, empty now */
    }
  </script>

  <!-- the script with tests (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>? 包含測試函數(shù),在我們的例子中 —— 和 ?pow ?相關的代碼。
  3. 測試代碼 —— 在我們的案例中是名為 ?test.js? 的腳本,它包含上面 ?describe("pow", ...)? 的那些代碼。
  4. HTML 元素 ?<div id="mocha">? 將被 Mocha 用來輸出結果。
  5. 可以使用 ?mocha.run()? 命令來開始測試。

結果:


到目前為止,測試失敗了,出現(xiàn)了一個錯誤。這是合乎邏輯的:我們的 pow 是一個空函數(shù),因此 pow(2,3) 返回了 undefined 而不是 8。

未來,我們會注意到有更高級的測試工具,像是 karma 或其他的,使自動運行許多不同的測試變得更容易。

初始實現(xiàn)

為了可以通過測試,讓我們寫一個 pow 的簡單實現(xiàn):

function pow() {
  return 8; // :) 我們作弊啦!
}

哇哦,現(xiàn)在它可以工作了。


改進規(guī)范

我們所做的這些絕對是作弊。函數(shù)是不起作用的:嘗試計算 pow(3,4) 的話就會得到一個不正確的結果,但是測試卻通過了。

……但是這種情況卻是在實際中相當?shù)湫屠?。測試通過了,但是函數(shù)卻是錯誤的。我們的規(guī)范是不完善的。我們需要給它添加更多的測試用例。

這里我們又添加了一個測試來檢查 pow(3, 4) = 81

我們可以選擇兩種方式中的任意一種來組織測試代碼:

  1. 第一種 —— 在同一個 it 中再添加一個 assert
  2. describe("pow", function() {
    
      it("raises to n-th power", function() {
        assert.equal(pow(2, 3), 8);
        assert.equal(pow(3, 4), 81);
      });
    
    });
  3. 第二種 —— 寫兩個測試:
  4. 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);
      });
    
    });

主要的區(qū)別是,當 assert 觸發(fā)一個錯誤時,it 代碼塊會立即終止。因此,在第一種方式中,如果第一個 assert 失敗了,我們將永遠不會看到第二個 assert 的結果。

保持測試之間獨立,有助于我們獲知代碼中正在發(fā)生什么,因此第二種方式更好一點。

除此之外,還有一個規(guī)范值得遵循。

一個測試檢查一個東西。

如果我們在看測試代碼的時候,發(fā)現(xiàn)在其中有兩個相互獨立的檢查 —— 最好將它拆分成兩個更簡單的檢查。

因此讓我們繼續(xù)使用第二種方式。

結果:


正如我們可以想到的,第二條測試失敗了。當然啦,我們的函數(shù)總會返回 8,而 assert 期望的是 81。

改進實現(xiàn)

讓我們寫一些更加實際的代碼來通過測試吧:

function pow(x, n) {
  let result = 1;

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

  return result;
}

為了確保函數(shù)可以很好地工作,我們來使用更多值測試它吧。除了手動地編寫 it 代碼塊,我們可以使用 for 循環(huán)來生成它們:

describe("pow", function() {

  function makeTest(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);
  }

});

結果:


嵌套描述

我們繼續(xù)添加更多的測試。但在此之前,我們需要注意到輔助函數(shù) makeTest 和 for 應該被組合到一起。我們在其他測試中不需要 makeTest,只有在 for 循環(huán)中需要它:它們共同的任務就是檢查 pow 是如何自乘至給定的冪次方。

使用嵌套的 describe 來進行分組:

describe("pow", function() {

  describe("raises x to power 3", function() {

    function makeTest(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” 測試。在輸出中我們可以看到帶有標題的縮進:


將來,我們可以在頂級域中使用 it 和 describe 的輔助函數(shù)添加更多的 it 和 describe,它們不會看到 makeTest。

?before/after? 和 ?beforeEach/afterEach?

我們可以設置 before/after 函數(shù)來在運行測試之前/之后執(zhí)行。也可以使用 beforeEach/afterEach 函數(shù)來設置在執(zhí)行 每一個 it 之前/之后執(zhí)行。

例如:

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)

通常,beforeEach/afterEach 和 before/after 被用于執(zhí)行初始化,清零計數(shù)器或做一些介于每個測試(或測試組)之間的事情。

延伸規(guī)范

pow 的基礎功能已經完成了。第一次迭代開發(fā)完成啦。當我們慶祝和喝完香檳之后,讓我們繼續(xù)改進它吧。

正如前面所說,函數(shù) pow(x, n) 適用于正整數(shù) n。

JavaScript 函數(shù)通常會返回 NaN 以表示一個數(shù)學錯誤。接下來我們對無效的 n 值執(zhí)行相同的操作。

讓我們首先將這個行為加到規(guī)范中(!):

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));
  });

});

新測試的結果:


新加的測試失敗了,因為我們的實現(xiàn)方式是不支持它們的。這就是 BDD 的做法:我們首先寫一些暫時無法通過的測試,然后去實現(xiàn)它們。

Other assertions

請注意斷言語句 assert.isNaN:它用來檢查 NaN

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

  • ?assert.equal(value1, value2)? —— 檢查相等 ?value1 == value2?。
  • ?assert.strictEqual(value1, value2)? —— 檢查嚴格相等 ?value1 === value2?。
  • ?assert.notEqual?,?assert.notStrictEqual? —— 執(zhí)行和上面相反的檢查。
  • ?assert.isTrue(value)? —— 檢查 ?value === true?。
  • ?assert.isFalse(value)? —— 檢查 ?value === false?。
  • ……完整的列表請見 docs

因此我們應該給 pow 再加幾行:

function pow(x, n) {
  if (n < 0) return NaN;
  if (Math.round(n) != n) return NaN;

  let result = 1;

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

  return result;
}

現(xiàn)在它可以工作了,所有的測試也都通過了:


總結

在 BDD 中,規(guī)范先行,實現(xiàn)在后。最后我們同時擁有了規(guī)范和代碼。

規(guī)范有三種使用方式:

  1. 作為 測試 —— 保證代碼正確工作。
  2. 作為 文檔 —— ?describe ?和 ?it ?的標題告訴我們函數(shù)做了什么。
  3. 作為 案例 —— 測試實際工作的例子展示了一個函數(shù)可以被怎樣使用。

有了規(guī)范,我們可以安全地改進、修改甚至重寫函數(shù),并確保它仍然正確地工作。

這在一個函數(shù)會被用在多個地方的大型項目中尤其重要。當我們改變這樣一個函數(shù)時,沒有辦法手動檢查每個使用它們的地方是否仍舊正確。

如果沒有測試,一般有兩個辦法:

  1. 展示修改,無論修改了什么。然后我們的用戶遇到了 bug,這應該是我們沒有手動完成某些檢查。
  2. 如果對出錯的懲罰比較嚴重,并且沒有測試,那么大家會很害怕修改這樣的函數(shù),然后這些代碼就會越來越陳舊,沒有人會想接觸它。這很不利于發(fā)展。

自動化測試則有助于避免這樣的問題!

如果這個項目被測試代碼覆蓋了,就不會出現(xiàn)這種問題。在任何修改之后,我們都可以運行測試,并在幾秒鐘內看到大量的檢查。

另外,一個經過良好測試的代碼通常都有更好的架構。

當然,這是因為覆蓋了自動化測試的代碼更容易修改和改進。但還有另一個原因。

要編寫測試,代碼的組織方式應確保每個函數(shù)都有一個清晰描述的任務、定義良好的輸入和輸出。這意味著從一開始就有一個好的架構。

在實際開發(fā)中有時候可能并不容易,有時很難在寫實際代碼之前編寫規(guī)范,因為還不清楚它應該如何表現(xiàn)。但一般來說,編寫測試使得開發(fā)更快更穩(wěn)定。

在本教程的后面部分,你將遇到許多包含了測試的任務。所以你會看到更多的實際例子。

編寫測試需要良好的 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);
});

附:從語法上來說這些測試代碼是正確且通過的。


解決方案

這些測試代碼展示了開發(fā)人員在編寫測試代碼時遇到的一些疑惑。

我們這里實際上有三條測試,但是用了一個函數(shù)來放置 3 個斷言語句。

有時用這種方式編寫會更容易,但是如果發(fā)生錯誤,那么到底什么出錯了就很不明顯。

如果錯誤發(fā)生在一個復雜的執(zhí)行流的中間,那么我們就必須找出那個點的數(shù)據(jù)。我們必須 調試測試。

將測試分成多個具有明確輸入和輸出的 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?,F(xiàn)在,如果某個測試失敗了,我們可以清楚地看到數(shù)據(jù)是什么。

此外,我們可以通過編寫 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);
  });
});


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號