ウェブサイト検索

Flaskでユニットテストを実行する方法


紹介

ソフトウェア開発プロセスにおいてテストは不可欠であり、コードが期待通りに動作し、欠陥がないことを保証します。Pythonでは、pytestは標準のunit testモジュールに対していくつかの利点を提供する人気のあるテストフレームワークです。標準のPythonテストフレームワークであり、標準ライブラリの一部です。pytestは、よりシンプルな構文、優れた出力、強力なフィクスチャ、および豊富なプラグインエコシステムを含んでいます。このチュートリアルでは、Flaskアプリケーションをセットアップし、pytestフィクスチャを統合し、pytestを使用してユニットテストを書く方法を案内します。

前提条件

始める前に、次のものが必要です。

  • Ubuntuを実行しているサーバーと、sudo特権を持つ非ルートユーザー、そしてアクティブなファイアウォールが必要です。この設定についてのガイダンスを得るには、このリストからご自身のディストリビューションを選択し、初期サーバーセットアップガイドに従ってください。サポートされているバージョンのUbuntuを使用することを確認してください。

  • Linuxコマンドラインに慣れていること。このLinuxコマンドラインの入門ガイドを訪れることができます。

  • Pythonプログラミングの基本的な理解と、Pythonにおけるpytestテストフレームワークについて。pytestについて詳しく学ぶには、当社のPyTest Pythonテストフレームワークのチュートリアルを参照してください。

  • UbuntuシステムにPython 3.7以上がインストールされていること。UbuntuでPythonスクリプトを実行する方法を学ぶには、「UbuntuでPythonスクリプトを実行する方法」についてのチュートリアルを参照してください。

unittestよりもpytestが優れた選択肢である理由

pytest は組み込みの unittest フレームワークに対していくつかの利点を提供します:

  • Pytestは、unittestで必要とされるより冗長なメソッドの代わりに、シンプルなアサートステートメントを使用して、より少ないボイラープレートコードでテストを書くことを可能にします。

  • それはより詳細で読みやすい出力を提供し、テストがどこで失敗したのか、なぜ失敗したのかを特定しやすくします。

  • Pytestのフィクスチャは、unittestのsetUpおよびtearDownメソッドよりも、より柔軟で再利用可能なテストセットアップを可能にします。

  • 複数の入力セットで同じテスト関数を実行するのが簡単になり、unittestではそれほど簡単ではありません。

  • Pytestは、コードカバレッジツールから並列テスト実行まで、その機能を拡張する豊富なプラグインコレクションを持っています。

  • テストファイルと関数を自動的に発見し、その命名規則に一致するものを見つけることで、テストスイートの管理において時間と労力を節約します。

これらの利点を考慮すると、pytestは現代のPythonテストでしばしば好まれる選択肢です。Flaskアプリケーションをセットアップし、pytestを使用してユニットテストを書きましょう。

ステップ1 - 環境の設定

Ubuntu 24.04はデフォルトでPython 3を搭載しています。ターミナルを開いて、Python 3のインストールを再確認するために次のコマンドを実行してください:

root@ubuntu:~# python3 --version
Python 3.12.3

もしあなたのマシンにPython 3がすでにインストールされている場合、上記のコマンドは現在のPython 3のインストールバージョンを返します。もしインストールされていない場合は、以下のコマンドを実行してPython 3のインストールを取得できます。

root@ubuntu:~# sudo apt install python3

次に、システムにpipパッケージインストーラーをインストールする必要があります。

root@ubuntu:~# sudo apt install python3-pip

一度 pip がインストールされたら、Flaskをインストールしましょう。

ステップ2 - Flaskアプリケーションを作成する

まずはシンプルなFlaskアプリケーションを作成しましょう。プロジェクトのために新しいディレクトリを作成し、その中に移動します:

root@ubuntu:~# mkdir flask_testing_app
root@ubuntu:~# cd flask_testing_app

さて、依存関係を管理するために仮想環境を作成し、有効化しましょう:

root@ubuntu:~# python3 -m venv venv
root@ubuntu:~# source venv/bin/activate

pipを使用してFlaskをインストールします:

root@ubuntu:~# pip install Flask

では、シンプルなFlaskアプリケーションを作成しましょう。新しいファイルを作成し、app.pyという名前を付け、次のコードを追加してください。

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def home():
    return jsonify(message="Hello, Flask!")

@app.route('/about')
def about():
    return jsonify(message="This is the About page")

@app.route('/multiply/<int:x>/<int:y>')
def multiply(x, y):
    result = x * y
    return jsonify(result=result)

if __name__ == '__main__':
    app.run(debug=True)

このアプリケーションには3つのルートがあります:

  • /: シンプルな“こんにちは、Flask!”メッセージを返します。
  • /about: 単純な“これはアバウトページです”メッセージを返します。
  • /multiply/<int:x>/<int:y>: 2つの整数を掛け算し、その結果を返します。

アプリケーションを実行するには、次のコマンドを入力してください:

root@ubuntu:~# flask run
output
* Serving Flask app "app" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

上記の出力から、サーバーがhttp://127.0.0.1で実行されており、ポート5000でリッスンしていることがわかります。別のUbuntuコンソールを開いて、以下のcurlコマンドを1つずつ実行してください:

  • 取得: curl http://127.0.0.1:5000/
  • 取得: curl http://127.0.0.1:5000/about
  • GET: curl http://127.0.0.1:5000/multiply/10/20

これらのGETリクエストが何をするのか理解しましょう:

  1. curl http://127.0.0.1:5000/:これは、Flaskアプリケーションのルートルート(‘/’)にGETリクエストを送信します。サーバーは、メッセージ“Hello, Flask! ”を含むJSONオブジェクトで応答し、ホームルートの基本的な機能を示しています。

  2. curl http://127.0.0.1:5000/about: これは/aboutルートにGETリクエストを送信します。サーバーは“これはAboutページです”.というメッセージを含むJSONオブジェクトで応答します。これは私たちのルートが正しく機能していることを示しています。

  3. curl http://127.0.0.1:5000/multiply/10/20: これは、2つのパラメータ(10と20)を持つ/multiplyルートにGETリクエストを送信します。サーバーはこれらの数値を掛け算し、結果(200)を含むJSONオブジェクトで応答します。これは、私たちのmultiplyルートがURLパラメータを正しく処理し、計算を実行できることを示しています。

これらのGETリクエストは、データを変更することなく、FlaskアプリケーションのAPIエンドポイントと対話し、情報を取得したりサーバー上のアクションをトリガーしたりすることを可能にします。それらはデータを取得したり、エンドポイントの機能をテストしたり、私たちのルートが期待通りに応答しているかを確認するのに役立ちます。

これらの GET リクエストがどのように動作するか見てみましょう:

root@ubuntu:~# curl http://127.0.0.1:5000/
{"message":"Hello, Flask!"}
root@ubuntu:~# curl http://127.0.0.1:5000/about
{"message":"This is the About page"}
root@ubuntu:~# curl http://127.0.0.1:5000/multiply/10/20
{"result":200}

ステップ3 - pytestのインストールと最初のテストの作成

基本的なFlaskアプリケーションができたので、pytestをインストールし、いくつかの単体テストを書きましょう。

pipを使ってpytestをインストールします:

root@ubuntu:~# pip install pytest

テストファイルを保存するためのテストディレクトリを作成してください:

root@ubuntu:~# mkdir tests

では、新しいファイル名をtest_app.pyとして作成し、次のコードを追加しましょう:

Import sys module for modifying Python's runtime environment
import sys
Import os module for interacting with the operating system
import os

Add the parent directory to sys.path
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

Import the Flask app instance from the main app file
from app import app 
Import pytest for writing and running tests
import pytest

@pytest.fixture
def client():
    """A test client for the app."""
    with app.test_client() as client:
        yield client

def test_home(client):
    """Test the home route."""
    response = client.get('/')
    assert response.status_code == 200
    assert response.json == {"message": "Hello, Flask!"}

def test_about(client):
    """Test the about route."""
    response = client.get('/about')
    assert response.status_code == 200
    assert response.json == {"message": "This is the About page"}

def test_multiply(client):
    """Test the multiply route with valid input."""
    response = client.get('/multiply/3/4')
    assert response.status_code == 200
    assert response.json == {"result": 12}

def test_multiply_invalid_input(client):
    """Test the multiply route with invalid input."""
    response = client.get('/multiply/three/four')
    assert response.status_code == 404

def test_non_existent_route(client):
    """Test for a non-existent route."""
    response = client.get('/non-existent')
    assert response.status_code == 404

このテストファイルの機能を分解してみましょう:

  1. @pytest.fixture def client(): これはFlaskアプリのためのテストクライアントを作成するpytestフィクスチャです。app.test_client() メソッドを使用して、実際のサーバーを起動せずにアプリにリクエストを送信できるクライアントを作成します。yield ステートメントにより、テストでクライアントを使用し、各テストの後に適切に閉じることができます。

  2. def test_home(client): この関数はアプリのホームルート(/)をテストします。テストクライアントを使用してルートにGETリクエストを送信し、その後、レスポンスのステータスコードが200(OK)であることと、JSONレスポンスが期待されるメッセージと一致していることを確認します。

  3. def test_about(client): test_homeに似て、この関数はaboutルート(/about)をテストします。200のステータスコードをチェックし、JSONレスポンスの内容を検証します。

  4. def test_multiply(client): この関数は、正しい入力 (/multiply/3/4) で multiply ルートをテストします。ステータスコードが 200 であり、JSON レスポンスに乗算の正しい結果が含まれていることを確認します。

  5. def test_multiply_invalid_input(client): この関数は無効な入力 (multiply/three/four) で乗算ルートをテストします。文字列の入力を必要な整数パラメータに一致させられないときの期待される動作である404(見つかりません)というステータスコードを確認します。

  6. def test_non_existent_route(client): この関数は、存在しないルートにアクセスしたときのアプリの動作をテストします。/non-existentにGETリクエストを送り、これは私たちのFlaskアプリでは定義されていません。テストは、レスポンスのステータスコードが404(未検出)であることを確認し、私たちのアプリが未定義のルートへのリクエストを正しく処理していることを保証します。

これらのテストは、私たちのFlaskアプリの基本的な機能をカバーしており、各ルートが有効な入力に対して正しく応答することを確認し、multiplyルートが無効な入力を適切に処理することを保証します。pytestを使用することで、これらのテストを簡単に実行し、アプリが期待どおりに動作していることを確認できます。

ステップ4 - テストの実行

テストを実行するには、以下のコマンドを実行してください:

root@ubuntu:~# pytest

デフォルトでは、pytest の発見プロセスは、現在のフォルダーとそのサブフォルダーを再帰的にスキャンし、名前が“test_”で始まるか、“_test”で終わるファイルを探します。そのファイル内にあるテストが実行されます。出力は次のようになります。

platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
rootdir: /home/user/flask_testing_app
collected 5 items                                                                                                                

tests/test_app.py ....                                                                                                     [100%]

======================================================= 5 passed in 0.19s ========================================================

これは、すべてのテストが正常に通過したことを示しています。

ステップ5: pytestでのフィクスチャの使用

フィクスチャは、テストにデータやリソースを提供するために使用される関数です。テスト環境のセットアップやクリーンアップ、データの読み込み、その他のセットアップ作業を行うために使用できます。pytestでは、フィクスチャは@pytest.fixtureデコレーターを使用して定義されます。

既存のフィクスチャを強化する方法は次のとおりです。クライアントフィクスチャを更新して、セットアップおよびティアダウンロジックを使用します:

@pytest.fixture
def client():
    """Set up a test client for the app with setup and teardown logic."""
    print("\nSetting up the test client")
    with app.test_client() as client:
        yield client  # This is where the testing happens
    print("Tearing down the test client")

def test_home(client):
    """Test the home route."""
    response = client.get('/')
    assert response.status_code == 200
    assert response.json == {"message": "Hello, Flask!"}

def test_about(client):
    """Test the about route."""
    response = client.get('/about')
    assert response.status_code == 200
    assert response.json == {"message": "This is the About page"}

def test_multiply(client):
    """Test the multiply route with valid input."""
    response = client.get('/multiply/3/4')
    assert response.status_code == 200
    assert response.json == {"result": 12}

def test_multiply_invalid_input(client):
    """Test the multiply route with invalid input."""
    response = client.get('/multiply/three/four')
    assert response.status_code == 404

def test_non_existent_route(client):
    """Test for a non-existent route."""
    response = client.get('/non-existent')
    assert response.status_code == 404

この設定は、テスト出力におけるセットアップとティアダウンのフェーズを示すためにプリント文を追加します。必要に応じて、これらは実際のリソース管理コードに置き換えることができます。

もう一度テストを実行してみましょう:

root@ubuntu:~# pytest -vs

-v フラグは冗長性を増し、-s フラグはコンソール出力に印刷文を表示できるようにします。

次の出力が表示されるはずです:

platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
rootdir: /home/user/flask_testing_app 
cachedir: .pytest_cache      

collected 5 items                                                                                          

tests/test_app.py::test_home 
Setting up the test client
PASSED
Tearing down the test client

tests/test_app.py::test_about 
Setting up the test client
PASSED
Tearing down the test client

tests/test_app.py::test_multiply 
Setting up the test client
PASSED
Tearing down the test client

tests/test_app.py::test_multiply_invalid_input 
Setting up the test client
PASSED
Tearing down the test client

tests/test_app.py::test_non_existent_route 
Setting up the test client
PASSED
Tearing down the test client

============================================ 5 passed in 0.35s =============================================

ステップ6:失敗テストケースの追加

既存のテストファイルに失敗テストケースを追加しましょう。test_app.pyファイルを修正し、間違った結果のための失敗テストケースとして、以下の関数を最後の方に追加してください:

def test_multiply_edge_cases(client):
    """Test the multiply route with edge cases to demonstrate failing tests."""
    # Test with zero
    response = client.get('/multiply/0/5')
    assert response.status_code == 200
    assert response.json == {"result": 0}

    # Test with large numbers (this might fail if not handled properly)
    response = client.get('/multiply/1000000/1000000')
    assert response.status_code == 200
    assert response.json == {"result": 1000000000000}

    # Intentional failing test: incorrect result
    response = client.get('/multiply/2/3')
    assert response.status_code == 200
    assert response.json == {"result": 7}, "This test should fail to demonstrate a failing case"

では、test_multiply_edge_cases 関数を分解して、それぞれの部分が何をしているのかを説明しましょう:

  1. ゼロでのテスト:このテストは、乗算関数がゼロとの乗算を正しく処理できるかを確認します。任意の数をゼロで乗算すると結果が0になることを期待しています。これは重要な境界ケースであり、一部の実装ではゼロとの乗算に問題があるかもしれないため、テストする必要があります。

  2. 大きな数値によるテスト:このテストは、乗算関数がオーバーフローや精度の問題なしに大きな数を処理できるかどうかを検証します。我々は100万の値を2つ掛け算して、1兆の結果を期待しています。このテストは、関数の能力の上限を確認するために重要です。サーバーの実装が大きな数を適切に処理できない場合、このテストは失敗する可能性があることに注意してください。これは、大きな数のライブラリや異なるデータ型が必要であることを示すかもしれません。

  3. 意図的な失敗テスト:このテストは意図的に失敗するように設定されています。2 * 3 が 7 に等しいかどうかをチェックしますが、これは間違っています。このテストは、失敗したテストがテスト出力にどのように表示されるかを示すことを目的としています。これは、失敗したテストを特定しデバッグする方法を理解するのに役立ちます。これはテスト駆動開発やデバッグプロセスにおいて重要なスキルです。

これらのエッジケースと意図的な失敗を含めることで、あなたは乗算ルートの基本機能だけでなく、極端な条件下での挙動やエラーレポート機能もテストしています。このテストアプローチは、私たちのアプリケーションの堅牢性と信頼性を確保するのに役立ちます。

もう一度テストを実行してみましょう:

root@ubuntu:~# pytest -vs

次の出力が表示されるはずです:

platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
rootdir: /home/user/flask_testing_app 
cachedir: .pytest_cache      
  
collected 6 items                                                                                                                           

tests/test_app.py::test_home 
Setting up the test client
PASSED
Tearing down the test client

tests/test_app.py::test_about 
Setting up the test client
PASSED
Tearing down the test client

tests/test_app.py::test_multiply 
Setting up the test client
PASSED
Tearing down the test client

tests/test_app.py::test_multiply_invalid_input 
Setting up the test client
PASSED
Tearing down the test client

tests/test_app.py::test_non_existent_route 
Setting up the test client
PASSED
Tearing down the test client

tests/test_app.py::test_multiply_edge_cases 
Setting up the test client
FAILED
Tearing down the test client


================================================================= FAILURES ==================================================================
_________________________________________________________ test_multiply_edge_cases __________________________________________________________

client = <FlaskClient <Flask 'app'>>

    def test_multiply_edge_cases(client):
        """Test the multiply route with edge cases to demonstrate failing tests."""
        # Test with zero
        response = client.get('/multiply/0/5')
        assert response.status_code == 200
        assert response.json == {"result": 0}
    
        # Test with large numbers (this might fail if not handled properly)
        response = client.get('/multiply/1000000/1000000')
        assert response.status_code == 200
        assert response.json == {"result": 1000000000000}
    
        # Intentional failing test: incorrect result
        response = client.get('/multiply/2/3')
        assert response.status_code == 200
>       assert response.json == {"result": 7}, "This test should fail to demonstrate a failing case"
E       AssertionError: This test should fail to demonstrate a failing case
E       assert {'result': 6} == {'result': 7}
E         
E         Differing items:
E         {'result': 6} != {'result': 7}
E         
E         Full diff:
E           {
E         -     'result': 7,...
E         
E         ...Full output truncated (4 lines hidden), use '-vv' to show

tests/test_app.py:61: AssertionError
========================================================== short test summary info ==========================================================
FAILED tests/test_app.py::test_multiply_edge_cases - AssertionError: This test should fail to demonstrate a failing case
======================================================== 1 failed, 5 passed in 0.32s ========================================================

上記の失敗メッセージは、tests/test_app.pyファイル内のテストtest_multiply_edge_casesが失敗したことを示しています。具体的には、このテスト関数の最後のアサーションが失敗を引き起こしました。

この意図的な失敗は、テスト失敗がどのように報告され、失敗メッセージにどの情報が提供されるかを示すのに役立ちます。失敗が発生した正確な行、期待される値と実際の値、そしてその二つの違いを示しています。

実際のシナリオでは、テストが通るようにコードを修正するか、期待される結果が間違っている場合はテストを調整します。しかし、この場合、失敗は教育目的のために意図的です。

結論

このチュートリアルでは、Flaskアプリケーションの単体テストをpytestを使用して設定する方法、pytestフィクスチャを統合する方法、およびテスト失敗の様子を示しました。これらの手順に従うことで、Flaskアプリケーションの信頼性と保守性を確保し、バグを最小限に抑え、コードの品質を向上させることができます。

FlaskとPytestの公式ドキュメントを参照して、もっと学ぶことができます。

関連記事: