私たちはエンジニアとして、機能するものを構築したいと考えていますが、新しい機能を作成するたびに、必然的にアプリのサイズと複雑さが増大します。
製品が成長するにつれて、変更によって影響を受けるすべての機能を手動で(手などで) テストするのはますます時間がかかります。
自動テストがないため、時間がかかりすぎて出荷速度が低下するか、速度を維持するためにあまりにも費用がかかりすぎて、PagerDuty からの深夜の電話とともにバックログに新たなバグが発生することになります。
逆に、コンピュータは同じことを繰り返し行うようにプログラムすることができます。そこで、テストをコンピューターに委任しましょう。
テスト ピラミッドのアイデアでは、単体テスト、統合テスト、エンドツーエンド テストという 3 つの主要なタイプのテストが提案されています。あらゆる種類を深く掘り下げて、それぞれが必要な理由を理解しましょう。
ユニットは、(他のコンポーネントに依存せずに)単独でテストする小さなロジックです。
単体テストは高速です。数秒以内に完了します。分離により、依存するサービスを起動したり、API やデータベースの呼び出しを行ったりすることなく、いつでもローカルおよび CI 上で実行できます。
単体テストの例: 2 つの数値を受け取り、それらを合計する関数。別の引数を指定して呼び出して、戻り値が正しいことをアサートしたいと考えています。
// Function "sum" is the unit const sum = (x, y) => x + y test('sums numbers', () => { // Call the function, record the result const result = sum(1, 2); // Assert the result expect(result).toBe(3) }) test('sums numbers', () => { // Call the function, record the result const result = sum(5, 10); // Assert the result expect(result).toBe(15) })
さらに興味深い例は、API リクエストの終了後にテキストをレンダリングする React コンポーネントです。 API モジュールをモックしてテストに必要な値を返し、コンポーネントをレンダリングし、レンダリングされた HTML に必要なコンテンツが含まれていることをアサートする必要があります。
// "MyComponent" is the unit const MyComponent = () => { const { isLoading } = apiModule.useSomeApiCall(); return isLoading ? <div>Loading...</div> : <div>Hello world</div> } test('renders loading spinner when loading', () => { // Mocking the API module, so that it returns the value we need jest.mock(apiModule).mockReturnValue(() => ({ useSomeApiCall: jest.fn(() => ({ // Return "isLoading: false" for this test case isLoading: false })) })) // Execute the unit (render the component) const result = render(<MyComponent />) // Assert the result result.findByText('Loading...').toBeInTheDocument() }) test('renders text content when not loading', () => { // Mocking the API module jest.mock(apiModule).mockReturnValue(() => ({ useSomeApiCall: jest.fn(() => ({ // Return "isLoading: false" for this test case isLoading: false })) })) // Execute the unit (render the component) const result = render(<MyComponent />) // Assert the result result.findByText('Hello world').toBeInTheDocument() })
あなたのユニットが他のユニットと相互作用すること (依存関係)を、私たちは統合と呼びます。これらのテストは単体テストよりも時間がかかりますが、アプリの各部分がどのように接続されるかをテストします。
統合テストの例:データベースにユーザーを作成するサービス。これには、テストの実行時に DB インスタンス (依存関係) が使用可能である必要があります。サービスが DB からユーザーを作成および取得できることをテストします。
import db from 'db' // We will be testing "createUser" and "getUser" const createUser = name => db.createUser(name) // creates a user const getUser = name => db.getUserOrNull(name) // retrieves a user or null test("creates and retrieves users", () => { // Try to get a user that doesn't exist, assert Null is returned const nonExistingUser = getUser("i don't exist") expect(nonExistingUser).toBe(null); // Create a user const userName = "test-user" createUser(userName); // Get the user that was just created, assert it's not Null const user = getUser(userName); expect(user).to.not.be(null) })
これは、完全にデプロイされたアプリをテストするときのエンドツーエンドのテストであり、そのすべての依存関係が利用可能です。これらのテストは、実際のユーザーの動作を最もよくシミュレートし、アプリ内で考えられるすべての問題を捕捉できますが、最も遅いタイプのテストです。
エンドツーエンドのテストを実行する場合は、すべてのインフラストラクチャをプロビジョニングし、環境内でサードパーティのプロバイダーが利用可能であることを確認する必要があります。
これらは、アプリのミッションクリティカルな機能のためにのみ必要です。
エンドツーエンドのテスト例、ログイン フローを見てみましょう。アプリにアクセスし、ログインの詳細を入力して送信し、ウェルカム メッセージを確認します。
test('user can log in', () => { // Visit the login page page.goto('https://example.com/login'); // Fill in the login form page.fill('#username', 'john'); page.fill('#password', 'some-password'); // Click the login button page.click('#login-button'); // Assert the welcome message is visible page.assertTextVisible('Welcome, John!') })
エンドツーエンド テストは統合テストよりも遅く、統合テストは単体テストよりも遅いことに注意してください。
取り組んでいる機能がミッションクリティカルである場合は、少なくとも 1 つのエンドツーエンドテストを作成することを検討してください (認証フローの開発時にログイン機能がどのように動作するかを確認するなど)。
ミッションクリティカルなフローに加えて、できるだけ多くのエッジケースと機能のさまざまな状態をテストしたいと考えています。統合テストを使用すると、アプリの各部分がどのように連携して動作するかをテストできます。
エンドポイントとクライアント コンポーネントの統合テストを行うことは良い考えです。エンドポイントは操作を実行し、期待どおりの結果を生成し、予期しないエラーをスローしない必要があります。
クライアント コンポーネントは、正しいコンテンツを表示し、ユーザーの応答に期待どおりに応答する必要があります。
最後に、いつ単体テストを選択すべきでしょうか?数値を合計するsum
や<button>
タグをレンダリングするButton
など、個別にテストできる小さな関数はすべて、単体テストの優れた候補です。テスト駆動開発アプローチに従えば、単元は完璧です。
いくつかのテストを書いてみましょう! (ただし、小さなことから始めてください)
仕組みを理解するために、上記のことを一度実行してください。その後、機能やバグの作業中にもう一度実行します。それを同僚と共有すれば、全員がテストを作成し、時間を節約し、夜の睡眠を良くすることができます。