Angularの単体テストの書き方。テストフレームワーク(Jasmine / Karma)。
この記事では、Angularの単体テスト(ユニットテスト)について初心者向けに概要と書き方を説明していく。
Webアプリケーションの開発においてもテストの重要度は年々上がっている。
ReactやAngularといったJavaScriptフレームワークによって作られるSPA(シングルページアプリケーション)は高性能である反面、様々な処理をクライアント側でおこなうためにバグが起きる危険性も従来のWebページより高くなってきている。
開発のテンポを崩すことなく、効率的にテストをおこない常に品質を保証するために様々なテスト手法が用いられている。
Web開発のテストの種類
Web開発の現場においておこなわれるテストには様々な種類や名称がある。
ここでは「単体テスト」と「E2Eテスト」にフォーカスして、内容と目的を説明したい。
単体テスト(ユニットテスト)
単体テストはコンポーネントやサービスといった小さな単位でおこなう機能テスト。
ひとつのテスト項目でひとつの関数をテストすることが多い。
単体テストはコンポーネントを開発したメンバーによって同時に作成されることが多い。
すべてのコンポーネント・サービスに単体テストを加える必要は無く、どういった場面で単体テストを書くと効果的かは開発チーム内で検討していったほうがいい。
E2Eテスト
E2Eテストはシナリオに基づく一連の画面操作をチェックするためのテスト。
テスト項目は「新規のユーザアカウントを作成して認証確認をおこなったのちにログインする。」といった具合に、ユーザの目的に焦点を置き作成される。
E2EはEnd-to-Endの意味であり、E2Eテストはインテグレーションテスト・結合テストなどとも呼ばれる。
E2Eテストについては、別の記事で詳しく説明する予定だ。
その他のテスト
Webの開発においては「単体テスト」「E2Eテスト」の他に必要に応じて以下のようなテストがおこなわれる。
- 負荷テスト
- 性能テスト
- ユーザビリティテスト
なお、Circle CI・Travis CIといったCI(継続的インテグレーション)ツールを導入している環境では、単体テスト・E2Eテストはコードの変更がマージされるたびに自動で実行される。
Angularの単体テスト
次にAngularにおける単体テストについて簡単に説明したい。
JasmineとKarma
Angularは単体テストのためにJasmie/Karmaという2つのツールを標準で備えている。
- Jasmine
単体テストのためのフレームワーク。
記述したテスト内容に則って処理を行い、結果が期待される値と一致するかどうかをテストする。
英語に近い構文で記述でき、直感的であり学習コストは低い。
また、その構文からテスト駆動型の開発にも向いている。 - Karma
Karmaはテストを実行するためのツール。テストの実行ツールは「ランナー」と呼ばれる。
Karmaはコマンドラインから実行することができる。
spec.tsファイル
Angularでは、ngコマンドを使用して新しいコンポーネントやサービスを作成すると標準でspec.tsというファイルが作成される。
これは単体テストのためのファイルでありJasmineの構文を使って記述される。
このファイルは単体テストの必要が無いならば削除してもいい。
ngコマンドにてspec.tsファイルを作成しないようにするには以下の2つの方法がある。
- 「ng config schematics.@schematics/angular:component.skipTests true」でプロジェクトの設定を変える。
- ng generateを実行する際にプロパティ「–skip-tests=true」を追記する。
単体テストを実行する
spec.tsファイルはrunコマンドにより実行することができる。
IntellijでAngularの単体テストを実行する場合はspec.tsファイルを右クリックしてコンテクストメニューからrunを選択する。
*メニューが表示されない場合は「Karma」プラグインがインストールされているか確認する。
テストが実行され「Run」タブが自動で開く。
各テスト項目は非同期で実行されるので、上の項目から順番に結果が表示される訳ではない。
Karmaの設定はAngularプロジェクト内のkarma.conf.jsにて変更することができる。
単体テストの書き方
では単体テストを書いていこう。
単体テストの例
まずはサンプルのコードを見て基本的な構文を理解しよう。
ここではAngularプロジェクトを作成する際に自動で作られるapp.component.spec.tsを例にして紹介する。
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'unit-test-app'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('unit-test-app');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('unit-test-app app is running!');
});
});
Jasmineの構文
上のサンプルコードを見ながらJasmineの構文を説明していく。
describe
テストの内容はdescribe関数の内側に記述する。
descriptionにはテストの名前を記述するがファイル名で問題ないだろう。
beforeEach & TestBed
beforeEachはその名の通り各テストが実行される前に実行される関数。
この関数でテストに必要なコンポーネントやサービスを初期化する。
単体テストでエラーが起こる場合は、ここの設定に問題があるケースが多い。
テストの終了後に一連の処理をおこなう時はafterEach関数を使用する。
it
itは単体テストのテスト項目にあたる関数。
引数exceptationに期待される結果を英文で記述する。
引数assertionにテストでおこなう処理と期待される結果を記述する。
expect & Matcher
テストで検証する内容と期待される結果はexpect関数とMatcherと呼ばれる関数を組み合わせて表現する。
例: expect(app.title).toEqual(‘unit-test-app’);
⇒ app.titleの文字列が’unit-test-app’であればテストは合格となる。
Matcher | 検証の内容 |
---|---|
toBe(expected) | 期待値と同じオブジェクトであるか |
toEqual(expected) | 期待値と同じ値であるか |
toBeTruthy() | trueであるか |
toThrow() | 例外が発生するか |
toContain(expected) | 期待値が配列に含まれているか |
結果の成否を逆にする時はnot構文が使用できる。