
pytestのparametrizeでdataclassを利用してみる
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
pytestのparametrizeを活用すれば、ひとつのテストメソッドに対して、パラメータを変えるテストが便利に書けます。
しかし、parametrizeのパラメータの数が多かったり、数は少なくとも内容が多いとき、テストコードが縦に長くなって見づらくなることがあります。 改善方法を考えているとき、ふと、「そういえば、pytestのparametrizeでdataclassが使えたりするかな?」と閃いたので、試してみました。
おすすめの方
- pytestのparametrizeでdataclassを利用したい方
テストパターンが見づらい例
たとえば、SNSトピックを受け取って処理するLambdaのUnit Testを書く場合を想定します。 パラメータの量や内容が多いと、途中で「これはどれが何だっけ?」「user_info と device_info は、どっちが先だっけ?」みたいになることがあるのです。
import pytest
import app
import json
@pytest.mark.parametrize(
"event, user_info, device_info, xxx, expected",
[
# パターン1
(
# 先頭なので、これは event だと分かりやすい
{
"Records": [
{
"body": json.dumps(
{
"Message": json.dumps(
{
"aaa": "111",
"bbb": "222",
"aaa": "333",
}
)
}
)
},
{
"body": json.dumps(
{
"Message": json.dumps(
{
"aaa": "AAA",
"bbb": "BBB",
"aaa": "CCC",
}
)
}
)
},
{
"body": json.dumps(
{
"Message": json.dumps(
{
"aaa": "xxx",
"bbb": "yyy",
"aaa": "zzz",
}
)
}
)
},
]
},
# これはどれが何だっけ?
{
# 略
},
{
# 略
},
{
# 略
},
{
# 略
},
),
# パターン2
(
{
"Records": [
{
"body": json.dumps(
{
"Message": json.dumps(
{
"aaa": "111",
"bbb": "222",
"aaa": "333",
}
)
}
)
},
{
"body": json.dumps(
{
"Message": json.dumps(
{
"aaa": "AAA",
"bbb": "BBB",
"aaa": "CCC",
}
)
}
)
},
{
"body": json.dumps(
{
"Message": json.dumps(
{
"aaa": "xxx",
"bbb": "yyy",
"aaa": "zzz",
}
)
}
)
},
]
},
# user_info と device_info は、どっちが先だっけ?
{
# 略
},
{
# 略
},
{
# 略
},
# これは何だっけ?
{
# 略
},
),
],
)
def test_message(event, user_info, device_info, xxx, expected):
# actual = app.main(event, None)
# assert actual == expected
pass
dataclassを利用してみる(短い版)
Messageという名前でデータクラスを作成して、parametrizeで利用しています。
import pytest
import app
from dataclasses import dataclass
@dataclass
class Message(object):
text1: str
text2: str
@pytest.mark.parametrize(
"message, expected",
[
(Message("hello", "world"), "hello world"), # キーワード引数なし
(Message(text1="FOO", text2="BAR"), "FOO BAR"), # キーワード引数あり
],
)
def test_message_use_data_class(message, expected):
actual = app.message1(message.text1, message.text2)
assert actual == expected
dataclassを利用してみる(長い版)
MessageParamsという名前でデータクラスを作成して、parametrizeで利用しています。
データクラスの定義(内容)はいろいろな方針があると思います。
- ひとつのデータクラスで、すべてのパラメータを扱えるようにする
- expectedとそれ以外のデータクラスを作成する(下記の例がこれ)
- それぞれのパラメータごとにデータクラスを作成する
- など
他でも使い回せるか?などを考慮しつつ、自分たちにとって分かりやすい&都合が良い方針を採用すると良いですね。
import pytest
import app
import json
from dataclasses import dataclass
@dataclass
class MessageParams(object):
event: dict
user_info: dict
device_info: dict
xxx: dict
@dataclass
class Expected(object):
expected: dict
@pytest.mark.parametrize(
"message_params, expected",
[
# パターン1
(
MessageParams(
event={
"Records": [
{
"body": json.dumps(
{
"Message": json.dumps(
{
"aaa": "111",
"bbb": "222",
"aaa": "333",
}
)
}
)
},
{
"body": json.dumps(
{
"Message": json.dumps(
{
"aaa": "AAA",
"bbb": "BBB",
"aaa": "CCC",
}
)
}
)
},
{
"body": json.dumps(
{
"Message": json.dumps(
{
"aaa": "xxx",
"bbb": "yyy",
"aaa": "zzz",
}
)
}
)
},
]
},
user_info={
# 略
},
device_info={
# 略
},
xxx={
# 略
},
),
Expected(
{
# 略
}
),
),
# パターン2
(
MessageParams(
event={
"Records": [
{
"body": json.dumps(
{
"Message": json.dumps(
{
"aaa": "111",
"bbb": "222",
"aaa": "333",
}
)
}
)
},
{
"body": json.dumps(
{
"Message": json.dumps(
{
"aaa": "AAA",
"bbb": "BBB",
"aaa": "CCC",
}
)
}
)
},
{
"body": json.dumps(
{
"Message": json.dumps(
{
"aaa": "xxx",
"bbb": "yyy",
"aaa": "zzz",
}
)
}
)
},
]
},
user_info={
# 略
},
device_info={
# 略
},
xxx={
# 略
},
),
Expected(
{
# 略
},
),
),
],
)
def test_message(message_params, expected):
# actual = app.main(message_params.event, None)
# assert actual == expected.expected
pass
さいごに
見通しが良いテストコードには過剰かもしれません。使い所には気をつけていきたいですね。