開発本部APP・UXチームの玉置(@tamappe)と申します。主にiOSとAndroidの運用を担当しています。 担当しているのに最近は専らiOSのみを開発するようになりました。
Swift 3 からSwift 4.2 にリプレイスとリファクタリングした話しを紹介します。
rarejob-tech-dept.hatenablog.com
前回の記事を投稿してからちょうど2ヶ月が経ちました。 この2ヶ月間が非常に内容が濃過ぎて2ヶ月間やってきた内容を振り返るのは非常に難しいです。
また本日で入社から三ヶ月間が経ち研修期間が終わります。 この三ヶ月間でチームメンバーだけでなく様々なメンバーに支えて頂きましたのでとても快適に開発を進めることができています。 来月からはこれまで以上に頑張ってレアジョブアプリを支えていくつもりです。
今回はフリースタイルに文章を書いても良いということでiOSでのユニットテストコードの書き方について紹介したいと思います。 モバイルアプリ開発においてもう避けて通れなくなっているからですね!
Xcodeのユニットテストクラスを追加する
どのIDEでもそうですがほとんどの場合、アプリのプロジェクトファイルを作成すると標準のテストクラスが作成されます。 Xcodeの場合もオプションですがUnit TestをTarget に追加するかどうかは最初に決められます。
また、最初にオプションで設定せずにプロジェクトファイルを作成しても後からユニットテスト用のTargetを追加できます。
今回は便宜上UnitTestSample
というプロジェクトファイル名にします。
ユニットテストの書き方について
それでは実際にユニットテストを書いていきます。
Xcodeのユニットテストは命名に厳しくないためどのようなクラス名にしてもいいはずですが、
だいたいの命名規約としてテストするクラス名Tests
という命名になるかと思います。
今回はUnitTestSampleTests
というユニットテストクラスを使います。
クラスの作成時のUnitTestSampleTestsの中身は以下の通りです。
UnitTestSampleTests
import XCTest @testable import UnitTestSample // この行を入れないとテストができません。 class UnitTestSampleTests: XCTestCase { override func setUp() { // このユニットテストクラスを使う場合の初期設定 } override func tearDown() { // クラスのテストを終了した時に実行する内容 } func testExample() { // サンプルの関数 } func testPerformanceExample() { // パフォーマンスを測定する際に使うような関数 self.measure { } } }
今回は特に上記4つを使う場面はありませんので全て削除します。削除しても問題ありません。
UnitTestSampleTests
import XCTest @testable import UnitTestSample class UnitTestSampleTests: XCTestCase { }
試しにユニットテストをしたいのでテストするためのクラスを本番スキームで作成します。
計算する用のクラスとしてCalculation
クラスを作成します。
Calculation
import UIKit class Calculation: NSObject { func addNumber(a: Int, b: Int) -> Int { return a + b } func minusNumber(a: Int, b: Int) -> Int { return a - b } }
足し算用のメソッドaddNumber
と引き算用のメソッドminusNumber
を作りました。
それぞれのメソッドに引数a, b を設定します。
これらのメソッドが正常に動くかをテストしたいと思います。
UnitTestSampleTests
クラスを修正します。
import XCTest @testable import UnitTestSample class UnitTestSampleTests: XCTestCase { func test_addNumber() { let calculation = Calculation() let testable = calculation.addNumber(a: 1, b: 2) XCTAssertEqual(testable, 3) } func test_minusNumber() { let calculation = Calculation() let testable = calculation.minusNumber(a: 5, b: 1) XCTAssertEqual(testable, 4) } }
テストの命名は私の慣習でtest_テストする関数名
としていますがチーム開発の場合はある程度の規則で命名すれば良いと思います。
この後に「command + B」のショートカットを実行すると行数の部分にダイヤマークが出てきます。
ユニットテストの実行は「command + U」で実行できます。
とりあえず、何も考えずに「command + B」してから「command + U」をすればユニットテストができると思います。
ビルドが成功するとTest Succeeded
と表示されるはずです。
成功した後はダイヤマークが緑色に変わると思います。
これがユニットテストの基本形です。
iOSエンジニアでユニットテストに書き慣れていない方でしたら
上記コードで見慣れないのがXCTAssertEqual
メソッドかと思われます。
これはXCTAssertEqual(A, B)
という書き方をしまして、AとBの値が等しいかどうかを確認するメソッドです。
正しくない場合はテストが失敗して赤色のエラーが表示されます。
試しに
UnitTestSampleTests
import XCTest @testable import UnitTestSample class UnitTestSampleTests: XCTestCase { func test_addNumber() { let calculation = Calculation() let testable = calculation.addNumber(a: 1, b: 2) XCTAssertEqual(testable, 3) } func test_minusNumber() { let calculation = Calculation() let testable = calculation.minusNumber(a: 5, b: 1) XCTAssertEqual(testable, 3) } }
と修正してみて「Command + U」を実行してみましょう。minusNumber
に 5, 1 を入れました。
testableには4が入ってますがそれと期待値である3
が等しいかどうかの確認です。
当然、4と3は違いますのでテストが失敗します。
テストが失敗する場合は緑色から赤色になります。
エラーの内容はXCTAssertEqual failed: ("4") is not equal to ("3")
というような感じです。
これで失敗した内容がわかりました。
UnitTest のメソッドの種類について
上記ではXCTAssertEqual
のみを使用してきましたが、他にも代表的なテスト用のメソッドがあります。
- XCTAssertNil(A) (A が
nil
であるかどうかの確認。Aがnil
であれば成功) - XCTAssertNotNil(B) (Bが
nil
以外のオブジェクトかどうかの確認。Bがnil
でなければ成功) - XCTAssertTrue(C) (C が
true
であるかどうかの確認。Cがtrue
であれば成功) - XCTAssertFalse(D) (Dが
false
であるかどうかの確認。Dがfalse
で成功) - XCTAssertNotEqual(E, F) (EとFが等しくないかどうかの確認。EとFが等しくない時に成功)
- XCTFail() (意図していない挙動の場合にユニットテストを失敗させることができる)
だいたいのテストはこれだけで知りたい内容の確認ができるかなと思います。
import XCTest @testable import UnitTestSample class UnitTestSampleTests: XCTestCase { func test_addNumber() { let calculation = Calculation() let testable = calculation.addNumber(a: 1, b: 2) XCTAssertEqual(testable, 3) } func test_minusNumber() { let calculation = Calculation() let testable = calculation.minusNumber(a: 5, b: 1) XCTAssertEqual(testable, 4) } func test_nilObject() { var nilObject: Int? = nil XCTAssertNil(nilObject) nilObject = 1 XCTAssertNotNil(nilObject) var isSample = false XCTAssertFalse(isSample) isSample = true XCTAssertTrue(isSample) let testable = "Hello World" XCTAssertEqual(testable, "Hello World") } }
他のオブジェクトに関するテストについて
今までは単純な型のユニットでしたが次はオブジェクトのユニットテストについて紹介します。
Calculationクラスに新しい構造体 User
を作成します。
import UIKit struct User { let id: Int let name: String let number: Int } class Calculation: NSObject { var id: Int = 0 func addNumber(a: Int, b: Int) -> Int { return a + b } func minusNumber(a: Int, b: Int) -> Int { return a - b } func createSampleUser(name: String, a: Int, b: Int) -> User { let number = addNumber(a: a, b: b) let user = User(id: id, name: name, number: number) id += 1 return user } }
こちらのクラスの挙動を確認します。
import XCTest @testable import UnitTestSample class UnitTestSampleTests: XCTestCase { // 省略します。 // func test_sampleUser() { let calculation = Calculation() let firstUser = calculation.createSampleUser(name: "太郎", a: 1, b: 2) XCTAssertEqual(firstUser.id, 0) XCTAssertEqual(firstUser.name, "太郎") XCTAssertEqual(firstUser.number, 3) let secondUser = calculation.createSampleUser(name: "花子", a: 3, b: 6) XCTAssertEqual(secondUser.id, 1) XCTAssertEqual(secondUser.name, "花子") XCTAssertEqual(secondUser.number, 9) } }
これを書いた後に「command + U」をタップしてユニットテストを実行してみましょう。
これでテストが通れば意図通りです。
テストが通ったことが分かりました。
このようにカスタムなstruct
やclass
に対する挙動もユニットテストができるようになります。
レアジョブアプリで導入する予定のクラスについて
レアジョブアプリは2016年にリリースしてからおよそ3年が経過しています。 3年も経過するとコードのメンテナンスが必要である場面が出てきたり、 プラットフォームのAPIのアップデートにより一部分のメソッドが非推奨になる可能性もありえます。
今後レアジョブアプリでユニットテストを施していこうと思うクラスについては
- Utilityなどの便利クラス
- extensionクラス
- API通信で使うModelクラス
このようなクラスのテストを書いていこうと思います。 実際にテストコードを書いてみるとそのクラスのプロパティやメソッドの使い方と結果がわかって動作確認にも便利です。
ぜひ今回の記事を参考にしてユニットテストを書いてみてください。