2011-02-25 23 views
7

在我的應用程序中,每個模型都有一個用於保存常用查詢的類(我猜它有點像DDD語言中的「Repository」)。每個這些類都通過SQLAlchemy會話對象來創建查詢。在確定某些查詢正在我的單元測試中運行的最佳方式時,我有點困難。使用無處不在的博客示例,假設我有一個包含列和屬性「日期」和「內容」的「發佈」模型。我還有一個名爲「find_latest」的「PostRepository」,它應該按照「date」降序查詢所有帖子。它看起來像這樣:Python SQLAlchemy - 嘲笑模型屬性的「desc」方法

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 

這並不工作,但感覺醜陋,我不知道爲什麼它不能沒有「AndReturn ('test')「piece of」Post.date.desc()。andReturn('test')「

回答

13

我不認爲你真的通過使用mock來測試你的查詢獲得了很多好處。測試應該測試代碼的邏輯,而不是實現。更好的解決方案是創建一個新的數據庫,添加一些對象,在該數據庫上運行查詢,然後確定是否獲得了正確的結果。例如:


# 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運行查詢,並且測試會通過。

要記住的一件事是,你可能有問題與其他數據庫類型的行爲不同。使用內存中的sqlite可以爲你提供快速的測試,但是如果你想要認真對待這些測試,你可能會想要在生產中使用相同類型的數據庫來運行它們。

+0

這很有道理,我對代碼的細節太在意了。感謝您的洞察力。 – 2011-03-13 19:37:13

+5

你在說什麼,而不是做一個單元測試(一個邏輯單元),做一個集成測試(與數據庫)。這是一種有效的方法,可能對於ORM來說最爲明智,但這些測試的性能會受到一兩個量級的影響。對? – 2014-12-10 06:53:04

+1

你是正確的,編寫一個能以某種方式訪問​​較慢資源(不管是磁盤驅動器,外部服務還是數據庫)的測試(無論你想稱之爲「單元」測試還是「集成」測試)將意味着比不訪問這種資源的測試慢。 – 2014-12-10 15:58:36