エンタープライズ アプリの単体テスト
Note
この電子ブックは 2017 年の春に公開され、それ以来更新されていません。 貴重なままの本に多くがありますが、資料の一部は時代遅れです。
モバイル アプリには、デスクトップおよび Web ベースのアプリケーションで心配する必要がない固有の問題があります。 モバイル ユーザーは、使用するデバイス、ネットワーク接続、サービスの可用性、およびその他のさまざまな要因によって異なります。 そのため、モバイル アプリは、品質、信頼性、パフォーマンスを向上させるために実際の世界で使用されるため、テストする必要があります。 単体テスト、統合テスト、ユーザー インターフェイス テストなど、アプリで実行する必要があるテストには多くの種類があり、単体テストはテストの最も一般的な形式です。
単体テストでは、アプリの小さな単位 (通常はメソッド) を受け取り、コードの残りの部分から分離し、期待どおりに動作することを確認します。 その目的は、各機能ユニットが期待どおりに実行されることをチェックして、エラーがアプリ全体に伝達されないようにすることです。 バグが発生したらそれを検出し、2 次的な障害点でバグの影響を間接的に観察する方が効率的です。
単体テストは、ソフトウェア開発ワークフローの不可欠な部分である場合に、コード品質に最も大きな影響を及ぼす。 メソッドが記述されるとすぐに、標準、境界、入力データの正しくないケースに応答してメソッドの動作を検証し、コードによって明示的または暗黙的な前提条件がチェック単体テストを記述する必要があります。 または、テスト 駆動型開発では、コードの前に単体テストが記述されます。 このシナリオでは、単体テストは設計ドキュメントと機能仕様の両方として機能します。
注意
単体テストは、回帰に対して非常に効果的です。つまり、以前は機能していたが、障害のある更新によって妨げられた機能です。
単体テストでは通常、arrange-act-assert パターンが使用されます。
- 単体テスト メソッドの arrange セクションは、オブジェクトを初期化し、テスト対象のメソッドに渡されるデータの値を設定します。
- act セクションでは、必須の引数を使用して、テスト対象のメソッドを呼び出します。
- assert セクションでは、テスト対象のメソッドのアクションが期待どおりに動作することを確認します。
このパターンに従って、単体テストの読み取りと一貫性が確保されます。
依存関係の挿入と単体テスト
疎結合アーキテクチャを採用する動機の 1 つが、単体テストが容易になることです。 Autofac に登録されている型の 1 つが クラスです OrderService
。 次のコード例は、このクラスのアウトラインを示しています。
public class OrderDetailViewModel : ViewModelBase
{
private IOrderService _ordersService;
public OrderDetailViewModel(IOrderService ordersService)
{
_ordersService = ordersService;
}
...
}
クラスは OrderDetailViewModel
、オブジェクトを IOrderService
インスタンス化するときにコンテナーが解決する型に OrderDetailViewModel
依存します。 ただし、クラスを単体テストするオブジェクトをOrderService
OrderDetailViewModel
作成するのではなく、テストの目的で オブジェクトをOrderService
モックに置き換えます。 図 10-1 は、この関係を示しています。
図 10-1: IOrderService インターフェイスを実装するクラス
この方法により、 OrderService
実行時に オブジェクトを OrderDetailViewModel
クラスに渡すことができます。また、テストの容易性のために、 OrderMockService
テスト時に クラスを OrderDetailViewModel
クラスに渡すことができます。 このアプローチのメイン利点は、Web サービスやデータベースなどの扱いにくいリソースを必要とせずに単体テストを実行できるという点です。
MVVM アプリケーションのテスト
MVVM アプリケーションからのモデルとビュー モデルのテストは、他のクラスのテストと同じであり、単体テストやモック作成などの同じツールと手法を使用できます。 ただし、モデル クラスをモデル化して表示するのが一般的なパターンがいくつかあります。これは、特定の単体テスト手法の恩恵を受ける可能性があります。
ヒント
各単体テストでテストするのは 1 つのことだけにしてください。 単体テストでユニットの動作の複数の側面を実行しようとしないでください。 これを行うと、テストの読み取りと更新が困難になります。 また、エラーを解釈するときに混乱を招く可能性もあります。
eShopOnContainers モバイル アプリは、2 種類の単体テストをサポートする単体テストを実行します。
- ファクトは常に true であるテストであり、不変条件をテストします。
- 理論は、特定のデータ セットに対してのみ当てはまるテストです。
eShopOnContainers モバイル アプリに含まれる単体テストはファクト テストであるため、各単体テスト メソッドは 属性で [Fact]
装飾されます。
注意
xUnit テストはテスト ランナーによって実行されます。 テスト ランナーを実行するには、必要なプラットフォームの eShopOnContainers.TestRunner プロジェクトを実行します。
非同期機能のテスト
MVVM パターンを実装する場合、ビュー モデルは通常、サービスに対して (多くの場合は非同期的に) 操作を呼び出します。 通常、これらの操作を呼び出すコードのテストでは、実際のサービスの代わりにモックが使用されます。 次のコード例は、モック サービスをビュー モデルに渡すことで非同期機能をテストする方法を示しています。
[Fact]
public async Task OrderPropertyIsNotNullAfterViewModelInitializationTest()
{
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
Assert.NotNull(orderViewModel.Order);
}
この単体テストでは、InitializeAsync
メソッドの呼び出し後に OrderDetailViewModel
インスタンスの Order
プロパティに値が設定されることを確認します。 InitializeAsync
メソッドは、ビュー モデルの対応するビューの移動時に呼び出されます。 ナビゲーションの詳細については、「ナビゲーション」を参照してください。
OrderDetailViewModel
インスタンスが作成されるときに、OrderService
インスタンスが引数として指定されることが期待されます。 ただし、OrderService
は Web サービスからデータを取得します。 したがって、 クラスのOrderMockService
OrderService
モック バージョンである インスタンスは、コンストラクターの引数OrderDetailViewModel
として指定されます。 次に、ビュー モデルの InitializeAsync
メソッドが呼び出され、操作が呼び出 IOrderService
されると、Web サービスと通信するのではなく、モック データが取得されます。
INotifyPropertyChanged 実装のテスト
INotifyPropertyChanged
インターフェイスを実装すると、ビューが、ビュー モデルとモデルから発生した変更に対応できるようになります。 これらの変更は、コントロールに表示されるデータに限定されるものではなく、アニメーションを開始するビュー モデルの状態やコントロールを無効にするビュー モデルの状態など、ビューの制御にも使用されます。
単体テストで直接更新できるプロパティは、イベント ハンドラーを PropertyChanged
イベントにアタッチし、プロパティの新しい値を設定した後でイベントが発生するかどうかを確認することでテストできます。 次のコード例は、そのようなテストを示します。
[Fact]
public async Task SettingOrderPropertyShouldRaisePropertyChanged()
{
bool invoked = false;
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
orderViewModel.PropertyChanged += (sender, e) =>
{
if (e.PropertyName.Equals("Order"))
invoked = true;
};
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
Assert.True(invoked);
}
この単体テストでは、OrderViewModel
クラスの InitializeAsync
メソッドが呼び出され、その Order
プロパティが更新されます。 Order
プロパティに対して PropertyChanged
イベントが発生した場合、単体テストは合格します。
メッセージベースの通信のテスト
次のコード例に示すように、疎結合クラス間の通信に MessagingCenter
クラスを使用するビュー モデルは、テスト対象のコードによって送信されるメッセージをサブスクライブすることによって単体テストできます。
[Fact]
public void AddCatalogItemCommandSendsAddProductMessageTest()
{
bool messageReceived = false;
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
Xamarin.Forms.MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(
this, MessageKeys.AddProduct, (sender, arg) =>
{
messageReceived = true;
});
catalogViewModel.AddCatalogItemCommand.Execute(null);
Assert.True(messageReceived);
}
この単体テストは、実行中の AddCatalogItemCommand
に応答して CatalogViewModel
が AddProduct
メッセージを発行することを確認します。 MessagingCenter
クラスはマルチキャスト メッセージ サブスクリプションをサポートしているため、単体テストは AddProduct
メッセージをサブスクライブし、その受信に応答してコールバック デリゲートを実行できます。 このコールバック デリゲートは、ラムダ式として指定され、テストの動作を検証するために ステートメントによってAssert
使用されるフィールドを設定boolean
します。
例外処理のテスト
次のコード例に示すように、無効なアクションまたは入力に対して特定の例外がスローされることを確認する単体テストを記述することもできます。
[Fact]
public void InvalidEventNameShouldThrowArgumentExceptionText()
{
var behavior = new MockEventToCommandBehavior
{
EventName = "OnItemTapped"
};
var listView = new ListView();
Assert.Throws<ArgumentException>(() => listView.Behaviors.Add(behavior));
}
コントロールに という名前OnItemTapped
のListView
イベントがないため、この単体テストでは例外がスローされます。 Assert.Throws<T>
メソッドは、予期される例外の型が T
であるジェネリック メソッドです。 Assert.Throws<T>
メソッドに渡される引数は、例外をスローするラムダ式です。 したがって、ラムダ式が ArgumentException
をスローする場合、単体テストは合格します。
ヒント
例外メッセージ文字列を調べる単体テストは記述しないでください。 例外メッセージ文字列は時間の経過と同時に変化する可能性があるため、その存在に依存する単体テストは脆弱と見なされます。
検証のテスト
検証の実装をテストするには、検証規則が正しく実装されていることをテストする方法と、クラスが期待どおりに実行するテストという ValidatableObject<T>
2 つの側面があります。
通常、検証ロジックはテストが簡単です。これは通常、出力が入力に依存する自己完結型のプロセスであるためです。 次のコード例に示すように、少なくとも 1 つの検証規則が関連付けられた各プロパティで Validate
メソッドを呼び出した結果に関するテストが必要です。
[Fact]
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
mockViewModel.Surname.Value = "Smith";
bool isValid = mockViewModel.Validate();
Assert.True(isValid);
}
この単体テストでは、MockViewModel
インスタンス内の 2 つの ValidatableObject<T>
プロパティに両方のデータがある場合に検証が成功することを確認します。
検証が成功したことを確認するだけでなく、検証単体テストでは、クラスが期待どおりに実行されることを確認するために、各 ValidatableObject<T>
インスタンスの Value
、IsValid
、および Errors
のプロパティの値も確認する必要があります。 次のコード例は、これを行う単体テストを示しています。
[Fact]
public void CheckValidationFailsWhenOnlyForenameHasDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
bool isValid = mockViewModel.Validate();
Assert.False(isValid);
Assert.NotNull(mockViewModel.Forename.Value);
Assert.Null(mockViewModel.Surname.Value);
Assert.True(mockViewModel.Forename.IsValid);
Assert.False(mockViewModel.Surname.IsValid);
Assert.Empty(mockViewModel.Forename.Errors);
Assert.NotEmpty(mockViewModel.Surname.Errors);
}
この単体テストでは、MockViewModel
の Surname
プロパティにデータがなく、各 ValidatableObject<T>
インスタンスの Value
、IsValid
、および Errors
プロパティが正しく設定されている場合に検証が失敗します。
まとめ
単体テストでは、アプリの小さな単位 (通常はメソッド) を受け取り、コードの残りの部分から分離し、期待どおりに動作することを確認します。 その目的は、各機能ユニットが期待どおりに実行されることをチェックして、エラーがアプリ全体に伝達されないようにすることです。
テスト対象のオブジェクトの動作は、依存オブジェクトの動作をシミュレートするモック オブジェクトに依存オブジェクトを置き換えることで分離できます。 これにより、Web サービスやデータベースなどの扱いにくいリソースを必要とせずに単体テストを実行できます。
MVVM アプリケーションからのモデルとビュー モデルのテストは、他のクラスのテストと同じであり、同じツールと手法を使用できます。