2013-04-04 104 views
38

在我嘗試學習TDD時,試圖學習單元測試和使用python進行模擬。慢慢地掌握它,但不確定我是否正確地做到了這一點。預先警告:我使用python 2.4,因爲供應商API是預編譯的2.4 pyc文件,所以我使用模擬0.8.0和單元測試(不是unittest2)如何在單元測試中正確使用python模擬setUp

鑑於此示例代碼在'mymodule.py '

import ldap 

class MyCustomException(Exception): 
    pass 

class MyClass: 
    def __init__(self, server, user, passwd): 
     self.ldap = ldap.initialize(server) 
     self.user = user 
     self.passwd = passwd 

    def connect(self): 
     try: 
      self.ldap.simple_bind_s(self.user, self.passwd) 
     except ldap.INVALID_CREDENTIALS: 
      # do some stuff 
      raise MyCustomException 

現在在我的測試用例文件'test_myclass.py'中,我想模擬ldap對象。 ldap.initialize返回ldap.ldapobject.SimpleLDAPObject,所以我想這將是我不得不嘲笑的方法。

import unittest 
from ldap import INVALID_CREDENTIALS 
from mock import patch, MagicMock 
from mymodule import MyClass 

class LDAPConnTests(unittest.TestCase): 
    @patch('ldap.initialize') 
    def setUp(self, mock_obj): 
     self.ldapserver = MyClass('myserver','myuser','mypass') 
     self.mocked_inst = mock_obj.return_value 

    def testRaisesMyCustomException(self): 
     self.mocked_inst.simple_bind_s = MagicMock() 
     # set our side effect to the ldap exception to raise 
     self.mocked_inst.simple_bind_s.side_effect = INVALID_CREDENTIALS 
     self.assertRaises(mymodule.MyCustomException, self.ldapserver.connect) 

    def testMyNextTestCase(self): 
     # blah blah 

信息我幾個問題:

  1. 是否正確? :)
  2. 這是正確的方法來嘗試和模擬一個對象,在我正在測試的類內實例化?
  3. 可以在setUp上調用@patch裝飾器,或者這會導致奇怪的副作用嗎?
  4. 有沒有辦法讓模擬來提升ldap.INVALID_CREDENTIALS異常而不必將異常導入到我的測試用例文件中?
  5. 我應該使用patch.object()來代替嗎?如果是這樣,怎麼辦?

謝謝。

+1

1-3)似乎沒什麼問題... 4)'進口ldap'代替,並設置'side_effect = ldap.INVALID_CREDENTIALS'? – Chris 2013-04-04 22:28:38

+0

您可以隨時進行相同的測試,但只能使用自己製作的簡單對象... – shackra 2014-05-11 20:09:36

回答

36

參見:26.5.3.4. Applying the same patch to every test method

更有意義設立該修補上的設置這樣,如果你想爲所有的測試方法進行修補。

+2

對於Python3之前的模擬(位於http://www.voidspace.org.uk/python/mock/上),請參閱[在每種測試方法上應用相同的補丁](http://www.voidspace.org.uk/python/mock/examples.html#applying-the-same-patch-to-every-test-method)。 – musiphil 2015-03-17 21:59:02

+2

我剛剛遇到了一個問題,我在TestCase類上有一個類級別的模擬,並假定它在'setUp()'方法中進行調用時已經存在。不是這種情況; class級別的mock不能及時應用於'setUp()'中。我解決了這個問題,而是創建了一個我在所有測試中使用的幫助器方法。不知道這是最好的方法,但它的工作原理。 – berto 2016-03-31 15:05:57

+0

@berto如果您在答覆中擴充您的評論,我認爲這會有所幫助。這是一個不同的,可能比其他人更容易的解決方案。 – KobeJohn 2016-11-17 03:47:01

4

如果你有很多的補丁應用,並且希望他們申請到的東西在設置方法初始化也試試這個:

def setUp(self): 
    self.patches = { 
     "sut.BaseTestRunner._acquire_slot": mock.Mock(), 
     "sut.GetResource": mock.Mock(spec=GetResource), 
     "sut.models": mock.Mock(spec=models), 
     "sut.DbApi": make_db_api_mock() 
    } 

    self.applied_patches = [mock.patch(patch, data) for patch, data in self.patches.items()] 
    [patch.apply for patch in self.applied_patches] 
    . 
    . rest of setup 
    . 


def tearDown(self): 
    patch.stop_all() 
+3

考慮在'tearDown()'中使用'patch.stop_all()'。 – 2015-03-30 15:42:09

+0

我試過了 - 似乎apply_patches也需要啓動。考慮一下這樣的一行:'在self.applied_pa​​tches:patch.start()'中使用補丁 – F1Rumors 2016-04-04 18:44:47

3

我會回答你的問題開始,然後我就給如何與patch()setUp()進行交互的詳細示例。

  1. 我不認爲它看起來不錯,詳情見答案#3。
  2. 是的,實際調用補丁看起來應該嘲笑你想要的對象。
  3. 不,你幾乎從不想在setUp()上使用@patch()修飾器。你很幸運,因爲該對象是在setUp()中創建的,並且在測試方法中永遠不會創建。
  4. 我不知道有什麼辦法可以讓一個模擬對象拋出一個異常,而不會將這個異常導入到測試用例文件中。
  5. 我在這裏看不到有任何需要patch.object()。它只是讓你修補一個對象的屬性,而不是將目標指定爲一個字符串。

要擴大答案#3,問題在於patch()裝飾器只適用於裝飾功能正在運行。一旦setUp()返回,修補程序將被刪除。在你的情況下,這是有效的,但我敢打賭它會讓看着這個測試的人感到困惑。如果你真的只想在setUp()期間發生這個補丁,我會建議使用with聲明來明確補丁將被刪除。

以下示例有兩個測試用例。 TestPatchAsDecorator顯示裝飾課程將在測試方法期間應用該補丁,但不在setUp()期間。 TestPatchInSetUp顯示瞭如何應用該修補程序,以便在setUp()和測試方法中都可以使用該修補程序。調用self.addCleanUp()可確保在tearDown()期間修補程序將被刪除。

import unittest 
from mock import patch 


@patch('__builtin__.sum', return_value=99) 
class TestPatchAsDecorator(unittest.TestCase): 
    def setUp(self): 
     s = sum([1, 2, 3]) 

     self.assertEqual(6, s) 

    def test_sum(self, mock_sum): 
     s1 = sum([1, 2, 3]) 
     mock_sum.return_value = 42 
     s2 = sum([1, 2, 3]) 

     self.assertEqual(99, s1) 
     self.assertEqual(42, s2) 


class TestPatchInSetUp(unittest.TestCase): 
    def setUp(self): 
     patcher = patch('__builtin__.sum', return_value=99) 
     self.mock_sum = patcher.start() 
     self.addCleanup(patcher.stop) 

     s = sum([1, 2, 3]) 

     self.assertEqual(99, s) 

    def test_sum(self): 
     s1 = sum([1, 2, 3]) 
     self.mock_sum.return_value = 42 
     s2 = sum([1, 2, 3]) 

     self.assertEqual(99, s1) 
     self.assertEqual(42, s2) 
相關問題