1. はじめに
PythonでDBアクセスを含むコードをテストするとき、実際のDBに接続してテストを行うと、以下のような課題に直面しがちです。
- 実際のDBに接続すると、テストが遅くなる
- DBの状態に依存してしまい、テストの再現性が低くなる
- CI環境でDBを立ち上げるのが面倒
- テストの失敗原因が「コード」なのか「DBの状態」なのか切り分けづらい
こうした課題を解決するために、本記事では pytest × mock を使って「DBアクセスを含む処理を、実際のDBに依存せずにテストする方法」を紹介します。
本記事では以下の構成で進めていきます:
本記事のゴール
- 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
初期化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コードを用意する
実装方針
「PostgreSQLに接続して users テーブルから名前一覧を取得する関数」を作り、後で mock によってテスト可能な構造にします。
| 要素 | 内容 |
|---|---|
| 使用ライブラリ | `psycopg2'(PostgresSQL用標準ドライバ) |
| 接続情報 |
localhost:5432,DB名: postgres,ユーザ:postgres,パスワード: password
|
| 関数 |
fetch_user_names():ユーザ名一覧を返す |
| テスト設計 |
psycopg2.connect()をmockで差し替え可能な構造にする |
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()
実装ポイント
- 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"]
テスト設計のポイント
|項目|内容|
|モック対象|psycopg2.connect:DB接続を模倣|
|モック構造|connect→conn→cursor→fetchallの階層を再現|
|テスト対象|fetch_user_namesの戻り値が期待通りか確認|
|再現性|DBの状態に依存せず、常に同じ結果を返す|
|実現方法|pytestコマンドで実行可能|
解説:なぜこの構造なのか?
-
mock.patch()によってpsycopg2.connectを差し替えることで、実際のDB接続を回避 -
conn.cursor()の戻り値に__enter__を設定することで、with 文の中でもモックが機能 -
fetchall()の戻り値を固定することで、テストの再現性を確保 -
assertによって、関数の振る舞いが期待通りかを明示的に検証
5. pytestでテストを実行する:mockの動作確認
test_db_access.pyを pytest で実行することで、mock が正しく機能しているかを確認できます。ここでは、テストの実行方法と、mock によるテストの利点を整理します。
テストの実行方法
プロジェクトルートで以下のコマンドを実行します:
pytest app/tests/test_db_access.py -v
-
-vオプションは詳細表示(verbose)を有効にします -
pytestはtest_*.pyという命名規則に従ってテスト関数を自動検出します
PASSEDが表示されれば、mockによるテストが正しく動作していることを意味します。
6. まとめ: python x mock でDBアクセスをテストする意義
本記事では、実際のDBに依存せずにDBアクセスを含む処理をテストする方法として、pytest x unittest.mock を活用する手順を紹介しました。
関連リンク
本記事と関連する内容を、以下の記事で詳しく解説しています。必要に応じて併せてご参照ください:
-
pytest入門:Pythonで始めるテスト自動化
→ pytestの基本構文やテスト設計の考え方を体系的に整理 -
Python関数設計の基本と応用:型ヒント・with構文・モジュール化まで
→ テストしやすい関数設計のための構造的な設計指針を解説 -
WSL2 × DockerでPostgreSQL環境を構築し、Pythonで接続確認するまで
→ 実DB環境の構築とPythonからの接続確認までの手順を整理