LoginSignup
6
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-11-28

1. 動機

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

  • アプリ: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

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

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2