【Swift/Xcode】XCTestの使い方!Unit Test(単体テスト)の作成方法

この記事からわかること
- SwiftのXCTestフレームワークとは?
- XcodeでUnit Test(単体テスト)を作成する方法
- iOSアプリ開発におけるテストとは
- 「UI Test(User Interfaceテスト)」と「Unit Test(単体テスト)」の違い
- @testableとは?
- アサーション(検証関数)の種類
index
[open]
\ アプリをリリースしました /
iOSアプリ開発においてなかなか学べなかったテストについてまとめていきたいと思います。
今回は以下のような流れでまとめているので興味のあるところまで飛ばしながら読んでください。
- 開発におけるテストとは?
- iOSアプリとテスト
- XCTestフレームワーク
- Xcodeプロジェクトへのテストの導入方法
- Unit Testの実装方法
アプリ開発におけるテストの必要性
アプリ開発に限った話ではありませんが何かしらのプログラムを作る際には作成したプログラムが正しく動作しているかテストすることが重要になります。
テストを行うメリット
- バグを発見する
- UXの向上
- 修正コストの削減
- メモリやCPUへの負荷を検証
- アプリケーションの処理速度を知れる
事前にテストを行うことでユーザーが使用している際におかしな挙動を引き起こす可能性を減らし、アプリの信頼性や使い心地を向上させることができます。
iOSアプリ開発のテスト
iOSアプリ開発におけるテストは統合開発環境であるXcode内にあらかじめ用意されている様々な種類のテストを使用することで簡略化されています。
またXcode内からメモリやCPUの使用状況を事前準備を必要なく簡単に確認することができるのでアプリケーションの速度や負荷といった性能もテストすることが可能になっています。

アプリの性能ではなく機能的なテストにおいてXcodeは「UI Test(User Interfaceテスト)」と「Unit Test(単体テスト)」の2種類に分かれます。
UI Test(User Interfaceテスト)
UI Test(User Interfaceテスト)はiOSアプリのユーザーインターフェース(UI)をテストするためのテスト方法です。実際のユーザーが行う操作をシミュレートしながらアプリケーションの正常な動作を確認することが目的になります。
例えば以下のようなテストを行います。
- ボタンタップ時の画面遷移が正しいか
- フォームに入力された情報を正しく操作できているか
- APIで取得したデータを正しく表示できているか
Unit Test(単体テスト)
Unit Test(単体テスト)は個々のユニット(メソッド、関数、クラスなど)が期待どおりに動作するかどうかを確認するためのテスト方法です。
実装した処理自体が間違っていないか、異なる結果を出力しないかを個々にチェックすることが目的になります。
例えば以下のようなテストを行います。
- 金額の計算をするメソッドの結果が期待通りかどうか
- ファイルからデータを読み込む関数が正しく動作するか
- クラスのイニシャライザが期待通りに動作するか
別ターゲットとして管理される
iOSアプリのテストはXcodeプロジェクト内に別のターゲットとして実装されるのでテスト対象のコードとは別にビルドされます。テスト結果は、Xcodeのテストナビゲーターペインに表示され、成功、失敗、スキップなどの結果が分かります。

SwiftとObjective-Cの両方をサポートしており、XCTestフレームワークを使用してテストを作成します。
XCTestフレームワーク
XCTestフレームワークはUI Test、Unit Test、性能テストなどを作成、実行するための機能を提供するフレームワークで、主に2つのクラスが重要になってきます。
XCTestクラス
XCTestクラスはテストを作成、管理、実行するための抽象的な基本クラスです。このクラスを継承して様々なテストクラスが提供されています。
XCTestCaseクラス
XCTestCaseクラスはテストケースの基本クラスです。XCTestCaseを継承して、単体テストやUIテストなど、様々なテストケースを作成できます。テストケースのセットアップやクリーンアップのためのメソッド、アサーションを行うためのメソッドが用意されています。テストメソッドは、メソッド名がtest
で始まる必要があります。
プロジェクト作成時からテストを導入する
UI TestやUnit Testはプロジェクトを作成時または作成後に導入する必要があります。
プロジェクト作成時からテストを導入するには新規プロジェクト作成時の以下の画面の際に「Include Tests」にチェックを入れるだけです。

チェックを入れて作成されたプロジェクトには2つのテストターゲットが追加されていることがナビゲータエリアから確認できます。

プロジェクト作成後からテストを導入する
プロジェクト作成後からテストを導入するためにはXcodeの上部メニューから「File」 > 「New」 > 「Target...」をクリックし、Test内の「UI Testing Bundle」または「Unit Testing Bundle」を選択して「NEXT」をクリックします。

テストターゲット名などを設定できるので変更したければ任意の設定に変更して「NEXT」をクリックします。

ナビゲータエリアにテストターゲットが表示されていれば導入は成功です。

Unit Test(単体テスト)の作成方法
今回はUnit Test(単体テスト)を実際に作成してみます。テストターゲットを追加すると「[プロジェクト名]Tests.swift」というファイルが作成され、中には以下のようなコードが記述されています。
import XCTest
@testable import Test
final class TestTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
// Any test you write for XCTest can be annotated as throws and async.
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
measure {
// Put the code you want to measure the time of here.
}
}
}
- setUpWithErrorメソッド:テスト開始前に実行
- tearDownWithErrorメソッド:テスト終了後に実行
- testExampleメソッド:テスト用メソッド
- testPerformanceExampleメソッド:パフォーマンステスト用メソッド
- measure:時間を計測したいコードを記述
testExample
メソッドはデフォルトで用意されているメソッド名なので任意のものに置き換えて使用しても問題ありません。その際には接頭辞にtest
を付与する必要があります。(例:testInputなど)
実装の手順
- テスト対象コードの準備
- テストコードの作成
- (Membershipの登録)→@testable importでOK
- テストの実行
テスト対象コードの準備
今回は単純な単体テストとして「文字列を数値に変換できるかチェックする関数」が正しく動作するか確認してみます。
struct ContentView: View {
static func canConvertToNumber(_ string: String) -> Bool {
return Double(string) != nil
}
var body: some View {
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
テストコードの作成
続いてテストコードを作成します。テストコードは「[プロジェクト名]Tests.swift」内に新しく追加していきます。接頭辞にtest
をつけてテスト内容がわかりやすい名前を意識してメソッド名をつけます。
func testCanConvertToNumber() throws {
XCTAssertEqual(ContentView.canConvertToNumber("123"),true)
XCTAssertEqual(ContentView.canConvertToNumber("-123"),true)
XCTAssertEqual(ContentView.canConvertToNumber("0.5"),true)
XCTAssertEqual(ContentView.canConvertToNumber("123a"),false)
XCTAssertEqual(ContentView.canConvertToNumber(""),false)
}
実際のテストコードにはアサーション(検証関数)を使用します。詳細は後述しますので次に進みます。
(Membershipの登録)→@testable importでOK
プロジェクトとテストはターゲットが異なるので別ターゲットのコードを参照できるようにする必要があります。Membershipの登録をして参照する方法が紹介されていることが多いですが、@testable
を使用することで別ターゲットのコードを参照することが可能です。
@testable import [プロジェクト名]
「[プロジェクト名]Tests.swift」内には最初から記載されているので特に何もしなくても良いですが、記載がない場合や新規でテストファイルを追加した場合は忘れずに記述しておきます。
新規テストファイルの追加はXcode上部メニュー>「File」>「New」>「File...」から「Unit Test Case Class」を追加します。

Membershipを使用して登録したい場合はプロジェクトを追加すると「No such module 'XCTest'」というエラーが発生することがあるので以下の記事を参考にしてください。

テストの実行
これで準備が整ったのでテストを実行してみます。テストごとに「開始ボタン」が左側に表示されるのでクリック、もしくはCommand + U
で実行できます。

テストが期待通りに実行できると以下のように表示されます。

@testableとは?
@testable
属性はモジュールの内部インターフェース(関数、メソッド、クラスなど)へのアクセスを可能にする属性です。通常別ターゲットにあるファイル内部のコードを参照することはできませんが、@testableを使うことでテストで使用する目的としてコードにアクセスできます。
@testable import モジュール名
Xcodeにおけるモジュールとは?
今回利用するモジュール名は「プロジェクト名」です。そもそもモジュールとはコードの集合体のことでありXcodeではライブラリやパッケージ、その他のアプリケーションのことを指します。
Xcodeではターゲットもモジュールと見なすのでimport
するのはターゲット名になります。
アサーション(検証関数)の種類
アサーションとは「断言」などの意味を持つ英単語でプログラミングにおいては「検証する仕組み」のことを指します。
Swiftでも検証時に実行エラーを発生させるassert
メソッドなどが用意されていますがXCTestでは独自の検証関数が複数用意されています。
- XCTAssertEqual:2つの値が等しいことをアサート
- XCTAssertNotNil:変数がnilではないことをアサート
- XCTAssertTrue:条件が真であることをアサート
- XCTAssertFalse:条件がfalseであることをアサート
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。