テストとは

ソフトウェアを作成するときは、テストによって正しく動作することを確認できます。テストは、ソフトウェアが意図したとおりに動作することを確認するために、特定の方法でソフトウェアを実行するプロセスとして広く定義できます。

テストを成功させることで、新しいコードや機能を追加したり、依存関係をアップグレードしたりしても、作成済みのソフトウェアが想定どおりに動作し続けることを確信できます。また、テストにより、思いがけないシナリオや予期しない入力からソフトウェアを保護できます。

テストするウェブの動作の例を以下に示します。

  • ボタンがクリックされたときにウェブサイトの機能が正しく動作することを確認する。
  • 複雑な関数が正しい結果を生成することを確認する。
  • ユーザー ログインが必要なアクションを完了する。
  • 不正な形式のデータが入力された場合に、フォームにエラーが適切に報告されることを確認する。
  • ユーザーの帯域幅が極端に低い場合やオフラインになった場合でも、複雑なウェブアプリが機能するようにします。

自動テストと手動テスト

ソフトウェアは主に、自動テストと手動テストという 2 つの方法でテストできます。

手動テストでは、人間がソフトウェアを直接実行します。たとえば、ブラウザにウェブサイトを読み込んで、想定どおりに動作することを確認します。手動テストは簡単に作成または定義できます。たとえば、サイトを読み込めるか、このようなアクションは実行できますが、1 回の実行には人間が膨大な時間を費やします。人間は非常に創造的で、探索的テストと呼ばれる種類のテストを行うことができますが、特に同じタスクを何度も実行する場合は、失敗や不整合に気づくことには苦手です。

自動テストとは、テストをコード化してコンピュータで繰り返し実行し、セットアップや結果の確認などの繰り返し手順を人間が行うことなく、ソフトウェアの意図された動作を確認できるようにするプロセスです。重要な点は、自動テストを構成すると、頻繁な実行が可能になるということです。これは依然として非常に広範な定義であり、自動テストにはさまざまな形や形式があることに注意してください。このコースの大部分は自動テストの練習に関するものです。

手動テストは、多くの場合、自動テストを作成する前身となりますが、自動テストの信頼性が低くなったり、対象が広がったり、作成が難しくなったりする場合でもあります。

例による基本

JavaScript や関連言語を作成するウェブ デベロッパーにとって、簡潔な自動テストとは、Node を介して、またはブラウザに読み込むことで、毎日実行する次のようなスクリプトです。

import { fibonacci } from "../src/math.js";

if (fibonacci(0) !== 0) {
  throw new Error("Invalid 0th fibonacci result");
}
const fib13 = fibonacci(13);
if (fib13 !== 233) {
  throw new Error("Invalid 13th fibonacci result, was=${fib13} wanted=233");
}

これは、次の分析情報を提供する簡単な例です。

  • ソフトウェア(フィボナッチ関数)を実行し、その結果を期待値と照合することで、意図したとおりに動作することを確認するために、これはテストです。動作が正しくない場合はエラーが発生します。これは JavaScript が Error をスローして表現します。

  • このスクリプトをターミナルやブラウザで手動で実行しても、個々の手順を実行しなくても繰り返し実行できるため、自動テストです。次のページ(テストが実行される場所)で詳細を説明します。

  • このテストではライブラリを使用しません(どこでも実行できる JavaScript であるため)。このコースで後ほど説明しますが、テストの作成に役立つツールは多数ありますが、どのツールも、なんらかの問題が発生した場合にエラーを発生させるという基本的な原則に沿って動作します。

ライブラリの実践テスト

ほとんどのライブラリまたは組み込みテスト フレームワークには、テストの作成を容易にする 2 つの主要なプリミティブがあります。それは、アサーションと、独立したテストを定義する方法です。これらについては、次のセクションのアサーションとその他のプリミティブで詳しく説明します。ただし、大まかに言えば、表示または作成するほぼすべてのテストで、このようなプリミティブが使用されることを覚えておくことが重要です。

アサーションは、結果の確認と、なんらかの問題が発生した場合にエラーを発生させる処理を組み合わせる方法です。たとえば、assert を導入すると、前のテストをより簡潔にできます。

import { fibonacci } from "../src/math.js";
import { assert } from "a-made-up-testing-library";

assert.equal(fibonacci(0), 0, "Invalid 0th fibonacci result");
assert.equal(fibonacci(13), 233, "Invalid 13th fibonacci result");

独立したテストを定義し、必要に応じてスイートにグループ化することで、このテストをさらに改善できます。次のスイートは、フィボナッチ関数とカタロニア語関数を個別にテストします。

import { fibonacci, catalan } from "../src/math.js";
import { assert, test, suite } from "a-made-up-testing-library";

suite("math tests", () => {
  test("fibonacci function", () => {
    assert.equal(fibonacci(0), 0, "Invalid 0th fibonacci result");
    assert.equal(fibonacci(13), 233, "Invalid 13th fibonacci result");
  });
  test("relationship between sequences", () => {
    const numberToCheck = 4;
    const fib = fibonacci(numberToCheck);
    const cat = catalan(numberToCheck);
    assert.isAbove(fib, cat);
  });
});

ソフトウェア テストのこのコンテキストでは、名詞としての testテストケースを指します。前の例の「シーケンス間の関係」テストケースなど、単一の独立したアドレス指定可能なシナリオです。

個別に名前を付けたテストは、特に次のタスクに役立ちます。

  • 時間の経過に伴うテストの成功または失敗の決定。
  • バグまたはシナリオを名前でハイライト表示して、シナリオの解決を簡単にテストできるようにします。
  • glob フィルタを使用するなど、一部のテストを他のテストから独立して実行する。

テストケースを考える一つの方法は、単体テストの「3 つの A」(arrange、action、assert)を使用することです。各テストケースの基本は次のとおりです。

  • いくつかの値または状態(ハードコードされた入力データ)の配置。
  • メソッドの呼び出しなどのアクションを実行します。
  • 出力値または更新された状態をアサートします(assert を使用)。

テストの規模

前のセクションのコードサンプルでは、単体テストについて説明しています。これは、ソフトウェアの小さな部分(通常は 1 つのファイル、この場合は 1 つの関数からの出力のみ)をテストするためです。複数のファイル、コンポーネント、相互接続された異なるシステム(ネットワーク サービスや外部依存関係の動作など、制御できない場合もあります)のコードを考慮すると、テストの複雑さは増大します。このため、テストタイプは多くの場合、スコープまたはスケールに基づいて名前が付けられます。

他のテストタイプの例には、単体テスト、コンポーネント テスト、ビジュアル テスト、統合テストなどがあります。これらの名前には厳密な定義はなく、コードベースによって意味が異なる可能性があるため、これらをガイドとして使用し、自分に適した定義を見つけてください。たとえば、システムのテスト対象のコンポーネントは何ですか?React デベロッパーにとっては、これは文字どおり「React コンポーネント」にマッピングされますが、他のコンテキストでは、別の意味を持つかもしれません。

個々のテストの規模は、「テスト ピラミッド」と呼ばれる概念の内側に配置できます。これは、テストの内容と実行方法に関する良い経験則です。

テストピラミッド。上部にエンドツーエンド(E2E)テスト、中央に統合テスト、下部に単体テストがあります。
テスト用ピラミッド。

このアイデアは繰り返し試され、今ではテスト用ダイヤモンドやテスト用アイスコーンなど、他のさまざまな形状が普及しています。テスト作成の優先度は、通常、コードベースに固有のものになります。ただし、単体テストのような単純なテストは、実行が速く、記述しやすい(そのためテスト数が多くなる)傾向があり、スコープが限られているのに対し、エンドツーエンド テストのような複雑なテストは作成が困難ですが、より広いスコープをテストできます。実際、多くのテスト「シェイプ」の最上位レイヤは手動テストになる傾向があります。これは、一部のユーザー操作が複雑すぎて自動テストにコード化できないためです。

これらのタイプは、自動テストのタイプで拡張されます。

理解度をチェックする

ほとんどのテスト ライブラリとフレームワークにはどのようなプリミティブが用意されていますか?

クラウド プロバイダを使用するランナー サービス。
一部のブラウザベースのランナーには、テストをアウトソーシングする方法が用意されていますが、これはテスト ライブラリの通常の機能ではありません。
満たされない場合に例外を発生させるアサーション。
エラーをスローしてテストを不合格にすることはできますが、assert() とそのバリエーションを使用すると、チェックの作成が簡単になるため、含める傾向があります。
テストをテストのピラミッドに分類する方法。
これを行う標準的な方法はありません。テストの名前の先頭に接頭辞を付けたり、別のファイルに配置したりすることもできますが、ほとんどのテスト フレームワークでは分類は組み込まれていません。
独立したテストを機能別に定義する機能。
test() メソッドは、ほぼすべてのテストランナーに含まれています。テストコードはファイルの最上位で実行されないため、テストランナーは各テストケースを独立したユニットとして扱うことができます。