統合テストは、モバイルアプリのコンポーネント—モジュール、サービス、データベース、外部API—の相互作用の正確さを検証します。各コンポーネントをアイソレートする単体テストとは異なり、統合テストは接合部でのエラーを検出します:データフォーマットの不妥合、パラメータ伝送の失敗、サーバー応答の不正な処理です。Martin Fowler, 2018によると、統合テストは単体テストが見逃した重大缺陷の40%までをカバーし、リリース前にシステムの精度に対する自信を与えます。
ポイント
統合テストは、アプリケーションの個々のモジュールやサブシステム間の相互作用の正確さを評価するソフトウェア検証ステージです。単体テストが各コンポーネントをアイソレートして検証するのに対し、統合テストはこれらのコンポーネントを一緒に集め、それらが連携してどう動作するかを検証します。典型なシナリオには、ネットワークレイヤーとリポジトリの間のデータ転送、ORMを通してのデータベースへの書き込み、サードパーティAPIからの応答処理があります。
モバイル開発の文脈では、統合テストはUIレイヤー、ビジネスロジック、データソースの間の相互作用をカバーします。例えば、テストは「ログイン」ボタンをクリックした後に、アプリがサーバーにリクエストを送信し、トークンを受信し、ローカルストレージに保存することを検証できます。このような検証により、コンポーネントチェーンが障害なく動作することが確認されます。
World Quality Report 2023によると、定期的に統合テストを実施している企業は、単体テストのみに依存するプロジェクトに比べてプロダクションインシデントの数を35%減少させています。これにより、統合テストは商業開発における品質保証戦略の必須要素となっています。
モバイルアプリは、ネットワークリクエスト、ローカルデータベース、プッシュ通知、システムサービス、サードパーティSDKなど、相互に関連した多くのコンポーネントから構成されています。これらの各コンポーネントは単独で開発されますが、ランタイムではリアルタイムでデータをやり取りします。統合テストは、アイソレートされたモジュール検証では見つけられない缺陷を検出します。
統合テストによって発見される典型的な問題には、APIとアプリモデルのデータ型の不一致、JSONシリアライゼーションエラー、ネットワークタイムアウトの不正な処理、RoomやCore Dataを通じた並行データベースアクセスの失敗があります。統合テストがなければ、こうした缺陷がプロダクションに到達し、実際のユーザーだけに現れます。
Google Testing Blog (2021)の研究によると、統合テスト中に発見された缺陷を修正するコストは、リリース後の5分の1です。これは、初期ステージでは開発者がエラーの全文脈を把握しており、緊急のホットフィックスサイクルなしで修正できるからです。統合テストの作成に時間を投資することは、保守コストの削減とユーザーの信頼性向上という形で優れた成果をもたらします。
統合テストには主なアプローチがあります:Big Bang、Bottom-Up、Top-Downです。戦略の選択は、プロジェクトの規模、アプリのアーキテクチャ、テスト作成時のコンポーネントの利用可能性に依存します。各アプローチには、テストカバレッジを計画する際に考慮すべき利点と制約があります。
Big Bang — システムのすべてのコンポーネントを同時に接続し、その後に総合テストを実行するアプローチです。この方法はシンプルで実装できます:stubsを書く必要や個々のモジュールをエミュレートする必要がありません。ただし、エラーが発見された場合、どのコンポーネントが原因かを特定するのが難しくなります。Big Bangは、モジュール数が5つを超えないシンプルなアーキテクチャの小規模プロジェクトに適しています。
Bottom-Up — 低レベルコンポーネント(データベース、ネットワークレイヤー、システムサービス)から統合テストを始める戦略です。各レベルの検証後、テストは渐次的に上位のモジュール(リポジトリ、Use Caseクラス、ViewModel)を接続します。主な利点は、アプリの基礎レイヤーでの缺陷の早期発見であり、開発後期における連鎖的なエラーのリスクを低減させます。
Top-Down — UI画面やナビゲーションなど上位コンポーネントからテストを始め、下位モジュールはstubsやmocksを使ってシミュレートするアプローチです。これにより、サーバーサイドやデータベースが完全に実装される前にユーザーシナリオを検証できます。Top-Downは、バックエンドがまだ実際の統合に準備できていない場合のクライアントとサーバーの並行開発に特に役立ちます。
モバイルアプリの統合テストには、専門ツールが使用され、サーバーエミュレーションライブラリ、データベースフレームワーク、システムサービス検証ツールの3つのカテゴリに分けられます。具体的なツールの選択は、プラットフォーム(AndroidまたはiOS)とプロジェクトのテクノロジースタックに依存します。
AndroidとiOSの統合テストの実践例を見てみましょう。AndroidプラットフォームではJUnitと組み合わせてMockWebServerを使用し、iOSではOHHTTPStubsライブラリとXCTestを使用します。どちらの例も、APIからデータを受け取り、ローカルリポジトリに保存するシナリオを検証します。
このテストは、エミュレートされたサーバーへのRetrofitリクエストが正しいJSONを返すこと、およびリポジトリが応答をドメインモデルに変換することを検証します。MockWebServerはリクエストを拦戮し、指定されたJSONを返し、その後テストは予期結果と実際の結果を比較します。
class UserRepositoryTest {
private val mockServer = MockWebServer()
@Before
fun setup() {
mockServer.start()
}
@Test
fun fetchUser_returnsCorrectData() {
val json = "{ \`"id\`": 1, \`"name\`": \`"Alice\`" }"
mockServer.enqueue(MockResponse()
.setBody(json)
.setResponseCode(200))
val repository = UserRepository(
createRetrofit(mockServer.url("/").toString()))
val user = repository.fetchUser(1)
assertEquals(1, user.id)
assertEquals("Alice", user.name)
}
@After
fun tearDown() {
mockServer.shutdown()
}
}
iOSでは、同様のテストでOHHTTPStubsを使用してURLリクエストを拦戮します。このライブラリは、URL Loading Systemのシステムフレームワークレベルでサーバー応答を置き換えるため、あらゆるネットワークライブラリ(URLSession、Alamofire、Moya)をテストできます。
import XCTest
import OHHTTPStubs
import OHHTTPStubsSwift
class UserRepositoryTests: XCTestCase {
func testFetchUser_returnsCorrectData() {
stub(condition: isPath("/users/1")) { _ in
return HTTPStubsResponse(
jsonObject: ["id": 1, "name": "Alice"],
statusCode: 200,
headers: nil
)
}
let repository = UserRepository()
let expectation = expectation(description: "fetch user")
repository.fetchUser(id: 1) { user in
XCTAssertEqual(user.id, 1)
XCTAssertEqual(user.name, "Alice")
expectation.fulfill()
}
waitForExpectations(timeout: 2.0)
}
}
効果的な統合テストには、テストの穏定性を高め、保守コストを削減するプラクティスの守りが必要です。外部依存をアイソレートする:プロダクションインスタンスの代わりにインメモリデータベースを使用し、サードパーティAPIをstubライブラリでエミュレートします。これにより、ネットワークの利用可能性や外部サービスの状態による非検定的な障害を除去できます。
テストの独立性を維持する:各統合テストは、他のテストの結果に依存せずに独立して動作する必要があります。JUnitでは@Beforeや@After注釈を、XCTestではsetUpやtearDownを使用してテスト環境を準備およびクリーンアップします。これにより、テストの相互影響を防ぐことができ、エラー診断を簡素化できます。
エッジケースをカバーする:統合テストは、成功シナリオ(happy path)だけでなく、エラー処理—タイムアウト、HTTP 4xxおよび5xxコード、空の応答、正しくないJSONも検証する必要があります。Google Testing Blog (2022)によると、プロダクションインシデントの60%は、テストでかばりれていなかったエッジケースの不正な処理に関係しています。
よくある質問
単体テストは、依存関係をstubsで置き換えて、単一のクラスまたは関数をアイソレートして検証します。統合テストは、複数の実装コンポーネントの相互作用を検証します—例えば、ネットワーク接続とデータベースを同時に。
統合テストの実行には、テスト数と環境の複雑さによって通常2から15分かかります。大規模プロジェクトでは、マージ前の総検証時間を縮短するために、テストをCIシステムの並列ジョブに分けることが推奨されます。
まず、統合テストはネットワークレイヤー、データベース、システムサービス(通知、カメラ、地理情報)に対して書かれます。バックエンドへのAPIリクエストとローカルストレージ操作は最高のROIを提供します。なぜなら、これらのコンポーネントがリグレッションの原因となることが多いからです。
単一画面には、ViewModelの単体テストとUIテストで十分です。単一画面に対する統合テストは、画面が複数のデータソースと相互作用する場合にのみ理由があります—例えば、2つの異なるAPIからの応答を組み合わせたり、ネットワークとローカルデータベースの両方に同時にデータを書き込んだりする場合。
統合テストは、各pull requestでCIパイプライン内で、およびメジャーリリースの前に実行する必要があります。また、夜間(nightly build)に統合テストの全セットを実行し、依存関係やテスト環境の変更による缺陷を検出することも推奨されます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。