8

私のアプリケーションには、一般的に使用されるクエリを保持する各モデルのクラスがあります (DDD 言語の「リポジトリ」のようなものだと思います)。これらの各クラスには、構築時にクエリを作成するための SQLAlchemy セッション オブジェクトが渡されます。単体テストで特定のクエリが実行されていることをアサートする最良の方法を理解するのに少し苦労しています。どこにでもあるブログの例を使用して、列と属性「日付」と「コンテンツ」を持つ「投稿」モデルがあるとします。また、「date」の降順ですべての投稿を照会する「find_latest」メソッドを使用した「PostRepository」もあります。次のようになります。

from myapp.models import Post

class PostRepository(object):
    def __init__(self, session):
        self._s = session

    def find_latest(self):
        return self._s.query(Post).order_by(Post.date.desc())

Post.date.desc() 呼び出しをモックするのに問題があります。現在、単体テストで Post.date.desc のモックにモンキー パッチを適用していますが、より良いアプローチがある可能性が高いと感じています。

編集:モック オブジェクトに mox を使用しています。現在の単体テストは次のようになります。

import unittest
import mox

class TestPostRepository(unittest.TestCase):

    def setUp(self):
        self._mox = mox.Mox()

    def _create_session_mock(self):
        from sqlalchemy.orm.session import Session
        return self._mox.CreateMock(Session)

    def _create_query_mock(self):
        from sqlalchemy.orm.query import Query
        return self._mox.CreateMock(Query)

    def _create_desc_mock(self):
        from myapp.models import Post
        return self._mox.CreateMock(Post.date.desc)

    def test_find_latest(self):
        from myapp.models.repositories import PostRepository
        from myapp.models import Post

        expected_result = 'test'

        session_mock = self._create_session_mock()
        query_mock = self._create_query_mock()
        desc_mock = self._create_desc_mock()

        # Monkey patch
        tmp = Post.date.desc
        Post.date.desc = desc_mock

        session_mock.query(Post).AndReturn(query_mock)
        query_mock.order_by(Post.date.desc().AndReturn('test')).AndReturn(query_mock)
        query_mock.offset(0).AndReturn(query_mock)
        query_mock.limit(10).AndReturn(expected_result)

        self._mox.ReplayAll()
        r = PostRepository(session_mock)

        result = r.find_latest()
        self._mox.VerifyAll()

        self.assertEquals(expected_result, result)

        Post.date.desc = tmp

これは機能しますが、醜く感じますが、「Post.date.desc().AndReturn('test')」の「AndReturn('test')」部分がないと失敗する理由がわかりません

4

2 に答える 2

13

クエリのテストにモックを使用しても、実際にはそれほどメリットが得られていないと思います。テストは、実装ではなく、コードのロジックをテストする必要があります。より良い解決策は、新しいデータベースを作成し、それにいくつかのオブジェクトを追加し、そのデータベースでクエリを実行して、正しい結果が返されるかどうかを判断することです。例えば:


# Create the engine. This starts a fresh database
engine = create_engine('sqlite://')
# Fills the database with the tables needed.
# If you use declarative, then the metadata for your tables can be found using Base.metadata
metadata.create_all(engine)
# Create a session to this database
session = sessionmaker(bind=engine)()

# Create some posts using the session and commit them
...

# Test your repository object...
repo = PostRepository(session)
results = repo.find_latest()

# Run your assertions of results
...

今、あなたは実際にコードのロジックをテストしています。これは、メソッドの実装を変更できることを意味しますが、クエリが正しく機能する限り、テストは合格するはずです。必要に応じて、このメソッドを、すべてのオブジェクトを取得し、結果のリストをスライスするクエリとして記述できます。当然のことながら、テストは合格します。後で、SA式APIを使用してクエリを実行するように実装を変更すると、テストに合格します。

覚えておくべきことの1つは、sqliteが別のデータベースタイプとは異なる動作をするという問題が発生する可能性があることです。インメモリでsqliteを使用すると、高速なテストが可能になりますが、これらのテストに真剣に取り組みたい場合は、本番環境で使用するのと同じタイプのデータベースに対しても実行することをお勧めします。

于 2011-03-13T06:30:10.580 に答える