PythonのUnittestでmotoによるmockを共通化する

# 1. 動機
テストコード作成中に各テストコードでmockを作成するのが冗長的だなと思い、共通化できないか試行錯誤してみた。
以下、スタート時点のサンプルアプリとサンプルテストコード。
テストコードのEC２作成部分を共通化する。

- アプリ：EC2の情報を取得して出力する

```app.py
import boto3


def main():
    client = boto3.client('ec2')
    # 全てのインスタンス情報を取得する。
    instances = client.describe_instances()
    # インスタンス名を抽出する。
    instace_name = [instance['KeyName'] for r in instances.get('Reservations') for instance in r.get('Instances')]
    # インスタンス名を出力する
    print(instace_name)
```
- テストコード：mockでEC2を作成しアプリを実行する

```test.py
import unittest
import boto3
from moto import mock_ec2
from app import app


class MyTestCase(unittest.TestCase):
    @mock_ec2
    def test_case_1(self):
        client = boto3.client('ec2')
        # 作成するEC2の条件を設定
        ec2objects = [
            {'KeyName': 'test_ec2_name_1'}
        ]
        # EC2を作成
        for o in ec2objects:
            client.run_instances(
                ImageId='ami-03cf127a',
                MinCount=1,
                MaxCount=1,
                KeyName=o.get('KeyName'))
        # アプリを実行
        app.main()

    @mock_ec2
    def test_case_2(self):
        client = boto3.client('ec2')
        # 作成するEC2の条件を設定
        ec2objects = [
            {'KeyName': 'test_ec2_name_2'}
        ]
        # EC2を作成
        for o in ec2objects:
            client.run_instances(
                ImageId='ami-03cf127a',
                MinCount=1,
                MaxCount=1,
                KeyName=o.get('KeyName'))
        # アプリを実行
        app.main()


if __name__ == '__main__':
    unittest.main()

```

# 2. setUpメソッドを使用してみる
setUpメソッドにEC2を作成するコードを記述し、テストケース実行前にEC2が作成されるか試してみる。

```test.py
import unittest
import boto3
from moto import mock_ec2
from app import app


class MyTestCase(unittest.TestCase):
    @mock_ec2
    def setUp(self):
        client = boto3.client('ec2')
        # 作成するEC2の条件を設定
        ec2objects = [
            {'KeyName': 'test_ec2_name_1'}
        ]
        # EC2を作成
        for o in ec2objects:
            client.run_instances(
                ImageId='ami-03cf127a',
                MinCount=1,
                MaxCount=1,
                KeyName=o.get('KeyName'))

    @mock_ec2
    def test_case_1(self):
        # アプリを実行
        app.main()

    @mock_ec2
    def test_case_2(self):
        # アプリを実行
        app.main()


if __name__ == '__main__':
    unittest.main()
```
```result.
test_case_1 (tests.test_setUp.MyTestCase) ... []
ok
test_case_2 (tests.test_setUp.MyTestCase) ... []
ok

----------------------------------------------------------------------
Ran 2 tests in 0.458s

OK
```
EC2名は出力されない。
motoによるmockの有効期限はメソッドの終了までのようで、setUpメソッドに記述してしまうとsetUpメソッド終了時点でmockが消えてしまう。
結果、テストケース実行時にはmockが存在していない。

# 3. 共通クラスを作成し、各メソッドから実行する
前項の結果からmockはテストケースないで作成しなければならないとわかったので、共通クラスを作って各ケースから共通クラス内のメソッドを実行できないか試してみる。

```test.py
import unittest
import boto3
from moto import mock_ec2
from app import app


class common:
    @staticmethod
    def enviroment_1(name):
        client = boto3.client('ec2')
        # 作成するEC2の条件を設定
        ec2objects = [
            {'KeyName': name}
        ]
        # EC2を作成
        for o in ec2objects:
            client.run_instances(
                ImageId='ami-03cf127a',
                MinCount=1,
                MaxCount=1,
                KeyName=o.get('KeyName'))


class MyTestCase(unittest.TestCase):

    @mock_ec2
    def setUp(self):
        self.common = common

    @mock_ec2
    def test_case_1(self):
        # EC2を作成
        self.common.enviroment_1('test_ec2_name_1')
        # アプリを実行
        app.main()

    @mock_ec2
    def test_case_2(self):
        # EC2を作成
        self.common.enviroment_1('test_ec2_name_2')
        # アプリを実行
        app.main()


if __name__ == '__main__':
    unittest.main()
```
```result.
test_case_1 (tests.test_common.MyTestCase) ... ['test_ec2_name_1']
ok
test_case_2 (tests.test_common.MyTestCase) ... ['test_ec2_name_2']
ok

----------------------------------------------------------------------
Ran 2 tests in 0.489s

OK
```
いい感じに共通化できて、思った通りの結果が返ってきた。
ただ共通クラスを作るのはコード数増えるのでもう少し簡単にしたい。

# 4. テストクラス内に共通メソッドを作成する
そもそもテストクラス内でテストケース以外の共通メソッドが作成できれば一番手っ取り早いことにやっと気づく。

```test.py
import unittest
import boto3
from moto import mock_ec2
from app import app


class MyTestCase(unittest.TestCase):
    @mock_ec2
    def test_cace_1(self):
        # EC2を作成
        self.__common('test_ec2_name_1')
        # アプリを実行
        app.main()

    @mock_ec2
    def test_cace_2(self):
        # EC2を作成
        self.__common('test_ec2_name_2')
        # アプリを実行
        app.main()

    def __common(self, name):
        client = boto3.client('ec2')
        # 作成するEC2の条件を設定
        ec2objects = [
            {'KeyName': name}
        ]
        # EC2を作成
        for o in ec2objects:
            client.run_instances(
                ImageId='ami-03cf127a',
                MinCount=1,
                MaxCount=1,
                KeyName=o.get('KeyName'))


if __name__ == '__main__':
    unittest.main()
```
```result.
test_case_1 (tests.test.MyTestCase) ... ['test_ec2_name_1']
ok
test_case_2 (tests.test.MyTestCase) ... ['test_ec2_name_2']
ok

----------------------------------------------------------------------
Ran 2 tests in 0.361s

OK
```
できた。
これが今できる一番スマートな共通化。