これで迷わないテストダブルの分類(ダミー、スタブ、スパイ、モック、フェイク)

# はじめに

みなさんユニットテスト書いてますか？

私はユニットテストを書くとき、テストダブルを利用することがありますが、
スタブ、スパイなど、しっかり理解せずに利用しており、そろそろ理解しないとまずそうなので、ネットの海をさまよっていたところ、t_wadaさんのこんなツイートを見つけました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Mock, Stub, Spy, Fake 等は混乱してしまいがちなので、いつもこのエントリを紹介している / xUnit Test PatternsのTest Doubleパターン(Mock、Stub、Fake、Dummy等の定義) <a href="http://t.co/yLLsiA3H6v">http://t.co/yLLsiA3H6v</a></p>&mdash; Takuto Wada (@t_wada) <a href="https://twitter.com/t_wada/status/608573127879069696?ref_src=twsrc%5Etfw">June 10, 2015</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

このツイートで[紹介されていたブログ](https://goyoki.hatenablog.com/entry/20120301/1330608789)では、テストダブルの分類についてわかりやすく書かれていました。
今回はこの記事の内容を前提にして、大元である [xUnit Test Patterns]((http://xunitpatterns.com/index.html))のサンプルを用いて（少しアレンジしてます）、テストダブルについてまとめてみました。
（ただし、モックとフェイクについてのサンプルは書いていません。いいかんじに例をアレンジできませんでした。）

ユニットテストでなぜテストダブルが必要になるのかをみつつ、それぞれの分類を見ていきます。

# ユニットテスト

まずユニットテストですが、ソースコードの個々のユニット単位(メソッド、クラス、モジュールなど)のプログラムが正しい動きをするか検証する方法です。
以下は、自動テストの文脈でユニットテストを見ていきます。

## 簡単な例

簡単な例ですが、以下のようなプロダクトコードがあるとします。
（以下すべてKotlinのサンプルコードですが、他の言語でも読み変えられるはず。）

`Calculator`クラスは`squre`という与えられた数の平方を返すメソッドを持っています。

```kotlin
class Calculator {
    fun square(a: Int): Int = a * a
}
```

このメソッドのテストを書くとしましょう。
テストを書くにはまず、テストケースを考えます。
「2を与えると4を返す」ので、テストコードは以下になります。

```kotlin
class CalculatorTest {

    @Test
    fun `square_2の平方は4になる`() {
        // Given
        val calculator = Calculator()

        // When
        val actual = calculator.square(2)

        // Then
        assertEquals(4, actual)
    }
}
```

## テストダブルが欲しくなる例

次に、この例はどうでしょうか。

`TimeDisplay`という時間に関係する表示を行うクラスで、`getCurrentTimeAsHtmlFragment`メソッドを持っています。
そのメソッド内で、`TimeProvider`から取得した時間によって出力するHTMLタグの部品の内容が `Midnight`, `noon` などに変わります。

```kotlin
class TimeDisplay(private val timeProvider: TimeProvider) {
    fun getCurrentTimeAsHtmlFragment(): String {
        val now = timeProvider.getTime()

        val text = when {
            now.hour == 0 && now.minute == 0 -> {
                "Midnight"
            }
            now.hour == 12 && now.minute == 0 -> {
                "noon"
            }
            else -> {
                val f = DateTimeFormatter.ofPattern("yyyy年MM月dd日")
                now.format(f)
            }
        }
        return HTML_TEMPLATE.format(text)
    }

    companion object {
        private const val HTML_TEMPLATE = "<span class=\"tinyBoldText\">%s</span>"
    }
}

class TimeProvider {
    fun getTime(): LocalDateTime = LocalDateTime.now()
}
```


このメソッドのテストケースを書こうとしてみます。
「0:00のときMidnightを含むHTMLタグの部品を出力する」というテストケースを考えてみます。

```kotlin
class TimeDisplayTest {

    @Test
    @Ignore
    fun `testDisplayCurrentTime_0:00のときMidnightを含むHTMLタグの部品を出力する`() {
        // Given
        val timeProvider = TimeProvider()
        val sut = TimeDisplay(timeProvider)

        // When
        // TimeProviderがシステムの時間を返すため、実行時に値が変わる
        val result = sut.getCurrentTimeAsHtmlFragment()

        // Then
        val expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>"
        assertEquals(expectedTimeString, result)
    }
}
```

このテストケースは大抵うまくいきません。
システムの時間によって`TimeProvider`が返す時刻が違うため、**実行する時間によってテストケースの結果が変わってしまいます**。
`Midnight`であることを確かめるには、0:00にテストコードを実行するしかありませんが、あまり良いとは言えません。

テストの記述がうまくいかない理由は、この**テスト対象であるメソッドが、外部コンポーネント(TimeProviderクラス)に依存しているため**です。
この問題を回避する方法はいくつかありますが、テストダブルを利用することを考えます。

# テストダブル

テストダブルとは、テスト実行時に、**テスト対象が依存しているコンポーネントと置き換わるもの**です。

|テストコード| => |テスト対象| => |外部コンポーネント|

これを

|テストコード| => |テスト対象| => |テストダブル|

にします。

テストダブルの由来、かどうかはわかりませんが、[xUnit Test PatternsのTest Doubleの説明](http://xunitpatterns.com/Test%20Double.html)には以下の記述があり、映画のスタントマン(stunt double)の例えが用いられています。

> When the movie industry wants to film something that is potentially risky or dangerous for the leading actor to carry out, they hire a "stunt double" to take the place of the actor in the scene. The stunt double is a highly trained individual who is capable of meeting the specific requirements of the scene. They may not be able to act, but they know how to fall from great heights, crash a car, or whatever the scene calls for. How closely the stunt double needs to resemble the actor depends on the nature of the scene. Usually, things can be arranged such that someone who vaguely resembles the actor in stature can take their place.

## なぜ置き換えたいのか

上記の`TimeDisplay`のように、外部コンポーネントに依存していて、**その挙動をコントロールできない**ときや、実際のDBを操作したり、実行に時間がかかる処理など、**コスト的・時間的・環境的にテストで実行できない or しにくいもの**など、実行上の制約があるものを、テストダブルで置き換えます。

## テストダブルの分類

ネットを調べると、テストダブルの定義や分類は、いろいろあるようですが、ここではxUnit Test Patternsの定義、分類を見ていきます。

[xUnit Test Patterns](http://xunitpatterns.com/index.html)とは、テスト自動化フレームワークのXUnit（JUnitなど）を使用して、自動テストを作成するためのパターン、もしくはその[書籍](https://www.amazon.co.jp/dp/B004X1D36K/ref=cm_sw_em_r_mt_dp_dB13FbKE8TM8X)のことです。

[xUnit Test Patterns](http://xunitpatterns.com/index.html)での分類は以下の5つです。

- ダミー(Dummy Object)
- スタブ(Test Stub)
- スパイ(Test Spy)
- モック(Mock Object)
- フェイク(Fake Object)

## 間接入力(indirect input)と間接出力(indirect output)

テストダブルの5つそれぞれが間接入力(indirect input)、間接出力(indirect output)を理解しているとわかりやすいため、[紹介されていたブログ](https://goyoki.hatenablog.com/entry/20120301/1330608789)を見てください。

## モックライブラリ
テストダブルをどう作るかですが、テストダブルに置き換えたいクラスを**インターフェースや抽象クラスに変更して置き換える方法**などがあります。
しかし、テストダブルを作るために大元のプロダクトコードを修正したり、テストごとに都度用意するのは面倒なので、大抵は**モックライブラリ**を利用します。
以下では [MockK](https://github.com/mockk/mockk) というKotlinのモックライブラリを利用します。（他の言語でも利用方法は似ていたりします。）

## Dummy Object

http://xunitpatterns.com/Dummy%20Object.html

Dummy Objectは、**テスト対象内でなにもしませんが、コンパイルなどの都合上、用意する必要があるもの**です。

例を見ます。
以下のような`Invoice`(請求書)クラスがあり、`Product`(製品)とそのquantity(数量)を`LineItem`(行)として`lineItems `リストで保持しています。
`addItemQuantity`メソッドで`LineItem`を保存して、`getLineItems`メソッドで今保持している`LineItem`のリストを返します。

```kotlin
class Invoice(customer: Customer) {
    private var lineItems: MutableList<LineItem> = mutableListOf()

    fun addItemQuantity(product: Product, quantity: Int) {
        lineItems.add(LineItem(this, product, quantity))
    }

    fun getLineItems(): List<LineItem> {
        return lineItems.toList()
    }
    
    // ... customerを利用するコードなど
}

class Customer(uniqueNumberAsString: String, uniqueNumber: Int, address: Address)
class Address(address: String, city: City, postalCode: String)
class City(name: String, state: State)
class State(name: String, abbreviation: String)
```

`LineItem`を1つ追加して、保持している`LineItem`の数と内容が同じかを検証するテストケースを考えます。

```kotlin
class InvoiceTest {

    @Test
    fun `LineItemを1つ追加して、保持しているLineItemの数と内容が同じ`() {
        // Given
        val QUANTITY = 1
        val product = Product(getUniqueNumberAsString(), getUniqueNumber())

        // Customerを作るのに必要
        val state = State("West Dakota", "WD")
        val city = City("Centreville", state)
        val address = Address("123 Blake St.", city, "12345")
        val customer = Customer(getUniqueNumberAsString(), getUniqueNumber(), address)

        // Invoiceイスタンスを作るのにCustomerが必要だが、テスト対象にCustomerは不要
        val inv = Invoice(customer)

        // When
        inv.addItemQuantity(product, QUANTITY)

        // Then
        val lineItems: List<LineItem> = inv.getLineItems()
        assertEquals("LineItemの数が同じか", lineItems.size, 1)

        val actual: LineItem = lineItems[0]
        val expItem = LineItem(inv, product, QUANTITY)
        assertEquals("LineItemが同じか", expItem, actual)
    }
}

private fun getUniqueNumberAsString() = "1"
private fun getUniqueNumber() = 1
```

このテストコードは通ります。
しかし、`Invoice`のインスタンスを作るのに`Customer`が必要になっていますが、このテスト対象のメソッド内では`Customer`を利用しておらず、不要になっています。
他にも`Customer`, `Address`, `City`, `State`というクラスもあり、これはすべて`Customer`インスタンスを作るためにテストコード内で実体を作っていますが、どれもテスト対象には不要なものです。

これをダミーオブジェクトに切り変えます。

```kotlin
class InvoiceTest {

    @Test
    fun `LineItemを1つ追加して、保持しているLineItemの数と内容が同じ`() {
        // Given
        val QUANTITY = 1
        val product = Product(getUniqueNumberAsString(), getUniqueNumber())

        // Customerをモックライブラリ(mockK)でダミーに切り替える
        val customerDummy: Customer = mockk()
        val inv = Invoice(customerDummy)

        // When
        inv.addItemQuantity(product, QUANTITY)

        // Then
        val lineItems: List<LineItem> = inv.getLineItems()
        assertEquals("LineItemの数が同じか", lineItems.size, 1)

        val actual: LineItem = lineItems[0]
        val expItem = LineItem(inv, product, QUANTITY)
        assertEquals("LineItemが同じか", expItem, actual)
    }
}
```

`mockk()` で`Customer`のダミー（テストダブル）を作成しました。
不要なインスタンス化部分のコードをなくすことができました。

## Test Stub

http://xunitpatterns.com/Test%20Stub.html

Test Stubとは、**間接入力を操作し、任意の値をテスト対象に与えるもの**です。
テストコード上で、外部コンポーネントからの入力を事前設定し、テストを実行します。

Test Stubの例は、先出の`TimeDisplay`で見ていきます。
冗長になってしまいますが、再掲します。

```kotlin
class TimeDisplay(private val timeProvider: TimeProvider) {
    fun getCurrentTimeAsHtmlFragment(): String {
        val now = timeProvider.getTime()

        val text = when {
            now.hour == 0 && now.minute == 0 -> {
                "Midnight"
            }
            now.hour == 12 && now.minute == 0 -> {
                "noon"
            }
            else -> {
                val f = DateTimeFormatter.ofPattern("yyyy年MM月dd日")
                now.format(f)
            }
        }
        return HTML_TEMPLATE.format(text)
    }

    companion object {
        private const val HTML_TEMPLATE = "<span class=\"tinyBoldText\">%s</span>"
    }
}

class TimeProvider {
    fun getTime(): LocalDateTime = LocalDateTime.now()
}
```

このメソッドのテストケースを書こうとしてみます。
「0:00のときMidnightを含むHTMLタグの部品を出力する」というテストケースを考えてみます。

```kotlin
class TimeDisplayTest {

    @Test
    @Ignore
    fun `testDisplayCurrentTime_0:00のときMidnightを含むHTMLタグの部品を出力する`() {
        // Given
        val timeProvider = TimeProvider()
        val sut = TimeDisplay(timeProvider)

        // When
        // TimeProviderがシステムの時間を返すため、実行時に値が変わる
        val result = sut.getCurrentTimeAsHtmlFragment()

        // Then
        val expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>"
        assertEquals(expectedTimeString, result)
    }
}
```

このテストケースでは、`TimeProvider`がシステムの時間を返すため、実行時に値が変わってしまい、テストがうまくいきませんでした。
このテストケースは、`TimeProvider` **が返す特定の時間によって、HTMLタグの内容が変わるロジックを検証することが目的**なので、`TimeProvider`が返す値（間接入力）を操作するために、Test Stubで`TimeProvider`を置き換えます。

```kotlin
class TimeDisplayTest {

    @Test
    @Ignore
    fun `testDisplayCurrentTime_0:00のときMidnightを含むHTMLタグの部品を出力する`() {
        // Given
        // timeProvider.getTime()からの入力をStubに置き換える
        val timeProviderStub: TimeProvider = mockk()
        every { timeProviderStub.getTime() } returns LocalDateTime.of(
                2020,
                1,
                1,
                0,
                0)
        val sut = TimeDisplay(timeProvider)

        // When
        // TimeProviderがシステムの時間を返すため、実行時に値が変わる
        val result = sut.getCurrentTimeAsHtmlFragment()

        // Then
        val expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>"
        assertEquals(expectedTimeString, result)
    }
}
```

`every { ... } returns ~` の部分は、mockKの書き方ですが、 `timeProvider.getTime()` が実行された時に、間接入力として、常に `LocalDateTime.of(2020, 1, 1, 0, 0)` を返すようにしています。

Test Stubに置き換えたことによって、外部コンポーネントからの入力をコントロールでき、本来確かめたいロジックのテストコードが書けるようになりました。


## Test Spy

http://xunitpatterns.com/Test%20Spy.html

Test Spyは、**テスト対象の間接出力を記録**します。
テスト対象で呼び出している外部モジュールの出力を記録して、テストコード上で、その間接出力を検証します。

フライトの予約窓口の例を考えます。
`FlightManagementFacade`クラスは、`reserveFlight`でフライトを予約し、`cancelFlight`でフライトをキャンセル、`confirmFlight`でフライトが予約済みか確認するメソッドを持っています。

`FlightManagementFacade` は、`FlightDao`の外部コンポーネント(クラス)を持っており、 `FlightDao` を通して、フライト業務をこなします。

ここで、簡略化のため、`FlightDao`自身で予約済みのフライト番号(flightNumbers)を保持するようにしていますが、実際はDBを操作するコードが書かれていたりします。

```kotlin
class FlightManagementFacade(
        private val dao: FlightDao
) {

    fun reserveFlight(flightNumber: Int) {
        // 外部メソッド
        dao.saveFlight(flightNumber)
    }

    fun cancelFlight(flightNumber: Int) {
        // 外部メソッド
        dao.removeFlight(flightNumber)
    }

    fun confirmFlight(flightNumber: Int): Boolean {
        return dao.hasFlight(flightNumber)
    }
}

class FlightDao {
    private val flightNumbers: MutableSet<Int> = mutableSetOf()

    fun removeFlight(flightNumber: Int) {
        flightNumbers.remove(flightNumber)
    }

    fun saveFlight(flightNumber: Int) {
        flightNumbers.add(flightNumber)
    }

    fun hasFlight(flightNumber: Int): Boolean {
        return flightNumbers.contains(flightNumber)
    }
}
```

`FlightManagementFacade`の`reserveFlight`、`cancelFlight`メソッドを何度か呼び出して、ちゃんと予約したフライトを保持しているか検証するテストケースを考えていみます。

```kotlin
class FlightManagementFacadeTest {

    @Test
    fun testRemoveFlight_2つ別のフライトを予約して、1つをキャンセルしたときに、1つはキャンセル、1つは予約済みにされているか() {
        // Given
        val flightNumber1 = 1
        val flightNumber2 = 2

        val flightDao = FlightDao()
        // テスト対象
        val facade = FlightManagementFacade(flightDao)

        // When
        facade.reserveFlight(flightNumber1)
        facade.reserveFlight(flightNumber2)
        facade.cancelFlight(flightNumber1)

        // Then
        // flightDaoの間接出力を検証できていない
        assertFalse("flight1 should not exist after being removed",
                facade.confirmFlight(flightNumber1)
        )
        assertTrue(facade.confirmFlight(flightNumber2) , "flight2 should exist")
    }
}
```

このテストはうまく通りますが、よくよくみると、本当に`FlightDao`のメソッドを呼んでいるのか、意図したflightNumberを渡しているのかどうかは検証できていません。
もしかしたら、`FlightDao`がflightNumber2を元から保持していたかもしれませんし、flightNumber1自体を`reserveFlight`でも`cancelFlight`でも実際には`FlightDao`に渡しておらず、操作していないかもしれません。
`FlightDao` がちゃんと値を保持しているかどうかは`FlightDao`自体のテストかと思いますが、`FlightManagementFacade` が、`reserveFlight`や`cancelFlight`メソッド内で、`FlightDao`のメソッドを呼んでいるかどうか（外部コンポーネントを呼ぶ責務を果たしているかどうか）を検証するためには、Test Spyを利用するように修正します。

※自分で少し例をアレンジしていますが、あまり良い例になっていないかもしれません。。テストケース次第なので、あくまでTest Spyの例だと思ってください。


```kotlin
class FlightManagementFacadeTest {

    @Test
    fun `testRemoveFlight_FlightManagementFacadeがFlightDAOのメソッドを呼び出しているか`() {
        // Given
        val flightNumber1 = 1
        val flightNumber2 = 2

        // TestSpyにする
        val flightDaoSpy = spyk<FlightDao>()

        // テスト対象
        val facade = FlightManagementFacade(flightDaoSpy)

        // When
        facade.reserveFlight(flightNumber1)
        facade.reserveFlight(flightNumber2)
        facade.cancelFlight(flightNumber1)

        // Then
        // FlightDaoのメソッド呼び出しと渡した引数(間接出力値)を検証
        verify(exactly = 1) { flightDao.saveFlight(flightNumber1) }
        verify(exactly = 1) { flightDao.saveFlight(flightNumber2) }
        verify(exactly = 1) { flightDao.removeFlight(flightNumber1) }
        verify(exactly = 0) { flightDao.removeFlight(flightNumber2) }
    }
}
```

`val flightDaoSpy = spyk<FlightDao>()` で`FlightDao`をTest Spyにして、メソッドの呼び出しやその渡された引数を記録させます。
`verify(exactly = 1) { flightDao.saveFlight(flightNumber1) }` でメソッドの呼び出し回数と、渡された引数を検証しています。

## Mock Object

http://xunitpatterns.com/Mock%20Object.html

Mock Objectは、テスト対象の間接出力の期待結果(間接出力がこうなって欲しいというものを渡す)と、間接出力を保持します。
間接出力を確保できたら、Mock Object内で間接出力と、間接出力の期待結果を比較検証して、成功か失敗かを判定します。

Test Spyとかなり似ていますが、Mock Objectは、**Mock Object内で間接出力の結果を評価**します。
Test Spyは、間接出力を保持するだけで、その評価はテストコード側で行います。

どう使い分けるかですが、テストダブル内でしかアクセスできない情報がある場合は、Test Spyではなく、Mock Objectを使います。

## Fake Object

http://xunitpatterns.com/Fake%20Object.html

Fake Objectはテスト実行中、本物と同じように動くが、本物ではないものです。
例としては、SQLiteに対し、同じ機能を持ったオンメモリデータベースなどが該当します。

# その他注意

テストダブル を利用する上で、注意すべきことがあります。

## 「モックする」という言葉
**テストダブルを作る行為**を単に **モックする** と言うことがあります。この場合、Mock Objectだけでなく、Test Stubなど他のテストダブルに対しても、「モックする」と言います。

## 「スタブのテスト」のような意味のないユニットテスト

「スタブのテスト」のような意味のないユニットテストを書いてしまうことがあるので注意です。
自分で外部コンポーネントの入力を操作したのに、その**外部コンポーネントの入力値自体**を検証するコードは意味がありません。
その場合、テスト対象で検証したいのは、**外部コンポーネントの値によって挙動が決まるロジック**のはずです。

## テストダブルが多くなってしまう場合はそもそもの設計を疑え

テストコードで、テストダブルを多く利用するということは、それだけ依存するものが多いと言うことなので、そもそものプロダクトコードの設計が良くない場合があります。

ただし、「リファクタリングのために、プロダクトコードをいきなり修正せず、まずテストコードを入れる時」など、仕方のない場面もあります。

## モックライブラリによっては、どのテストダブルを生成するか特に明示しない

上記MockKを利用した例で、Dummy Object, Test Stub共に、 `mockk()` でテストダブルを作っただけで、「dummy, stubとして生成する」といった指定はしませんでした。
利用する側で上記のテストダブルの分類（何が目的か)を意識すべきですが、モックライブラリでは分類を意識したテストダブルの生成ではないようです。（他のモックライブラリもそうなっているのかはわかりません。）

# まとめ

上で見てきたことをまとめると以下になります。

- xUnit Test Patternsによるテストダブルの分類がある
- スタブは間接入力を操作する
- スパイは間接出力を記録する
- ダミーは何もしない
- モックは間接出力の期待結果を持ち、中で評価する
- フェイクは限りなく本物に近いけど違うもの
- モックライブラリを使いましょう

テストダブルはとても便利ですが、意識して使わないとテストコードを無駄に複雑にしてしまうことになるので、それぞれのテストダブルが何を目的にしているのか十分理解して利用するようにしましょう。

# 参考

- [xUnit Patterns.com](http://xunitpatterns.com/index.html)
- [xUnit Test PatternsのTest Doubleパターン(Mock、Stub、Fake、Dummy等の定義)](https://goyoki.hatenablog.com/entry/20120301/1330608789)
- [Androidテスト全書](https://peaks.cc/books/android_testing)