2010-09-30 25 views
46

Giả sử tôi có đoạn mã sau trong một thử nghiệm đơn vị Python:Khẳng định rằng một phương pháp được gọi trong một thử nghiệm đơn vị Python

aw = aps.Request("nv1") 
aw2 = aps.Request("nv2", aw) 

Có một cách dễ dàng để khẳng định rằng một phương pháp cụ thể (trong trường hợp của tôi aw.Clear()) được gọi trong dòng thứ hai của thử nghiệm? ví dụ. là có một cái gì đó như thế này:

#pseudocode: 
assertMethodIsCalled(aw.Clear, lambda: aps.Request("nv2", aw)) 

Trả lời

80

tôi sử dụng Mock cho việc này:

from mock import patch 
from PyQt4 import Qt 

@patch.object(Qt.QMessageBox, 'aboutQt') 
def testShowAboutQt(self, mock): 
    self.win.actionAboutQt.trigger() 
    self.assertTrue(mock.called) 

Đối với trường hợp của bạn, nó có thể trông như thế này:

import mock 

def testClearWasCalled(self): 
    aw = aps.Request("nv1") 
    with patch.object(aw, 'Clear') as mock: 
     aw2 = aps.Request("nv2", aw) 

    mock.assert_called_with(42) # or mock.assert_called_once_with(42) 

Mock hỗ trợ khá nhiều tính năng hữu ích, trong đó có cách để vá một đối tượng hoặc mô-đun , cũng như kiểm tra xem điều đúng có được gọi hay không, v.v.

Caveat emptor! (người mua hãy cẩn thận!)

Nếu bạn nhập nhầm assert_called_with (để assert_called_once hoặc assert_called_wiht) thử nghiệm của bạn vẫn có thể chạy, như Mock sẽ nghĩ rằng đây là một chức năng chế giễu và vui vẻ đi cùng, trừ khi bạn sử dụng autospec=true. Để biết thêm thông tin, hãy đọc assert_called_once: Threat or Menace.

+3

+1 để làm sáng tỏ thế giới của tôi một cách cụ thể bằng mô-đun Mock tuyệt vời. –

+0

@RonCohen: Vâng, nó khá tuyệt vời và luôn trở nên tốt hơn. :) – Macke

+1

Trong khi sử dụng giả chắc chắn là con đường để đi, tôi khuyên không nên sử dụng assert_called_once, chỉ đơn giản là không tồn tại :) – FelixCQ

4

Bạn có thể thử ra aw.Clear, hoặc bằng tay hoặc sử dụng một khuôn khổ kiểm tra như pymox. Bằng tay, bạn muốn làm điều đó bằng một cái gì đó như thế này:

class MyTest(TestCase): 
    def testClear(): 
    old_clear = aw.Clear 
    clear_calls = 0 
    aw.Clear = lambda: clear_calls += 1 
    aps.Request('nv2', aw) 
    assert clear_calls == 1 
    aw.Clear = old_clear 

Sử dụng pymox, bạn muốn làm điều đó như thế này:

class MyTest(mox.MoxTestBase): 
    def testClear(): 
    aw = self.m.CreateMock(aps.Request) 
    aw.Clear() 
    self.mox.ReplayAll() 
    aps.Request('nv2', aw) 
+0

Tôi thích cách tiếp cận này cũng vậy, mặc dù tôi vẫn muốn old_clear để gọi. Điều này làm cho nó rõ ràng những gì đang xảy ra. –

7

Vâng, tôi có thể cung cấp cho bạn phác thảo nhưng Python của tôi là một hơi mệt và tôi quá bận rộn để giải thích chi tiết.

Về cơ bản, bạn cần phải đặt một proxy trong phương pháp đó sẽ gọi ban đầu, ví dụ:

class fred(object): 
    def blog(self): 
    print "We Blog" 


class methCallLogger(object): 
    def __init__(self, meth): 
    self.meth = meth 

    def __call__(self, code=None): 
    self.meth() 
    # would also log the fact that it invoked the method 

#example 
f = fred() 
f.blog = methCallLogger(f.blog) 

StackOverflow answer này về callable có thể giúp bạn hiểu được ở trên.

Cụ thể hơn:

Mặc dù câu trả lời đã được chấp nhận, do các cuộc thảo luận thú vị với Glenn và có một vài phút miễn phí, tôi muốn mở rộng về câu trả lời của tôi:

# helper class defined elsewhere 
class methCallLogger(object): 
    def __init__(self, meth): 
    self.meth = meth 
    self.was_called = False 

    def __call__(self, code=None): 
    self.meth() 
    self.was_called = True 

#example 
class fred(object): 
    def blog(self): 
    print "We Blog" 

f = fred() 
g = fred() 
f.blog = methCallLogger(f.blog) 
g.blog = methCallLogger(g.blog) 
f.blog() 
assert(f.blog.was_called) 
assert(not g.blog.was_called) 
+0

đẹp. Tôi đã thêm số lượng cuộc gọi vào methCallLogger để tôi có thể khẳng định nó. –

+0

Điều này qua giải pháp toàn diện, khép kín mà tôi đã cung cấp? Nghiêm túc? –

+0

@Glenn Tôi rất mới với Python - có thể cái của bạn tốt hơn - tôi chưa hiểu hết. Tôi sẽ dành một chút thời gian sau khi thử nó. –

13

tôi 'không nhận thức được bất cứ điều gì được xây dựng trong. Việc triển khai thực hiện khá đơn giản:

class assertMethodIsCalled(object): 
    def __init__(self, obj, method): 
     self.obj = obj 
     self.method = method 

    def called(self, *args, **kwargs): 
     self.method_called = True 
     self.orig_method(*args, **kwargs) 

    def __enter__(self): 
     self.orig_method = getattr(self.obj, self.method) 
     setattr(self.obj, self.method, self.called) 
     self.method_called = False 

    def __exit__(self, exc_type, exc_value, traceback): 
     assert getattr(self.obj, self.method) == self.called, 
      "method %s was modified during assertMethodIsCalled" % self.method 

     setattr(self.obj, self.method, self.orig_method) 

     # If an exception was thrown within the block, we've already failed. 
     if traceback is None: 
      assert self.method_called, 
       "method %s of %s was not called" % (self.method, self.obj) 

class test(object): 
    def a(self): 
     print "test" 
    def b(self): 
     self.a() 

obj = test() 
with assertMethodIsCalled(obj, "a"): 
    obj.b() 

Điều này yêu cầu chính đối tượng sẽ không sửa đổi self.b, điều này hầu như luôn đúng.

+0

Tôi đã nói rằng Python của tôi đã bị rỉ sét, mặc dù tôi đã thử nghiệm giải pháp của mình để đảm bảo nó hoạt động :-) Tôi đã mã hóa Python trước phiên bản 2.5, trên thực tế tôi chưa bao giờ sử dụng 2,5 cho bất kỳ Python quan trọng nào. Khi xem xét giải pháp của bạn, tôi đã tìm thấy http://effbot.org/zone/python-with-statement.htm như một mô tả rõ ràng. Tôi sẽ khiêm tốn đề xuất phương pháp tiếp cận của tôi trông nhỏ hơn và có thể dễ áp ​​dụng hơn nếu bạn muốn nhiều hơn một điểm ghi nhật ký, chứ không phải lồng nhau "với". Tôi thực sự muốn bạn giải thích nếu có bất kỳ lợi ích cụ thể nào của bạn. –

+0

@Andy: Câu trả lời của bạn nhỏ hơn vì nó là một phần: nó không thực sự kiểm tra kết quả, nó không khôi phục lại chức năng ban đầu sau khi kiểm tra để bạn có thể tiếp tục sử dụng đối tượng, và bạn phải liên tục viết mã để làm tất cả điều đó một lần nữa mỗi khi bạn viết một bài kiểm tra. Số dòng mã hỗ trợ không quan trọng; lớp này đi trong mô-đun thử nghiệm riêng của nó, không phải nội tuyến trong một chuỗi - điều này có một hoặc hai dòng mã trong thử nghiệm thực tế. –

+0

+1 để sử dụng trình quản lý ngữ cảnh, ý tưởng gọn gàng! –

10

Có nếu bạn đang sử dụng Python 3.3+. Bạn có thể sử dụng được xây dựng trong unittest.mock để khẳng định phương pháp được gọi là. Đối với Python 2.6+ sử dụng backport lăn Mock, đó là điều tương tự.

Dưới đây là một ví dụ nhanh chóng trong trường hợp của bạn:

from unittest.mock import MagicMock 
aw = aps.Request("nv1") 
aw.Clear = MagicMock() 
aw2 = aps.Request("nv2", aw) 
assert aw.Clear.called 
Các vấn đề liên quan