緑の勇者で学ぶユニットテストのテストダブル

# プロローグ

昔々、SWテストは全てのユニットの結合の後に始めるものでした。

![6TinyBugs.png](https://qiita-image-store.s3.amazonaws.com/0/199141/81455e2d-21f7-a993-bf4d-489736ff2588.png)

![HugeBug.png](https://qiita-image-store.s3.amazonaws.com/0/199141/be571290-4180-7af5-d5b4-0f76ecca01e1.png)

しかし、バグが見つかっても、原因がどのユニットなのか
突き止めるのが難しく、多くの戦士たちが倒れて行きました。

![LoseAgainstHugeBug.png](https://qiita-image-store.s3.amazonaws.com/0/199141/21388d6e-bd6b-e9c2-acbb-8cc440417cf6.png)

しかしある時、緑の衣を纏った勇者があらわれました。
彼は結合前にソースコードの個別の単位(Unit)に対してテストすれば、
バグの原因を追う時間が減る事を発見しました(Unit Test)。

![GreenHero.png](https://qiita-image-store.s3.amazonaws.com/0/199141/9e777416-bb47-383e-b6b1-87c783a29c4d.png)

マスターソード乱麻を切るとは正にこの事です。

# 単純なユニットテスト

その伝承で語られるのは何も関数を呼ばず、
引数だけで制御がわかれる以下の様な処理に対するテストです。

```python
class Legend:

    def is_princess(self, name):

        # Actually, Malon is not a princess

        if name in ['Zelda', 'Seek', 'Ruto', 'Malon']:
            return True
        else:
            return False

```

is_princess は名前を引数として取り、王女様か否かを判定するメソッドです。
王女ではないマロンの時もTrueが返ってしまうバグが入っています。

これは以下の単体テストコードで評価できます。

```python
import unittest # python's built-in unit test frame work

#
# snip
#

class LegendTest(unittest.TestCase):

    def test_is_princess(self):
        legend = Legend()
        self.assertEqual(True,  legend.is_princess('Zelda'))
        self.assertEqual(True,  legend.is_princess('Seek'))
        self.assertEqual(False, legend.is_princess('Saria'))
        self.assertEqual(True,  legend.is_princess('Ruto'))
        self.assertEqual(False, legend.is_princess('Malon'))

if __name__ == "__main__":
    unittest.main()
```

実行すると、以下の通りマロンの判定が間違っていることが検出できます。

```console
F
======================================================================
FAIL: test_is_princess (__main__.LegendTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "LegendTest_Simple.py", line 12, in test_is_princess
    self.assertEqual(False, legend.is_princess('Malon'))
AssertionError: False != True

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)
```

# テストダブルの登場

しかし、現実には他クラスのメソッド呼び出し結果を元に
制御が変わることがあります。

```python
class Legend:

    def __init__(self):
        self.cv_actor  = ''
        self.cv_volume = 100

        self.navi = Navi()


    def set_cv(self):
        name = self.navi.ask('Hey! Listen! What is your name?')

        if name == 'Link':
            self.cv_actor = 'Hayami'
        else:
            self.cv_volume = 0
```

ここでNaviはユーザ入力を行うクラスとします。

![Navi.png](https://qiita-image-store.s3.amazonaws.com/0/199141/b87c456c-f4da-7d94-3f96-7a83d93f37ba.png)


```python
class Navi:

    def ask(self, message):
        print(message)
        return input('>> ')

```

このコードに対して、先ほどと同じようにテストしようとすると、
いちいちユーザ入力を求めてくるため、自動化が大変です。
また、Naviクラスに不具合があればそちらの影響も受けてしまいます。

こういうとき、当該クラスまたはメソッドの代役(Double)を用意し、
テストコードが設定した値を返すようにすれば容易に自動化できます。

これがテストダブル(Test Double)です。

さっそくテストダブルの一種であるスタブを自作し、
Naviクラスのaskメソッドを置き換えてみます。

例：）

```python
class LegendTest(unittest.TestCase):

    # Test stub
    def naviAskStub(self, message):      # Naviクラスのaskメソッドの代役(Double)
        return self.navi_ask_resp

    # Usage of test spy
    def test_set_cv_stub(self):
        legend = Legend()

        legend.navi.ask = self.naviAskStub  # 実物のaskメソッドをスタブに挿げ替える
        self.navi_ask_resp = 'Link'         # 'Link' という文字列を返すよう設定

        legend.set_cv()

        self.assertEqual(100,       legend.cv_volume)
        self.assertEqual('Hiyama', legend.cv_actor)

if __name__ == "__main__":
    unittest.main()
```

これを実行した場合の結果は以下の通り、リンクの声優が'Hiyama'で無いという
バグを検出できています。

```console
======================================================================
FAIL: test_set_cv_stub (__main__.LegendTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "LegendTest_DiyDouble.py", line 21, in test_set_cv_stub
    self.assertEqual('Hiyama', legend.cv_actor)
AssertionError: 'Hiyama' != 'Hayami'
- Hiyama
+ Hayami
```

このように、テストダブルを使ってキーボード入力の変わりに
テストコードから'Link'という文字列を入力できるようになりました。

この他にも、時間のかかる処理や未実装のクラスなどの場合も
テストダブルに置き換えてしまいます。

このように、テストダブルを使うことによって、何らかの呼び出しを行う
ユニットのテストを行うことができる様になります。

なお、ここで取り上げたのは動的言語であるPythonですが、
静的言語(例：c++)の場合、Naviは別のコンパイル単位(Navi.cpp)とし、
ユニットテスト時にはスタブ(NaviStub.cpp)の方とリンクします。

これが俗に言うリンクの冒険です。

![Link.png](https://qiita-image-store.s3.amazonaws.com/0/199141/152b42a5-7f60-5900-3778-272af2700767.png)

# テストダブルの種類

テストダブルには上で取り上げたスタブの他に以下の種類があります。
下に行くほど高機能です。

|種類     |実処理と同様の動作|テスト用の戻り値設定|呼出し記録|チェック処理|
|---------|------------------|--------------------|----------|------------|
|ダミー   |しない            |なし                |なし      |なし        |
|フェイク |する              |なし                |なし      |なし        |
|スタブ   |しない            |あり                |なし      |なし        |
|スパイ   |しない            |あり                |あり      |なし        |
|モック   |しない            |あり                |基本あり  |あり        |


## ダミー

そのままダミーデータです。
テスト対象が受け取った引数をそのまま下位のメソッドに渡す場合、
引数は何でも良いのでダミーを渡します。

```python
# テスト対象のコード
def create_parser(yml):
    self.parser = YmlParser(yml)

# ・
# ・
# ・

# ユニットテスト
class BraBraBraTest(unittest.TestCase):

    def test_create_parser():
        dummy_data = 'aaaabbbbcccc'  # どうせ使わないので、何でも良い

        create_parser(dummy_data)

        # この後、評価できるものがあればしても良い
```

## フェイク

実処理と同様に動作する簡易版です。
例えばデータベースのダブルとして [インメモリデータベース](https://ja.wikipedia.org/wiki/インメモリデータベース "wikipedia") を使用する例が挙げられます。

## スタブ

既に説明済みですが、比較用に一応コードを載せておきます。

```python
    def naviAskStub(self, message):
        return self.navi_ask_resp
```

## スパイ

スタブの一種で、呼出しの記録機能を備えています。


```python
    def naviAskSpy(self, message):
        self.navi_ask_cnt += 1       # Stubから追加
        return self.navi_ask_resp
```

## モック

スタブの仲間の中でもっとも高機能で、正しく呼び出されたかの確認もします。

定義上はスパイのような呼出し記録機能が無くてもこの分類ですが、
それはあるのが普通のようです。

```python
    def naviAskMock(self, message):
        self.navi_ask_cnt += 1
        self.assertTrue(message.startswith('Hey')) # Spyから追加
        return self.navi_ask_resp
```

# Python標準のモック

上の例では説明のためスタブを自作しましたが、Python 3.3以降は
汎用的に使えるモックが標準で入っています。

https://docs.python.jp/3/library/unittest.mock.html

以下のように、何の準備もなしに使えてしまいます。

```python
import unittest
from unittest.mock import Mock

# (略)

class LegendTest(unittest.TestCase):

    def test_set_cv_mock(self):
        mock = Mock(return_value='Link')    # Python標準のMockを使用し、戻り値を設定

        legend = Legend()
        legend.navi.ask = mock              # 自作スタブと同様、askメソッドと挿げ替え
        legend.set_cv()

        self.assertEqual(100,      legend.cv_volume)
        self.assertEqual('Hiyama', legend.cv_actor)
        self.assertEqual(1,        mock.call_count)
```

# まとめ

テストダブルはユニットテストで使う実コードの代役(Double)です。
テストダブルを使うことで、ユーザ入力や重い処理等を省略し、
効率的にテストすることが出来ます。

テストダブルにはいくつか種類があり、その中で高機能なモックについては
Pythonなら標準で汎用モックが入っています。

まじめに書こうとしたらどんどん方向性がずれてふざけた記事になってしまいました。
何か認識違いなどがあれば、コメントか編集リクエストで教えていただければ幸いです。

なお、コードは全てGithubに上げてあります。
https://github.com/YankeeDeltaBravo225/test_double_examples

それでは、良いユニットテストライフを。

![master_sword.png](https://qiita-image-store.s3.amazonaws.com/0/199141/5e0e0340-dba7-08eb-1387-fee465aaf15c.png)

# 参考

* [スタブとモックの違い](https://qiita.com/k5trismegistus/items/10ce381d29ab62ca0ea6)
* [TDD > モック / スタブ](https://qiita.com/7of9/items/8e2cb2070f2b2ea4e5ec)
* https://www.martinfowler.com/bliki/InMemoryTestDatabase.html
* https://martinfowler.com/bliki/TestDouble.html