テストをひっくり返す


自動テストは素晴らしいです.これらは、数秒で数百の入力の組み合わせを実行するのに役立ちます.これは、手作業でテストするのは非常に面倒な作業です.

私の経験では、典型的なテスト スイートは次のようになります.

describe('my test suite', () => {
  it('should work with basic test case', async () => {
    const user = await UserFactory.create({});
    expect(user.id).toBe(null);
    expect(user.name).toBe(null);
  });
  it('should work with a long name', async () => {
    const user = await UserFactory.create({
      firstName: 'Pippilotta',
      middleName: 'Delicatessa',
      lastName: 'Windowshade Mackrelmint Ephraimsdaughter Longstocking',
    });
    expect(user.id).toBe(null);
    expect(user.name).toBe('Pippilotta Delicatessa Windowshade Mackrelmint Ephraimsdaughter Longstocking');
  });
});



この設計は、エンジニアが問題に取り組んだ順序を反映しています.多くの場合、テスト ケースは、エンジニアが検討したエッジ ケースに直接対応しています.各テストは、次のおおよその形式に従います.
  • スイート: 特定の主題またはサービスに関連するすべてのテスト
  • テスト: 条件 A
  • テスト ケースの設定
  • 結果の検証

  • テスト: 条件 B
  • テスト ケースの設定
  • 結果の検証

  • 必要な一連の条件をカバーするための追加のテスト.


  • ただし、このスタイルにはいくつかの欠点があります.
  • 新しいテストを追加するコストが高い.テストを実行するには、各テスト セットアップを新しいブロックにコピーする必要があります.
  • コード障害に対するアトミックな可視性の欠如.最新のテスト ランナーのほとんどは、最初の失敗を見つけた時点でテスト スイートを終了します.上記のように複数のチェックを一緒に実行している場合、最初の問題のみが表示されます.

  • 別のデザインを次に示します.

    describe('my test suite', () => {
      describe('basic test case', () => {
        let user;
        beforeAll(async () => {
          user = await UserFactory.create({});
        });
        it('should set null user id', async () => {
          expect(user.id).toBe(null);
        });
        it('should set null user name', async () => {
          expect(user.name).toBe(null);
        });
      });
      describe('with a long name', () => {
        let user;
        beforeAll(async () => {
          user = await UserFactory.create({
            firstName: 'Pippilotta',
            middleName: 'Delicatessa',
            lastName: 'Windowshade Mackrelmint Ephraimsdaughter Longstocking',
          });
        });
        it('should set null user id', async () => {
          expect(user.id).toBe(null);
        });
        it('should correctly form full name', async () => {
          expect(user.name).toBe(
            'Pippilotta Delicatessa Windowshade Mackrelmint Ephraimsdaughter Longstocking'
          );
        });
      });
    });
    
    


  • スイート: 特定の主題またはサービスに関連するすべてのテスト
  • スイート: 状態 A
  • beforeAll/Each: テスト ケースを設定する
  • テスト: 検証結果 1
  • テスト: 検証結果 2

  • スイート: コンディションB
  • beforeAll/Each: テスト ケースを設定する
  • テスト: 検証結果 1
  • テスト: 検証結果 2

  • 必要な一連の条件をカバーするためのより多くのテスト スイート.


  • これにはいくつかの利点があります.
  • 失敗したテストが複数あると、デバッグが容易になります. 1 つの障害が別の障害を引き起こすことがあります.テストごとに 1 つの失敗メッセージしか表示されない以前のアプローチでは、デバッグに役立つ情報が少なくなります.
  • すべてのテストの失敗は平易な英語で書かれています.これにより、何が起こっているのかを把握しやすくなります.
  • 合格したテストも平易な英語で書かれています.これも重要です!私は、コードにつながるビジネス上の意思決定を追跡することを強く信じています.テストを英語で書くことを強いられると、コードの一部が古くなって削除される可能性があることに気づきやすくなります.
  • ネストされたテスト設定を実行する方が簡単です.複数レベルのバリエーションをテストする場合、たとえば、ユーザー名、メール アドレス、パスワードの複数の組み合わせをチェックする場合、beforeAll または beforeEach を使用して各レベルで詳細を追加し、テスト スイートを好きなだけ深くネストすることができます. .終了するたびに、必ず afterAll または afterEach を使用して各ケースをクリーンアップしてください!
  • 将来のテストのためにプレースホルダーを追加するのがより簡単になりました. Jest などの多くのフレームワークには test.todo などの修飾子があり、実装を提供せずにテストのタイトルを記述できます.これは//TODO コメントよりもはるかに優れています.テスト ランナーはまだ作業が残っていることを知らせてくれるからです.

  • コードベースを採用すると、そのコードベースによって確立されたパターンや規則に陥りやすくなります.ただし、少し努力するだけで、将来行う必要がある作業の量を減らす新しい習慣を始めることができます.