実現したいこと
自動テストコードをなんとか自動で生成したい!
現在、モデルベースドテストやキーワード駆動テストなど「説明可能性の高いテスト」に関する技術について調査を進めています。
そのような技術の一つのゴール地点である「自動テストコードが自動で生成され、自動で実行される」を目指すために、如何にテストコードを生成するかを検討しました。
上流部分(モデルやキーワードからどのようにテストケースを導出するか)は別途検討中のため今回は触れません。
実装内容
「あらかじめテンプレートを設定しておくと、渡されたデータに応じたテストコードを生成してくれる」という仕組みになります。
Pythonのコードやテストコードはお試しで作ったものなので、ご参考までに!
ディレクトリ構成
testcode-gen/
├ .gitignore
├ poetry.lock
├ pyproject.toml
├ src/
├ main.py
├ templates/
├ templete.py.mak
├ outputs/
├ test_sample.py
1. Makefileでテストコードのテンプレートを作る
Makefileは、pytest-bdd(templetes/test.py.mak)を参考にさせていただきました。
このテンプレートが呼び出されると、下記のような処理が実行されます。
- 渡されたシナリオの数だけテストメソッドを作る
- 渡されたコードをテストメソッド内に転記する
from playwright.sync_api import Page
class TestSample:
% for scenario in sorted(scenarios, key=lambda scenario: scenario.id):
def test_${scenario.id}_${scenario.name}(self, page: Page):
% for src in scenario.src:
${src}
% endfor
% endfor
2. テストコードを生成するためのスクリプトを実行する
makoというテンプレートライブラリを使用します。
同様の処理を実現できるライブラリは他にもあるようですが、こちらもpytest-bddのgeneration.pyにならって実装しています。
from mako.lookup import TemplateLookup
import os
from dataclasses import dataclass, field
@dataclass
class ScenarioData():
name: str
id: int
src: list = field(default_factory=list)
if __name__ == "__main__":
# テンプレートを参照する
template_lookup = TemplateLookup(
directories=[os.path.join(os.path.dirname(__file__), "templates")]
)
template = template_lookup.get_template("template.py.mak")
# テストシナリオを定義する(コードをベタ書きしているが、モデルやキーワードから生成される状態を目指したい)
sd_1 = ScenarioData(name="goto_main", id=1, src=[
"page.goto('https://playwright.dev/')",
"title = page.title()",
"assert title == 'Fast and reliable end-to-end testing for modern web apps | Playwright'"
])
sd_2 = ScenarioData(name="goto_doc_intro", id=2, src=[
"page.goto('https://playwright.dev/docs/intro')",
"title = page.title()",
"assert title == 'Installation | Playwright'"
])
sd_3 = ScenarioData(name="goto_doc_api", id=3, src=[
"page.goto('https://playwright.dev/docs/api/class-playwright')",
"title = page.title()",
"assert title == 'Playwright Library | Playwright'"
])
# テンプレートに沿ってテストコードを作成する
code = template.render(
scenarios=[sd_1, sd_2, sd_3]
)
# テストコードを.pyファイルに書き出す
with open(file="outputs/test_sample.py", mode="w") as pyfile:
for line in code:
pyfile.write(line.replace('\n', ''))
3. テストコードが生成される
下記のような形でpytestのコードが生成されます。
テンプレートで定義しておけば、docstringを動的に生成することも可能です。
from playwright.sync_api import Page
class TestSample:
def test_1_goto_main(self, page: Page):
page.goto('https://playwright.dev/')
title = page.title()
assert title == 'Fast and reliable end-to-end testing for modern web apps | Playwright'
def test_2_goto_doc_intro(self, page: Page):
page.goto('https://playwright.dev/docs/intro')
title = page.title()
assert title == 'Installation | Playwright'
def test_3_goto_doc_api(self, page: Page):
page.goto('https://playwright.dev/docs/api/class-playwright')
title = page.title()
assert title == 'Playwright Library | Playwright'
所感
この技術だけで何か良い未来が見えるわけではないですが、テスト設計の自動化との相性の良さは感じました。
より良い方法がないか引き続き探っていきたいです!
参考