6
3

More than 1 year has passed since last update.

テンプレートライブラリを用いてpytestのコードを生成する

Last updated at Posted at 2023-01-06

実現したいこと

自動テストコードをなんとか自動で生成したい!

現在、モデルベースドテストやキーワード駆動テストなど「説明可能性の高いテスト」に関する技術について調査を進めています。

そのような技術の一つのゴール地点である「自動テストコードが自動で生成され、自動で実行される」を目指すために、如何にテストコードを生成するかを検討しました。
上流部分(モデルやキーワードからどのようにテストケースを導出するか)は別途検討中のため今回は触れません。

実装内容

「あらかじめテンプレートを設定しておくと、渡されたデータに応じたテストコードを生成してくれる」という仕組みになります。
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'

所感

この技術だけで何か良い未来が見えるわけではないですが、テスト設計の自動化との相性の良さは感じました。
より良い方法がないか引き続き探っていきたいです!

参考

6
3
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
3