2010-04-30 74 views
3

假設我們有一個類UserService,屬性爲current_user。假設它在AppService類中使用。TDD在Python中被破解了嗎?

我們有AppService覆蓋測試。在測試設置中,我們存根出current_user一些模擬值:

UserService.current_user = 'TestUser' 

假設我們決定重新命名current_useractive_user。我們將其重命名爲UserService,但忘記更改其在AppService中的使用。

我們運行測試,他們通過!測試設置會添加屬性current_user,該屬性在AppService中仍然(錯誤但成功)使用。

現在我們的測試是無用的。他們通過但應用程序將在生產失敗。

我們不能依賴我們的測試套件==> TDD是不可能的。

TDD在Python中被破解了嗎?

+12

您無法在Python中使用TDD!= Python中的TDD已損壞。 – Pierreten 2010-04-30 00:23:33

+1

這是你設置被破壞的模擬值的方式,你應該使用普通的UserService構造函數來設置current_user(或者真實應用中的任何變化),而不是使用這個快捷方式。 – 2010-04-30 00:33:33

+1

@Pierreten,更好地告訴我我做錯了什麼 – 2010-04-30 00:37:12

回答

1

好的,我找到了解決方案。 Python庫Mockdoes what I want

下面是我最終的代碼。

模式和服務定義:

class User(object): 
    def __init__(self): 
     self.roles = [] 


class UserService(object): 
    def get_current_user(self): 
     return None # get from environment, database, etc. 

    current_user = property(get_current_user) 


class AppService(object): 
    def __init__(self, userService): 
     self.userService = userService 

    def can_write(self): 
     return 'admin' in self.userService.current_user.roles 

下面是如何測試can_write方法AppService與不同的用戶:

class AppServiceTests(unittest.TestCase): 
    def test_can_write(self): 
     user = User() 

     @patch_object(UserService, 'current_user', user) 
     def can_write(): 
      appService = AppService(UserService()) 
      return appService.can_write() 

     user.roles = ['admin'] 
     self.assertTrue(can_write()) 

     user.roles = ['user'] 
     self.assertFalse(can_write()) 

如果僅在UserService類重命名屬性current_user,你會得到錯誤當試圖修補對象時。這是我一直在尋找的行爲。

0

在更改之前,根據current_user的值,對象的行爲應該有所不同。我們稱之謂謂詞()。並赦免我的蟒蛇;考慮這個僞代碼:

UserService.current_user = 'X' 
assertFalse(obj.predicate()) 
UserService.current_user = 'Y' 
assertTrue(obj.predicate()) 

好嗎?所以,那是你的考驗。讓它通過。現在更改被測試的類,以便將current_user重命名爲active_user。現在,測試將在第一次斷言或第二次失敗時失敗。由於您不再更改以前稱爲current_user的字段的值,因此在兩種情況下謂詞都將爲false或true。現在你有一個非常專注的測試,當班級發生變化,使其他測試的設置無效時,它會提醒你。

+0

也許我不明白「predicate」應該是什麼樣子。如果'predicate'使用'current_user',那麼在將'UserService'的屬性重命名爲'active_user'之後,這個測試不會失敗,因爲這個測試本身設置了'current_user'。 – 2010-04-30 00:49:30

+1

您現有的測試依賴於該設置 - 它對active_user/current_user分割不敏感。所以你需要一個測試,如果你想要自動檢測到這樣的變化。作爲一個* unit *測試,你想要測試一些小的東西 - 你不希望你現有的測試測試任何測試,*和*域的名稱。因此,添加一個僅對字段名稱敏感的新測試。 – 2010-04-30 03:13:18

+0

檢查字段名稱的理想測試是'assert(hasattr(UserService,'current_user'))''。這個測試在我重命名爲'active_user'後會失敗,我會修復它。但是我會忘記第二次測試,它會一直是綠色的。 – 2010-04-30 08:29:22

2

這個問題真的不在TDD中,也不在Python中。首先,TDD不會給你一個證明,即當你所有的測試通過時,你的應用程序都是好的。想象一下,例如multiplyBy2()函數,它可以用輸入1,2,3和輸出2,4,8進行測試,現在想象一下,您將multiplyBy2實現爲平方。你所有的測試都通過了,你有100%的代碼覆蓋率,並且你的實現是錯誤的。你必須明白,TDD只能給你保證,一旦你的測試失敗了,你的應用程序出了問題,沒有什麼更多,沒有什麼不足。正如其他答案中所建議的那樣,問題在於你沒有失敗的測試。如果你使用了一些靜態類型的語言,編譯器會爲你做這個測試,並抱怨使用不存在的方法。這並不意味着你應該使用靜態類型語言,它只是意味着你需要用動態類型語言編寫更多的測試。 如果您有興趣強化代碼的正確性,您應該至少在運行時和正式規範中查看合同設計以確保正確性,以便至少對某些算法進行證明,但這與標準編碼相距甚遠。

+0

TDD也給了我保證,我的舊功能仍然存在(也就是沒有迴歸)。 'DBC'聽起來像一個可能的解決方案 – 2010-04-30 11:28:36

+0

@Konstantin不,它不,你沒有理解我的觀點。以我的乘法爲例,假設它以正確的方式實現,你的測試通過了,你做了一個改變 - 用平方代替乘法,你的測試仍然通過,但你可能已經引入了迴歸錯誤。真正只有失敗的測試會給你一些真實的信息。我非常喜歡TDD,但我認爲重要的是要真正理解它是如何工作的以及它的功能。 – 2010-04-30 13:26:02

+0

好的,我的問題是如何編寫測試,測試我的生產代碼,而不是測試自己的測試。我不能同意,如果測試沒有失敗,它不會提供任何信息。每個測試都包含一個關於系統的知識,如果你願意的話,它是不變的。不變不應該改變,如果在重構之後測試通過,那意味着你現在仍然擁有的功能仍然存在。如果'test_multiplyBy2'即使在使用正方形時也沒有失敗,那麼測試是不好的。我正在尋找一種方法來寫出一個好的測試。 – 2010-04-30 14:39:05

相關問題