🌟
フロントエンド:単体テストの観点
対象読者
- Jest や Testing Library のテストの書き方は理解しているが、何をテストすべきかわからないという方。
動機
- 近年、フロントエンドでもテストを書くことが重要視されるようになってきました。
- テスト導入する際、共通で利用するコンポーネントや関数などをテストする記事は多いですが、何をテストすべきかについての記事が見当たらなかったため、私が意識してるテスト観点を執筆しようと思いました。
まず初めに各種テストの説明
単体テスト
- 関数やメソッドなどの最小単位の部分を個別にテストする手法のこと。
- 利用ツール例
- Jest
- React-Testing-Library
結合テスト
- 複数のコンポーネントが、互いにうまく動作していることをテストする手法のこと。
- 利用ツール例
- Jest
- React-Testing-Library
End to End (E2E)テスト
- ユーザーが実際にシステムを使用するときと同様の条件で、システム全体の機能をテストする手法のこと。
- フロントエンド、バックエンド、およびデータベースなどを含む全体システムを対象とする。
- 利用ツール例
- Puppeteer
- Cypress
- Playwright
ビジュアルリグレッションテスト
- UI(ユーザーインターフェイス)コンポーネントの変更によって、予期しない視覚的な変更が発生していないことをテストする手法のこと。
- UI のデザインや表示に関連する問題を特定するために用いる。
- 利用ツール例
- reg-suit
単体テストを記述する際に意識している点
- 命令網羅(Statement coverage : C0)
- テスト対象のコードが少なくとも一度は実行されるかどうかを確認する。
- 全ての処理(命令)をテストする。
- 分岐網羅(Branch coverage : C1)
- テスト対象のコードの条件分岐が少なくとも一度は実行されるかどうかを確認する。
- 条件分岐内(if else)に注目して、条件式のすべての組み合わせをテストする。
- 条件網羅(Condition coverage : C2)
- 複数の条件式が含まれる分岐の全ての真偽値の組み合わせを確認する。
- 条件分岐内の条件(&& や ||)に着目して、全ての条件結果をテストする。
3 つの網羅率を意識してテストを記述しています。
観点を意識して実際にテストを書いていく
本記事で利用するテスト対象コード
type Props = {
isLoggedIn: boolean;
isAdmin: boolean;
};
export function ConditionalMessage({ isLoggedIn, isAdmin }: Props) {
const getMessage = () => {
if (isLoggedIn || isAdmin) {
return isAdmin ? "Hello, Admin!" : "Hello, User!";
}
return "Please log in.";
};
return <div>{getMessage()}</div>;
}
- この ConditionalMessage コンポーネントは、与えられた isLoggedIn と isAdmin のプロパティに基づいて、異なるメッセージを表示するシンプルな React コンポーネントです。以下は、このコンポーネントの機能を説明します。
- isLoggedIn と isAdmin の 2 つのプロパティを受け取ります。isLoggedIn はユーザーがログインしているかどうかを示す bool 値であり、isAdmin はユーザーが管理者であるかどうかを示す bool 値です。
- getMessage 関数は、与えられたプロパティに基づいて表示すべきメッセージを決定します。
- isLoggedIn または isAdmin のどちらかが true の場合、次の条件に基づいてメッセージを返します。
- isAdmin が true の場合、"Hello, Admin!" というメッセージを返します。
- そうでない場合は、"Hello, User!" というメッセージを返します。
- どちらも false の場合、"Please log in." というメッセージを返します。
- isLoggedIn または isAdmin のどちらかが true の場合、次の条件に基づいてメッセージを返します。
命令網羅、分岐網羅、条件網羅を意識してテスト観点を洗い出す
- 分岐網羅を意識したテスト観点を洗い出します。命令網羅は、全ての分岐を網羅すれば自動的に達成されるため、特に意識しません。
- 分岐網羅に基づくテスト観点は以下のようになります。
- 管理者の場合
- ログイン済みかつ一般ユーザーの場合
- 未ログインかつ一般ユーザーの場合
- 管理者の場合にログイン状態を確認していない為、条件網羅を意識し、さらに細かくテスト観点を分けます。
- 未ログインかつ管理者の場合
- 未ログインかつ一般ユーザーの場合
- 最終的なテスト観点は以下の通りです。
- ログイン済みかつ管理者の場合
- ログイン済みかつ一般ユーザーの場合
- 未ログインかつ管理者の場合
- 未ログインかつ一般ユーザーの場合
実際にテストコード書く
describe("ConditionalMessage", () => {
describe("ログイン済み", () => {
describe("管理者の場合", () => {
test("Hello, Admin!と表示されること", () => {
render(<ConditionalMessage isLoggedIn={true} isAdmin={true} />);
expect(screen.getByText("Hello, Admin!")).toBeInTheDocument();
});
});
});
describe("ログイン済み", () => {
describe("一般ユーザーの場合", () => {
test("Hello, User!と表示されること", () => {
render(<ConditionalMessage isLoggedIn={true} isAdmin={false} />);
expect(screen.getByText("Hello, User!")).toBeInTheDocument();
});
});
});
describe("未ログイン", () => {
describe("管理者の場合", () => {
test("Hello, Admin!と表示されること", () => {
render(<ConditionalMessage isLoggedIn={false} isAdmin={true} />);
expect(screen.getByText("Hello, Admin!")).toBeInTheDocument();
});
});
});
describe("未ログイン", () => {
describe("一般ユーザーの場", () => {
test("Please log in.と表示されること", () => {
render(<ConditionalMessage isLoggedIn={false} isAdmin={false} />);
expect(screen.getByText("Please log in.")).toBeInTheDocument();
});
});
});
});
このテストコードは ConditionalMessage コンポーネントの機能を正しく検証するために必要な条件をカバーしており、命令網羅(C0)、分岐網羅(C1)、および条件網羅(C2)のカバレッジが 100%達成されています。
Tips
Jest CLI を用いたカバレッジレポートの見方
-
Jest CLI を使用して、--coverage オプションを付けてテストを実行します。
-
テストが実行されると、Jest はカバレッジレポートを生成します。
-
デフォルトでは、カバレッジレポートは
coverage
ディレクトリに作成されます。 -
HTML 形式のレポートは
coverage/lcov-report
ディレクトリに格納されています。 -
ブラウザで HTML ファイルを開くと、より詳細な情報を確認できます。
-
カバレッジレポートには、いくつかのアイコンとハイライトがあります。よく出てくるものをいくつか紹介します。
-
I
はif
がテストされていないことを表します。 -
E
はelse
はテストされていないことを表します。 - 左の列
○N
は、その行が何回実行されたかを表します。 - 実行されていない行またはコードの一部は、ピンクでハイライトが当たります。
- 通過してない分岐は、黄色のハイライトが当たります。
- 以下は、サンプルのカバレッジレポートです。
-
-
これらの機能を利用することで、テストされていない箇所をわかりやすく特定することができます。
まとめ
この記事では、自分の意識してる単体テスト観点について解説してみました。これからテストを書こうとしてる人の助けになれば幸いです。
最後までご覧いただきありがとうございました!
Discussion