Jestテストフレームワーク入門

9330 ワード

近年,フロントエンドのエンジニアリング化の発展に伴い,フロントエンドは天地を覆すような変化を遂げている.jQueryは徐々に私たちの視野を薄め、React、Vue、anglurの3つの馬車が急速に走ってきた.それ以来、フロントエンドはデータ駆動の時代に入り、明確なモジュール化開発の方式もあった.それに伴って、自分のコードの正確性を保証する方法があります.
なぜフロントエンドの自動化テストが必要なのか
どうして私が自分で書いたコードをテストするためにテストコードを書きますか?そんなに自分を信用しないの?はい、そうです.自分が書いたコードを信じないでください.そうしないと、どうしてそんなに多くのバグがありますか.
それ以外に、1つのプロジェクトに複数のメンテナンスが必要なため、他の人がうっかりコードを変更すると新しい問題になるかもしれません.コードを提出する前に、テスト例を走って、自分が他の人の論理を変更していないことを確認する必要があります.他人のコードを変更する場合は、このような変更が新しい問題を生むかどうかを明らかにし、最後にテスト用例コードも変更することを覚えておいてください.
フロントエンドテストツール一覧
フロントエンドテストツールもフロントエンドのフレームワークと同様に複雑で、よく見られるテストツールは、テストフレームワーク、断言ライブラリ、テストオーバーライド率ツールなどに大きく分けられます.本稿を正式に開始する前に、まずそれらを大まかに理解します.
テストフレームワーク
テストフレームワークの役割は、テスト例を記述し、使用例をグループ化するための便利な構文を提供することです.
テストフレームワークは、TDD(テスト駆動開発)とBDD(動作駆動開発)の2つに分けられます.両者の違いは、主にいくつかの文法的な違いであることを理解しています.ここで、BDDは可読性の高い使用例文法を提供しています.詳細な違いについては、The Difference Between TDD and BDDを参照してください.
一般的なテストフレームワークには、Jasmine、Mocha、および本稿で説明するJestがあります.
アサーション・ライブラリ
断言ライブラリは主に意味化手法を提供し,テストに参加する値を様々に判断するために用いられる.これらの意味化メソッドは、テストの結果を返します.成功するか、失敗します.一般的なブレークスルーにはShouldがあります.js, Chai.jsなど.
オーバーライド率のテストツール
テスト・インスタンスのコードに対するテスト状況を統計し、istanbulなどの対応するレポートを生成します.
Jest
どうしてJestを選んだの?
JestはFacebook製品のテストフレームワークであり、他のテストフレームワークに比べて、よく使われるテストツールが内蔵されていることが大きな特徴です.例えば、断言、テストカバー率ツールを持参し、開梱・即用を実現しています.
一方、フロントエンド向けのテストフレームワークとして、Jestは独自のスナップショットテスト機能を利用して、UIコードで生成されたスナップショットファイルを比較することで、Reactなどの一般的なフレームワークの自動テストを実現することができる.
また、Jestのテスト例は並列に実行され、変更されたファイルに対応するテストのみが実行され、テスト速度が向上します.現在Githubではstar数が2万を超えている.Facebookのほか、業界内の他の会社も他のテストフレームワークからJestに転換し始め、例えばAirbnbの試みは、将来のJestの発展傾向が比較的速いと信じています.
インストール
Jestはnpmまたはyarnでインストールできます.npmを例にとると、npm install -g jestでグローバルインストールが可能である.局所的にインストールしてpackageにするだけでもよい.jsonでtestスクリプトを指定します.
{
  "scripts": {
    "test": "jest"
  }
}

Jestのテストスクリプトの名前は*.test.jsのようで、Jestがグローバルに実行されてもnpm run testで実行されても、現在のディレクトリの下にある*.test.jsまたは*.spec.jsのすべてのファイルを実行し、テストを完了します.
使用法
具体的な使い方はJEST公式サイトを参考にして、ここではいくつかの一般的な使い方を簡単に紹介します.
用例の表示
テスト用例はテストフレームワークが提供する最も基本的なAPIであることを示し、Jest内部ではJasmine 2を使用してテストを行うため、その用例文法はJasmineと同じである.test()関数は、簡単な例を挙げるテスト例を記述する.
// hello.js
module.exports = () => 'Hello world'
// hello.test.js
let hello = require('hello.js')

test('should get "Hello world"', () => {
    expect(hello()).toBe('Hello world') //     
    // expect(hello()).toBe('Hello') //     
})

このうちtoBe('Hello world')は断言です(Jestは「matcher」と呼ばれていますが、matcherの詳細についてはドキュメントを参照してください).使用例を書き終え、プロジェクトディレクトリの下でnpm testを実行すると、テスト結果が表示されます.
使用例の前処理または後処理
テストが開始される前に環境の検査を行ったり、テストが終了した後にクリーンアップ操作をしたりしたい場合があります.これは、使用例の前処理または後処理が必要です.テストファイルのすべての使用例に対して統一的な前処理を行い、beforeAll()関数を使用することができる.一方、各使用例の開始前に前処理を行う場合は、beforeEach()関数を使用することができる.後処理については,対応するafterAll()afterEach()の関数もある.
いくつかの使用例に対して同じ前処理または後処理を行うだけであれば、これらの使用例を先にグループにまとめることができる.describe()関数を使用すると、一組の使用例を表すことができ、上述した4つの処理関数をdescribe()の処理コールバック内に配置すると、一組の使用例に対する前処理または後処理が実現される.
describe('test testObject', () => {
    beforeAll(() => {
        //      
    })

    test('is foo', () => {
       expect(testObject.foo).toBeTruthy()
    })

    test('is not bar', () => {
        expect(testObject.bar).toBeFalsy()
    })

    afterAll(() => {
        //      
    })
})

非同期コードのテスト
非同期コードのテストは、テストフレームワークのテストがいつ完了するかを通知し、適切なタイミングで断言させることが重要です.Babelが盛んになるにつれて、フロントエンドの非同期書き方の多くはPromiseの形式で行われ、async/awaitのような同期の方法で非同期を書くことができます.この書き方のテスト方法を見てみましょう.
// promiseHello.js
module.exports = (name) => {
    return new Promise((resolve) => {
        setTimeout(() => resolve(`Hello ${name}`), 1000)
    })
}
// promiseHello.test.js
let promiseHello = require('promiseHello.js')

test('should get "Hello World"', async () => {
  const data = await promiseHello('World');
  expect(data).toBe('Hello World');
});

test('the fetch fails with an error', async () => {
  expect.assertions(1);
  try {
    const data = await promiseHello('World');
    expect(data).toBe('Hello World');
  } catch (e) {
    expect(e).toMatch('error');
  }
});

Mock Functions
Mock関数では、コード間の接続をテストできます.実装には、消去関数の実際の実装、関数への呼び出しのキャプチャ(およびこれらの呼び出しで渡されたパラメータ)、newを使用してインスタンス化されたときに構造関数のインスタンスをキャプチャし、テスト時に戻り値を設定できるようにします.
mock関数の使用
入力された配列内の各要素に対してコールバック関数を呼び出す関数forEachの内部実装をテストすると仮定する.
function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}

この関数をテストするには、mock関数を使用して、mock関数のステータスを確認して、コールバック関数が予定通りに呼び出されることを確認します.
const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);

//   mock         
expect(mockCallback.mock.calls.length).toBe(2);

//                 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

//                 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

//              42
expect(mockCallback.mock.results[0].value).toBe(42);
.mockプロパティ
すべてのmock関数には、この特殊な.mock属性があり、この関数がどのように呼び出され、呼び出されたときの戻り値に関する情報が保存されています.
// The function was called exactly once
expect(someMockFunction.mock.calls.length).toBe(1);

// The first arg of the first call to the function was 'first arg'
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');

// The second arg of the first call to the function was 'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');

// The return value of the first call to the function was 'return value'
expect(someMockFunction.mock.results[0].value).toBe('return value');

// This function was instantiated exactly twice
expect(someMockFunction.mock.instances.length).toBe(2);

// The object returned by the first instantiation of this function
// had a `name` property whose value was set to 'test'
expect(someMockFunction.mock.instances[0].name).toEqual('test');

Mockの戻り値
Mock関数は、試験中に試験値をコードに注入するためにも使用することができる.
const myMock = jest.fn();
console.log(myMock());
// > undefined

myMock
  .mockReturnValueOnce(10)
  .mockReturnValueOnce('x')
  .mockReturnValue(true);

console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true

Mock関数は、関数連続伝達スタイル(functional continuation-passing style)のコードでも有効です.このコードスタイルは、複雑な中間操作を回避し、コンポーネントの真の意図を直感的に表現するのに役立ち、呼び出される前にテストに値を直接注入するのに役立ちます.
const filterTestFn = jest.fn();

// Make the mock return `true` for the first call,
// and `false` for the second call
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);

const result = [11, 12].filter(num => filterTestFn(num));

console.log(result);
// > [11]
console.log(filterTestFn.mock.calls);
// > [ [11], [12] ]

多くの現実世界の例では,実際には依存するコンポーネントにシミュレーション関数を割り当てて構成しているが,手法は同じである.これらの場合、本当にテストしたい関数ではない論理を実現することはできるだけ避けます.
Mockシミュレーションモジュール
APIからユーザを取得するクラスがあると仮定する.クラスはaxiosでAPIを呼び出し、dataに戻ります.このクラスには、すべてのユーザーのプロパティが含まれています.
// users.js
import axios from 'axios';

class Users {
  static all() {
    return axios.get('/users.json').then(resp => resp.data);
  }
}

export default Users;

現在、実際のAPIを呼び出さずにこの方法をテストするために(テストを遅く不安定にする)、jest.mock(...)関数を使用してaxiosモジュールを自動的にシミュレートすることができます.
axiosモジュールをシミュレートすると,axiosの戻り結果を任意にシミュレートすることができる..getmockResolvedValueを提供し、テストのために偽のデータを返します.実際にはaxiosにget('/users.json')には偽のresponseがあります.
// users.test.js
import axios from 'axios';
import Users from './users';

jest.mock('axios');  // mock    

test('should fetch users', () => {
  const users = [{name: 'Bob'}];
  const resp = {data: users};
  axios.get.mockResolvedValue(resp);  //       axios     

  // or you could use the following depending on your use case:
  // axios.get.mockImplementation(() => Promise.resolve(resp))

  return Users.all().then(data => expect(data).toEqual(users));
});

フロントエンド自動化テストの価値
近年、フロントエンドのエンジニアリング化が盛んになっているが、フロントエンドの自動化テストという内容はあまり重視されていないようだ.プロジェクトの反復中に専門のテスト担当者がテストを行いますが、彼らがテストに来ると、コードが開発された状態になります.これに比べて、開発中にテストを行った場合、次のようなメリットがあります.
  • コード品質と機能の実現の完全度を保障する
  • 開発効率を向上させ、開発過程でテストを行うことで、バグを早期に発見することができます.このとき、問題の位置決めと修復を行う速度は、開発が終わってからバグを修理するように呼ばれるよりも自然に速いです.
  • はプロジェクトのメンテナンスを容易にし、その後のコード更新もテスト例に通じなければならない.再構築や開発者の変化を行っても、予想される機能の実現を保障できる
  • .
    もちろん、何事にも両面性があり、メリットは明らかですが、すべてのプロジェクトがテストフレームワークを導入する価値があるわけではありません.結局、テスト用例を維持するにもコストがかかります.アクティブなページなど、頻繁に変更され、多重性の低いコンテンツに対しては、開発者にテスト用例を書くために人力を割いてもらうのは確かに損ではありません.
  • 長期メンテナンスが必要な項目.コードの保守性、機能の安定性を保証するためにテストが必要です.
  • 比較的安定したプロジェクト、またはプロジェクトの比較的安定した部分.テスト例を作成し、メンテナンスコストを
  • 削減
  • の多重化された部分、例えば、いくつかの汎用コンポーネントおよびライブラリ関数.多重化のため、品質は
  • を保障しなければならない.
    参照先:
    フロントエンドテストフレームJest