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
できた。
これが今できる一番スマートな共通化。