paint-brush
安定したソフトウェア: 自動テストの威力について学ぶ@igorluchenkov
1,022 測定値
1,022 測定値

安定したソフトウェア: 自動テストの威力について学ぶ

Igor Luchenkov6m2024/03/02
Read on Terminal Reader

長すぎる; 読むには

自動テストは、あまり時間をかけずにソフトウェアをテストする方法です。テストには、単体テスト、統合テスト、エンドツーエンド テストの 3 種類があります。あらゆる種類を深く掘り下げて、それぞれが必要な理由を理解しましょう。単体テストは高速です。分離により、依存するサービスを起動したり、API やデータベースを呼び出したりすることなく、いつでもローカルおよび CI 上で実行できます。統合テストは時間がかかりますが、アプリの各部分がどのように接続されるかをテストします。
featured image - 安定したソフトウェア: 自動テストの威力について学ぶ
Igor Luchenkov HackerNoon profile picture

この記事は、次のような場合に注目してください。

  • 高品質のソフトウェアを作成することに情熱を持っており、テストによってアプリの安定性を強化したいと考えています。


  • 実稼働システムに予期せぬバグが発生することにうんざりしています。


  • 自動テストとは何か、またそのアプローチ方法を理解するのに助けが必要です。

なぜ自動テストが必要なのでしょうか?

私たちはエンジニアとして、機能するものを構築したいと考えていますが、新しい機能を作成するたびに、必然的にアプリのサイズと複雑さが増大します。


製品が成長するにつれて、変更によって影響を受けるすべての機能を手動で(手などで) テストするのはますます時間がかかります。


自動テストがないため、時間がかかりすぎて出荷速度が低下するか、速度を維持するためにあまりにも費用がかかりすぎて、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など、個別にテストできる小さな関数はすべて、単体テストの優れた候補です。テスト駆動開発アプローチに従えば、単元は完璧です。


次は何ですか?

いくつかのテストを書いてみましょう! (ただし、小さなことから始めてください)

  • プロジェクト/言語に合ったテスト フレームワークをインストールします。各言語には、JavaScript 用のJest / Vitest 、エンドツーエンド用のCypress / Playwright (JavaScript も使用)、Java 用のJUnitなど、テスト用の一般的なライブラリがあります。


  • プロジェクト内で小さな関数を見つけて、その単体テストを作成します。


  • コンポーネント/サービスとデータベースの相互作用のための統合テストを作成します。


  • 簡単なログイン フローなど、すぐにテストできる重要なシナリオを選択し、そのためのエンドツーエンドテストを作成します。


仕組みを理解するために、上記のことを一度実行してください。その後、機能やバグの作業中にもう一度実行します。それを同僚と共有すれば、全員がテストを作成し、時間を節約し、夜の睡眠を良くすることができます。


役立つリソース: