0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

pytestでPostgres アクセスをテストする最小構成:mockで外部依存を切り離す方法

Posted at

1. はじめに

PythonでDBアクセスを含むコードをテストするとき、実際のDBに接続してテストを行うと、以下のような課題に直面しがちです。

  • 実際のDBに接続すると、テストが遅くなる
  • DBの状態に依存してしまい、テストの再現性が低くなる
  • CI環境でDBを立ち上げるのが面倒
  • テストの失敗原因が「コード」なのか「DBの状態」なのか切り分けづらい

こうした課題を解決するために、本記事では pytest × mock を使って「DBアクセスを含む処理を、実際のDBに依存せずにテストする方法」を紹介します。

本記事では以下の構成で進めていきます:

:closed_book: 本記事のゴール

  • DockerでPostgreSQLを立ち上げる(開発環境用)
  • DBアクセスを行うPythonコードを用意する
  • unittest.mock を使ってDBアクセス部分を差し替える
  • pytestでテストを実行し、mockの動作を確認する

※ 本記事では、DockerやPostgreSQLのセットアップ手順は簡略化しています。環境構築済みの前提で進めます。


2.DockerでPostgreSQLを立ち上げる

本記事では、DBアクセスを含む関数を mock によってテストします。
そのため、テスト実行時に実際のDBを構築・起動する必要はありません。

実際にDBアクセスを含むコードのテストを行うには、開発環境で実際にDBを立ち上げて動作確認できる状態が必要です。今回は Docker Compose を使って PostgreSQL を構築し、テーブルと初期データを投入できるようにします。

※ 本記事では、Docker環境の構築手順は簡略化します。詳細なセットアップは割愛し、構成とポイントのみ紹介します。

📁 プロジェクト構成

workplace/
 |--docker-compose.yml
 |--docker/
 |    |-- Dockerfile
 |    └── init.sql
 |--app/
 |    |-- main.py
 |    |-- db_access.py
 |    └── tests/
 |         └── test_db_access.py

:page_with_curl: 初期化SQL(docker/init.sql)

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL
);

INSERT INTO users (name) VALUES ('Alice'), ('Bob');

このような構成を前提に、db_access.pyでは、fetch_user_names()のような関数が定義さていると仮定してこれ以降の説明をします。

3. DBアクセスを行うPythonコードを用意する

:clipboard: 実装方針

「PostgreSQLに接続して users テーブルから名前一覧を取得する関数」を作り、後で mock によってテスト可能な構造にします。

要素 内容
使用ライブラリ `psycopg2'(PostgresSQL用標準ドライバ)
接続情報 localhost:5432,DB名: postgres,ユーザ:postgres,パスワード: password
関数 fetch_user_names():ユーザ名一覧を返す
テスト設計 psycopg2.connect()をmockで差し替え可能な構造にする

:clipboard: app/db_access.pyのコード例

import psycopg2
from psycopg2.extras import RealDictCursor

def fetch_user_names():
    """
    PostgreSQLに接続して、usersテーブルのname一覧を取得する関数。
    """
    conn = psycopg2.connect(
        dbname="postgres",
        user="postgres",
        password="miki#pwd",
        host="localhost",
        port="5432"
    )
    try:
        with conn.cursor(cursor_factory=RealDictCursor) as cur:
            cur.execute("SELECT name FROM users;")
            rows = cur.fetchall()
            return [row["name"] for row in rows]
    finally:
        conn.close()

:white_check_mark: 実装ポイント

  • RealDictCursor を使うことで row["name"] のように辞書形式でアクセス可能
  • with conn.cursor() によってカーソルのクローズを自動化
  • finally: conn.close() によって接続の確実なクローズを保証
  • 接続情報はハードコードしているが、後で環境変数や設定ファイルに切り出すことも可能

この関数を対象に、次は test_db_access.py で mock を使ったテストコードを実装します。mock対象は psycopg2.connect で、返すカーソルの .fetchall() を差し替えることで、DBに依存せずにテストできます。

4. mockの作り方:DBアクセスを差し替えてテストする

DBアクセスを含む関数 fetch_user_names() をテストする際、実際のDBに接続せずにテストを行うためには、psycopg2.connect() を mock によって差し替える必要があります。ここでは、unittest.mock を使って、DB接続・カーソル・fetchall の挙動を模倣する方法を解説します。

テストコードの全体像

app/tests/test_db_access.py

from unittest import mock
from app import db_access

def test_fetch_user_names():
    # モックの戻り値を定義
    mock_rows = [{"name": "Alice"}, {"name": "Bob"}]

    # psycopg2.connect をモック化
    with mock.patch("app.db_access.psycopg2.connect") as mock_connect:
        # モック接続オブジェクトとカーソルを構築
        mock_conn = mock.Mock()
        mock_cursor = mock.Mock()

        # connect() が返すオブジェクトを設定
        mock_connect.return_value = mock_conn

        # conn.cursor() が返すカーソルを設定
        mock_conn.cursor.return_value.__enter__.return_value = mock_cursor

        # cursor.fetchall() の戻り値を設定
        mock_cursor.fetchall.return_value = mock_rows

        # テスト対象関数を実行
        result = db_access.fetch_user_names()

        # 期待される結果を検証
        assert result == ["Alice", "Bob"]

:white_check_mark: テスト設計のポイント

|項目|内容|
|モック対象|psycopg2.connect:DB接続を模倣|
|モック構造|connect→conn→cursor→fetchallの階層を再現|
|テスト対象|fetch_user_namesの戻り値が期待通りか確認|
|再現性|DBの状態に依存せず、常に同じ結果を返す|
|実現方法|pytestコマンドで実行可能|

:bulb: 解説:なぜこの構造なのか?

  • mock.patch() によって psycopg2.connect を差し替えることで、実際のDB接続を回避
  • conn.cursor() の戻り値に __enter__ を設定することで、with 文の中でもモックが機能
  • fetchall() の戻り値を固定することで、テストの再現性を確保
  • assert によって、関数の振る舞いが期待通りかを明示的に検証

5. pytestでテストを実行する:mockの動作確認

test_db_access.pyを pytest で実行することで、mock が正しく機能しているかを確認できます。ここでは、テストの実行方法と、mock によるテストの利点を整理します。

:rocket: テストの実行方法

プロジェクトルートで以下のコマンドを実行します:

pytest app/tests/test_db_access.py -v
  • -vオプションは詳細表示(verbose)を有効にします
  • pytesttest_*.pyという命名規則に従ってテスト関数を自動検出します

PASSEDが表示されれば、mockによるテストが正しく動作していることを意味します。

6. まとめ: python x mock でDBアクセスをテストする意義

本記事では、実際のDBに依存せずにDBアクセスを含む処理をテストする方法として、pytest x unittest.mock を活用する手順を紹介しました。

:paperclip: 関連リンク

本記事と関連する内容を、以下の記事で詳しく解説しています。必要に応じて併せてご参照ください:

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?