Edited at

ユニットテストにも可読性を持たせる (Four Phase Test)

More than 1 year has passed since last update.


はじめに

ユニットテストを書いていると、

プロダクトコード同様、可読性が悪くなってきますよね。

ユニットテストの「Four Phase Test」という考え方について、

はじめて知ったので簡単にまとめます。

「Four Phase Test」とは、下記のような4つの手順に分けて、

実施する方法です。

No.
フェーズ
説明

1.
Setup
前処理

2.
Exercise
実行

3.
Verify
検証

4.
TearDown
後処理


可読性が悪くなる原因

テスト毎の前提条件や、後処理など、

検証以外のテストコードが増えてきたときに、

どこが実行部分で、どこが検証部分か分かりづらくなりがちです。


解決策の方針

そこで、「Four Phase Test」の手順に則って、

テストコードを実装してみる。

(他にもあれば、ご教授願います。)

ユニットテスト全体で共通のSetupとTearDownは、意識していましたが、

ExerciseとVerifyは、意識できていませんでした。


リファクタリング対象のテストコード

コード量も長くなり、可読性が落ちています。


WarikanModelTests.swift

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つのフェーズに分けます。

一旦、グルーピングは出来ました。

但し、別の前提条件のテストをするときなど、冗長になることもあります。


WarikanModelTests.swift


/* 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つのフェーズに分けます。

ヘルパーメソッドの切り出すと、

リファクタリング①で上げた冗長な記載はなくなりました。

合わせて可読性も上がりましたね。


WarikanModelTests.swift

/* 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」を何らかの形で特化させたものになる。