概要
テストの構造化とBDD
テストコードは一つの関数について多くのパターンを実装することが多く、可読性を上げるためにも構造化をすることが重要です。テストを構造的に実装する際の考え方としてBDD(振る舞い駆動開発)があります。
BDDはソフトウェアの振る舞いを仕様書として明示し、その仕様に基づいたテストを書く開発手法です。BDDの目的は、技術者だけでなく非技術者(ビジネス担当者、顧客など)も含めたチーム全体で仕様を理解し、共有できるようにすることです。そのため、テストケースや仕様が読みやすく自然な言語で書かれていることが重要です。
BDDのテストは通常、Given-When-Then
形式で記述されます
- Given: 前提条件や設定を示す部分(何かが既に存在する、どのような状態であるか)
- When: 操作やイベントを示す部分(ユーザーが何かアクションを起こした)
- Then: 結果や期待される振る舞いを示す部分(どうなるべきか)
pytest-describe&pytest-spec
pytest-describeはpytest用のプラグインで、テストの可読性を高めるためにネストされた記述的なテスト構造を提供します。describeを用いて構造的に記述することで、テストのシナリオや意図を明確にしやすくします。特にソフトウェアの動作を自然言語で定義した後、テストを記述していくBDD(振る舞い駆動開発)と相性が良いといわれています。さらにpytest-specを導入することでテスト結果を仕様書のようにわかりやすく可視化することができます。
pytest-describeの使い方
前提
pytest-describeを使うためにはpytestがインストールされている必要があります。もしインストールされていない場合は下記を参考にインストールをしてください。
# 仮想環境の構築(実行は任意ですが、仮想環境で実行すると環境を汚さずに済みます)
python -m venv pytest-env
cd pytest-env
source bin/activate
(pytest_env) [ec2-user@ip-172-31-38-158 pytest_env]$
# pytest.markを利用するためにpytest.markを利用するために必要
pip install pytest
# pytest-describe pytest-specもインストールが必要
pip install pytest-describe pytest-spec
その他にpytestの実装に関する基本的なことを学びたい方は下記の記事をご参考ください
pytest-describe&pytest-specの使い方
pytest-describe
は、BDDの考え方に基づいて、テストケースをわかりやすく構造化し、自然言語に近い形でテストを書くことができるためBDDと非常に相性が良いです。Given-When-Then
のうち特にWhen-Then
に関わる部分の記述で利用されます。(ちなみにGiven
はpytestのフィクスチャという前処理・後処理を制御する機能で実現されます。)
5行目や13行目のdescribe
ブロックでは、テスト対象となる機能やコンポーネントをまとめて表現するためのものです。BDDの文脈では、Given
に相当する部分やシナリオ全体を整理するために使われます。例えば、「加算機能のテスト」や「ユーザーログインの振る舞い」のように大きな機能単位で分けられます。
7、10、15、18行目のit
は具体的なテストケースを記述する部分で、「何が期待されるか」(Then
に相当)を記述します。自然言語に近い形で「この振る舞いが期待される」ということを明示できるため、読みやすく、意図がはっきりしたテストを書けます。
def describe_user_authentication():
def describe_when_user_logs_in():
def it_succeeds_with_correct_credentials():
assert login("valid_user", "correct_password") == "Login successful"
def it_fails_with_incorrect_credentials():
assert login("valid_user", "wrong_password") == "Login failed"
def describe_when_user_registers():
def it_succeeds_with_valid_information():
assert register("new_user", "valid_password") == "Registration successful"
def it_fails_with_existing_username():
assert register("existing_user", "any_password") == "Username already taken"
続いてテストの出力結果の例です。通常のpytest -v
でもどの関数が実行されたがわかりま。慣れていればもんだいないかもしれませんが、初めのうちは少々見づらさを感じるかもしれません。
# 通常のpytest出力結果、コマンドオプションに-vをつけて実行
pytest -v
================================ test session starts ================================
collected 4 items
test_user_authentication.py::describe_user_authentication::describe_when_user_logs_in::it_succeeds_with_correct_credentials PASSED
test_user_authentication.py::describe_user_authentication::describe_when_user_logs_in::it_fails_with_incorrect_credentials PASSED
test_user_authentication.py::describe_user_authentication::describe_when_user_registers::it_succeeds_with_valid_information PASSED
test_user_authentication.py::describe_user_authentication::describe_when_user_registers::it_fails_with_existing_username PASSED
================================= 4 passed in 0.01s =================================
そこでpytest-spec
をインストールすることで下記のように構造化された出力が表示され、テスト結果がとても見やすくなります。
# pytest-specを利用した場合の出力結果例
User Authentication
When user logs in
✓ succeeds with correct credentials
✓ fails with incorrect credentials
When user registers
✓ succeeds with valid information
✓ fails with existing username
===================================== 4 passed in 0.01s =====================================
User Authentication
When user logs in
✓ succeeds with correct credentials
✓ fails with incorrect credentials
When user registers
✓ succeeds with valid information
✓ fails with existing username
===================================== 4 passed in 0.01s =====================================
まとめ
本記事ではpytest-descrbeについて解説しました。BDDと合わせてぜひ開発の現場でも積極的に使用して見やすいテストの記述ができるようにトレーニングしてみてください!