とらすたのーと

試したこと。学んだこと。おぼえがき。

SQLiteを使ったPython+SQLAlchemyのテストコードを書いてみた

概要

前回は、mock-alchemyを使ってテストコードを書いてみました。 今回は、SQLiteを使った場合のテストコードを書いてみようと思います。

やりたいこと

今回のテストも、レポジトリ層の関数に対してのテストとなります。ただ前回と違い、今回はデータベース(SQLite)を使用するので、「データベースがどうなったか」を確認することが出来ます。そこを活かして、今回のテスト内容は以下のイメージです。

  • レポジトリ層の関数を呼び出した時、データベースに対して意図した操作(INSERT、SELECT、UPDATE、DELETEなど)が行われ、データベースが意図したように変化したか。
  • データベース操作の結果が返ってきた時、その結果に基づいて、関数の戻り値が想定通りの内容になっているか。

これらを、Create(作成)、Read(読み取り)、Update(更新)、Delete(削除)の各操作で確認していきます。

事前準備

テスト対象のコードは、前回と同じプログラムです。
前回はこちら

  • models.py
  • repository.py

やってみた

それでは、実際にテストコードを書いていきます。 テストコード用として下記を用意しました。

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Base, User
from repository import UserRepository
from unittest.mock import MagicMock

@pytest.fixture
def session():
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    TestingSessionLocal = sessionmaker(bind=engine)
    db = TestingSessionLocal()
    yield db
    db.close()

@pytest.fixture
def repo(session):
    return UserRepository(session)

def test_create_user():
    pass

def test_get_user():
    pass

def test_update_user():
    pass

def test_delete_user():
    pass

CREATE

まずはCreate。
テスト内容としては、

  • 関数に対して登録したい値を渡した時、データベースに登録されたか。
  • 関数の戻り値として、登録された結果が呼び出し元に返されるか。

となります。
実際に書いてみたのがこちら。

def test_create_user(repo):
    # given: 
    name = "Taro"
    email = "taro@example.com"
    # when: ユーザーを作成する
    user = repo.create(name=name, email=email)
    # then: ユーザーが正しく作成されていることを確認する
    assert user.id is not None
    assert user.name == name
    assert user.email == email

実際の登録処理を動かし、その結果が想定通りか確認しています。

READ

次はRead。
テスト内容としては、

  • 関数に対して取得したい値(条件)を渡した時、データベースの値を取得したか。
  • 関数の戻り値として、取得された結果が呼び出し元に返されるか。

となります。
実際に書いてみたのがこちら。

def test_get_user(repo):
    # given: 既存のユーザーを作成する
    user = repo.create(name="Jiro", email="jiro@example.com")
    # when: ユーザーIDでユーザーを取得する
    found = repo.get(user.id)
    # then: ユーザーが正しく取得できることを確認する
    assert found is not None
    assert found.name == "Jiro"

初めに登録処理でデータを登録し、そのあとに取得処理を動かすことで、結果が想定通りか確認します。

UPDATE

次はUpdate。
テスト内容としては、

  • 関数に対して更新したい値を渡した時、データベースの値を更新したか。
  • 関数の戻り値として、更新された結果が呼び出し元に返されるか。

となります。
実際に書いてみたのがこちら。

def test_update_user(repo):
    # given: 既存のユーザーを作成する
    user = repo.create(name="Saburo", email="saburo@example.com")
    # when: ユーザー情報を更新する
    updated = repo.update(user.id, name="SaburoUpdated", email="saburo2@example.com")
    # then: ユーザー情報が正しく更新されていることを確認する
    assert updated.name == "SaburoUpdated"
    assert updated.email == "saburo2@example.com"

こちらも初めに登録処理でデータを登録し、そのあとに更新処理を動かすことで、戻り値が想定通りか確認します。

DELETE

次はDelete。
テスト内容としては、

  • 関数に対して削除したい値(条件)を渡した時、データベースのデータを削除したか。
  • 関数の戻り値として、削除された結果が呼び出し元に返されるか。

となります。
実際に書いてみたのがこちら。

def test_delete_user(repo):
    # given: 既存のユーザーを作成する
    user = repo.create(name="Shiro", email="shiro@example.com")
    # when: ユーザーを削除する
    result = repo.delete(user.id)
    # then: ユーザーが削除され、取得できないことを確認する
    assert result is True
    assert repo.get(user.id) is None

こちらも初めに登録処理でデータを登録し、そのあとに削除処理を動かします。
戻り値の確認と、最初に登録したデータを取得してデータがないことを確認しています。

まとめ

前回に比べて、かなり記述量が少なくテストを書くことが出来ました。これは、実際のデータベースを使用することで得られる大きな利点ですね。
ただ、取得、更新、削除のテストでデータ作成の処理を使っているので、作成のロジックがエラーとなったときは、すべてのテストがエラーとなりますね(笑
まぁでも、問題なく動きの確認もできますし、次にテストコードを書く機会があれば、SQLiteを使って書こうと思いました。