テスト
Ionic CLI を使用して @ionic/angular
アプリケーションを生成すると、アプリケーションのユニットテストとエンドツーエンドのテスト用に自動的に準備されます。これは Angular CLI で使われる設定と同じものです。Angular で作られたアプリケーションのテストについての詳細は Angular Testing Guide をご参照ください。
テストの原則
アプリケーションをテストするときは、テストによってシステムに欠陥があるかどうかを確認できる、ということを覚えておくことが 一番です。しかし、どんなささいなシステムも完全に欠陥のないことを証明することは不可能です。このため、テストの目的はコードが正しいことを確認することではなく、コードの中の問題を見つけることです。これは微妙ですが、重要な違いです。
もし私たちがコードが正しいことを証明しようとするのであれば、私たちはコードを通じて幸せな道を歩み続けようとするでしょう。もし私が問題の発見しようとするのであれば、コードをより完全に実行し、そこに潜むバグを発見する可能性が高くなります。
最初からアプリケーションのテストを開始することも最良です。これにより、修正が容易な段階で早期に欠陥を発見できます。またこれにより、システムに新しい機能が追加されたときに、コードを確実にリファクタリングすることもできます。
ユニットテスト
ユニットテストでは、システムの他の部分から分離して、単一のコードユニット(Component、Page、Service、Pipe など)を実行します。分離は、コードの依存関係の代わりにモックオブジェクトを注入することによって実現されます。モックオブジェクトによって、テストは依存関係の切り出しをきめ細かく制御することができます。モックによって、どの依存関係が呼び出され、何が渡されたかをテストで判断することもできます。
適切に記述されたユニットテストは、コードの単位とそれに含まれる機能が describe()
コールバックによって記述されるように構成されています。コード単位とその機能の要件は、it()
コールバックによってテストされます。describe()
コールバックと it()
コールバックの説明を読むと、フレーズとして意味がわかります。ネストされた describe()
と最後の it()
の記述をつなげると、テストケースを完全に記述する文が形成されます。
ユニットテストはコードを分離して実行するため、高速で堅牢であり、高度なコードカバレッジが可能です。
モックの利用
ユニットテストでは、コードをコードをモジュールで分離して実行します。これを簡単にするには、Jasmine(https://jasmine.github.io/) を使用することをお勧めします。Jasmine は、テスト実行中に依存関係の代わりにモックオブジェクト(Jasmine は 「スパイ」 と呼んでいます)を作成します。モックオブジェクトを使用すると、テストはその依存関係への呼び出しによって返される値を制御できるため、依存関係に加えられた変更から現在のテストを独立させることができます。これにより、テストのセットアップも簡単になり、テスト対象のモジュール内のコードだけをテストすることができます。
モックを使用すると、モックが呼び出されたかどうか、および toHaveBeenCalled*
セットの関数を介してどのように呼び出されたかを判断するために、テストでモックを 確認することもできます。これらの関数では、メソッドが呼び出されたことをテストするときに、toHaveBeenCalled
メソッドの呼び出しよりも toHaveBeenCalledTimes
の呼び出しを優先して、テストをできるだけ具体的に行う必要があります。つまり、expect(mock.foo).toHaveBeenCalledTimes(1)
は expect(mock.foo).toHaveBeenCalled()
よりも優れています。何かが呼ばれていないこと(expect(mock.foo).not.toHaveBeenCalled()
)をテストする際は、逆のアドバイスに従うべきです。
Jasmine でモックオブジェクトを作成する一般的な方法は 2 つあります。モックオブジェクトは、jasmine.createSpy
とjasmine.createSpyObj
を使ってスクラッチで作成することも、spyOn()
と spyOnProperty()
を使って既存のオブジェクトにスパイをインストールすることもできます。
jasmine.createSpy
と jasmine.createSpyObj
の利用
jasmine.createSpyObj
は、作成時に定義された一連のモックメソッドを使用して、完全なモックオブジェクトをスクラッチで作成します。これはとてもシンプルで便利です。テストのために何かを組み立てたり注入したりする必要はありません。この関数の使用する欠点は、実際のオブジェクトと一致しないオブジェクトを生成できることです。
jasmine.createSpy
も似ています が、スタンドアロンのモック関数を作成します。
spyOn()
と spyOnProperty()
の利用
spyOn()
は、既存のオブジェクトにスパイをインストールします。この手法を使用する利点は、オブジェクト上に存在しないメソッドをスパイしようとすると、例外が発生することです。これにより、テストが存在しないメソッドをモックすることを防ぎます。欠点は、テストが最初から完全に整形されたオブジェクトを必要とすることであり、これはテストに必要なセットアップの量を増加させるかと思います。
spyOnProperty()
は似ていますが、メソッドではなくプロパティに対してスパイするという点で異なります。
一般的なテストの構成
ユニットテストは、エンティティ(Component、Page、Service、Pipe など)ごとに 1 つの spec
ファイルを持つ spec
ファイルに含まれています。spec
ファイルは、テスト中のソースと一緒に存在し、かつその名前が付けられます。たとえば、プロジェクトに WeatherService という Service がある場合、そのコードはweather.service.ts
という名前のファイルにあり、テストは weather.service.spec.ts
という名前のファイルにあります。これらのファイルは両方とも同じフォルダにあります。
spec
ファイル自体には、そのテスト全体を定義するただ一つの describe
コールが含まれています。その中には、主要な機能領域を定義する他の describe
コールがネストされています。各 describe
コールには、setup コードと teardown コード(一般的に beforeEach
と afterEach
コールによって処理される)、機能を階層的に分解した describe
コール、また個々のテストケースを定義する it
コールが含まれます。
describe
と it
コールには、説明のテキストラベルも含まれます。適切な形式のテストでは、describe
と it
をコールすると、ラベルと組み合わせた適切なフレーズが実行され、各テストケースのすべてのラベルが describe
と it
ラベルを組み合わせて構成され、完全な文が作成されます。
例:
describe('Calculation', () => {
describe('divide', () => {
it('calculates 4 / 2 properly' () => {});
it('cowardly refuses to divide by zero' () => {});
...
});
describe('multiply', () => {
...
});
});
外側の describe
コールは Calculation
Service が テストされていることを示し、内側の describe
コールはテストされている機能を正確に示し、そして it
コールはテストケースが何であるかを示しています。各テストケースの完全なラベルを実行すると、意味のある文になります(卑劣な 0 での除算という計算を拒否しました)。
ページとコンポーネント
Pages は単なる Angular コンポーネントです。そのため、ページとコンポーネントは両方とも Angular のコンポーネントテストガイドライン を使ってテストされます。
ページとコンポーネントには TypeScript コードと HTML テンプレートマークアップの両方が含まれているため、コンポーネントクラスのテストとコンポーネント DOM のテストの両方を実行できます。ページが作成されると、生成されるテンプレートテストは次のようになります:
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TabsPage } from './tabs.page';
describe('TabsPage', () => {
let component: TabsPage;
let fixture: ComponentFixture<TabsPage>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [TabsPage],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
fixture = TestBed.createComponent(TabsPage);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
コンポーネントクラスのテストを行う場合、コンポーネントオブジェクトは component=fixture.componentInstance;
によって定義されたコンポーネントオブジェクトを使用してアクセスされます。これはコンポーネントクラスのインスタンスです。DOM テストを行う際には、fixture.nativeElement
プロパティが使用されます。これはコンポーネントの実際の HTMLElement
であり、テストで DOM を調べるために HTMLElement.querySelector
などの標準の HTML API メソッドを使うことを可能にします。
Service
Service は、多くの場合、計算やその他の操作を実行するユーティリティの service と、主に HTTP 操作やデータ操作を実行するデータの service の 2 つの大まかなカテゴリーのいずれかに分類されます。