#はじめに
ユニットテストを書いていると、
プロダクトコード同様、可読性が悪くなってきますよね。
ユニットテストの「Four Phase Test」という考え方について、
はじめて知ったので簡単にまとめます。
「Four Phase Test」とは、下記のような4つの手順に分けて、
実施する方法です。
No. | フェーズ | 説明 |
---|---|---|
1. | Setup | 前処理 |
2. | Exercise | 実行 |
3. | Verify | 検証 |
4. | TearDown | 後処理 |
可読性が悪くなる原因
テスト毎の前提条件や、後処理など、
検証以外のテストコードが増えてきたときに、
どこが実行部分で、どこが検証部分か分かりづらくなりがちです。
解決策の方針
そこで、「Four Phase Test」の手順に則って、
テストコードを実装してみる。
(他にもあれば、ご教授願います。)
ユニットテスト全体で共通のSetupとTearDownは、意識していましたが、
ExerciseとVerifyは、意識できていませんでした。
リファクタリング対象のテストコード
コード量も長くなり、可読性が落ちています。
import XCTest
@testable import Warikan
class WarikanModelTests: XCTestCase {
let usecase = WarikanUsecase()
let delegete = SpyWarikanDelegate()
override func setUp() {
super.setUp()
usecase.delegate = delegete
}
override func tearDown() {
super.tearDown()
usecase.delegate = nil
}
func test10000div3() {
let exp = expectation(description: "10000円を3人で割り勘したときのテスト")
delegete.asyncExpectation = exp
usecase.calc(totalAmountStr: "10000", numberOfPeopleStr: "3")
waitForExpectations(timeout: 1) { error in
if let error = error {
XCTFail("waitForExpectationsエラー: \(error)")
}
switch self.delegete.result! {
case .success(let bugetStr):
XCTAssertEqual(bugetStr, "1人辺りの支払い額は、3,400円です。")
case .error(_) :
XCTFail("テスト失敗")
}
}
}
}
リファクタリング①
コメントで、4つのフェーズに分けます。
一旦、グルーピングは出来ました。
但し、別の前提条件のテストをするときなど、冗長になることもあります。
/* test10000div3は、割り切れないときのテストを実行します。
* Check: 10000 / 3 = 3400.
*/
func test10000div3() {
// 1. Setup
let exp = expectation(description: "10000円を3人で割り勘したときのテスト")
delegete.asyncExpectation = exp
// 2. Exercise
usecase.calc(totalAmountStr: "10000", numberOfPeopleStr: "3")
// 3. Verify
waitForExpectations(timeout: 1) { error in
if let error = error {
XCTFail("waitForExpectationsエラー: \(error)")
}
switch self.delegete.result! {
case .success(let bugetStr):
XCTAssertEqual(bugetStr, "1人辺りの支払い額は、3,400円です。")
case .error(_) :
XCTFail("テスト失敗")
}
}
//4. TearDown
}
リファクタリング②
ヘルパーメソッドで、4つのフェーズに分けます。
ヘルパーメソッドの切り出すと、
リファクタリング①で上げた冗長な記載はなくなりました。
合わせて可読性も上がりましたね。
/* test10000div3は、割り切れないときのテストを実行します。
* Check: 10000 / 3 = 3400.
*/
func test10000div3() {
// 1. Setup
whenWarikan(description: "10000円を3人で割り勘したときのテスト")
// 2. Exercise
usecase.calc(totalAmountStr: "10000", numberOfPeopleStr: "3")
// 3. Verify
verifyWarikanResult(bugetStr: "1人辺りの支払い額は、3,400円です。")
//4. TearDown
}
// 1. Setup
private func whenWarikan(description: String){
delegete.asyncExpectation = expectation(description: description)
}
// 2. Exercise
private func exerciseWarikan(totalAmountStr: String, numberOfPeopleStr: String) {
usecase.calc(totalAmountStr: totalAmountStr, numberOfPeopleStr: numberOfPeopleStr)
}
// 3. Verify
private func verifyWarikanResult(bugetStr: String) {
waitForExpectations(timeout: 1) { error in
if let error = error {
XCTFail("waitForExpectationsエラー: \(error)")
}
switch self.delegete.result! {
case .success(let bugetStr):
XCTAssertEqual(bugetStr, bugetStr)
case .error(_) :
XCTFail("テスト失敗")
}
}
}
まとめ
ユニットテストにも可読性が求められますね。
一つの方針として、「Four Phase Test」も推進していきたいです。
その他の語録
用語 | 説明 |
---|---|
Test | テストケース |
Suite | Testをまとめたもの |
SUT | 「System Under Test」の略で、テスト対象のこと |
Fixture | 個別の期待結果を生成するためのまとまり。「Setup」を何らかの形で特化させたものになる。 |