2015-05-19 18 views
5

Tôi đã thử gần đây để rèn luyện bản thân rất nhiều trong các thực hành tốt nhất về kiểm thử đơn vị. Hầu hết nó có ý nghĩa hoàn hảo, nhưng có cái gì đó thường bị bỏ qua và/hoặc giải thích một cách tồi tệ: làm thế nào một đơn vị thử nghiệm các chức năng trang trí?Làm thế nào để kiểm tra đơn vị chức năng trang trí?

Giả sử tôi có mã này:

def stringify(func): 
    @wraps(func) 
    def wrapper(*args): 
     return str(func(*args)) 

    return wrapper 


class A(object): 
    @stringify 
    def add_numbers(self, a, b): 
     """ 
     Returns the sum of `a` and `b` as a string. 
     """ 
     return a + b 

tôi rõ ràng có thể viết các bài kiểm tra sau:

def test_stringify(): 
    @stringify 
    def func(x): 
     return x 

    assert func(42) == "42" 

def test_A_add_numbers(): 
    instance = MagicMock(spec=A) 
    result = A.add_numbers.__wrapped__(instance, 3, 7) 
    assert result == 10 

này mang lại cho tôi bảo hiểm 100%: Tôi biết rằng bất kỳ chức năng đó được trang trí với stringify() được kết quả của mình như là một chuỗi, và tôi biết rằng các chức năng A.add_numbers() undecorated trả về tổng của các đối số của nó. Vì vậy, bằng cách chuyển đổi, phiên bản trang trí của A.add_numbers() phải trả về tổng số đối số của nó, dưới dạng một chuỗi. Tất cả có vẻ tốt!

Tuy nhiên tôi không hoàn toàn hài lòng với điều này: các bài kiểm tra của tôi, như tôi đã viết chúng vẫn có thể vượt qua nếu tôi sử dụng một trang trí khác (làm điều gì đó khác, nói nhân kết quả bằng 2 thay vì đúc thành str) . Chức năng của tôi A.add_numbers sẽ không chính xác nữa nhưng các bài kiểm tra vẫn sẽ vượt qua. Không tuyệt vời.

Tôi có thể kiểm tra phiên bản trang trí A.add_numbers() nhưng sau đó tôi sẽ vượt qua mọi thứ kể từ khi trang trí của tôi đã được kiểm tra đơn vị.

Có vẻ như tôi đang thiếu thứ gì đó ở đây. Một chiến lược tốt để kiểm tra đơn vị chức năng trang trí là gì?

Trả lời

1

Tôi đã kết thúc việc chia trang trí của mình thành hai. Vì vậy, thay vì phải:

def stringify(func): 
    @wraps(func) 
    def wrapper(*args): 
     return str(func(*args)) 

    return wrapper 

tôi có:

def to_string(value): 
    return str(value) 

def stringify(func): 
    @wraps(func) 
    def wrapper(*args): 
     return to_string(func(*args)) 

    return wrapper 

nào cho phép tôi sau này chỉ đơn giản là mock-out to_string khi kiểm tra chức năng trang trí. Rõ ràng trong trường hợp ví dụ đơn giản nó có vẻ quá mức cần thiết, nhưng khi được sử dụng trên một trang trí thực sự làm một cái gì đó phức tạp hoặc đắt tiền (như mở một kết nối đến một DB, hoặc bất cứ điều gì), có thể thử nó ra là rất điều tốt đẹp.

0

Kiểm tra giao diện công khai công khai mã của bạn. Nếu bạn chỉ mong đợi mọi người gọi các chức năng được trang trí, thì đó là những gì bạn nên kiểm tra. Nếu trang trí cũng công khai, thì hãy kiểm tra điều đó (như bạn đã làm với test_stringify()). Đừng kiểm tra các phiên bản được bao bọc trừ khi mọi người gọi trực tiếp cho họ.

+0

Tôi tin rằng bạn đã đọc câu hỏi của mình một chút quá nhanh. Tôi không có ý định viết các bài kiểm tra chức năng mà là các bài kiểm tra đơn vị. – ereOn

+1

Thực tế là 'add_numbers' thực sự là một chức năng khác được trang trí bởi' stringify' là một chi tiết thực hiện. Kiểm tra rằng 'add_numbers' hoạt động như dự định, và nó sẽ không thành vấn đề nếu bạn sử dụng một trang trí khác (hoặc không có trang trí nào cả). – chepner

+0

@chepner: Bài kiểm tra đơn vị chính xác ở đây để kiểm tra chi tiết triển khai. Một lần nữa, nếu chúng ta đang nói về các bài kiểm tra chức năng/tích hợp, tôi sẽ đồng ý. Đó chỉ là chủ đề ở đây. – ereOn

1

Một trong những lợi ích chính của thử nghiệm đơn vị là cho phép tái cấu trúc với mức độ tin cậy nào đó mà mã được mã hóa tiếp tục hoạt động giống như trước đây. Giả sử bạn đã bắt đầu với

def add_numbers(a, b): 
    return str(a + b) 

def mult_numbers(a, b): 
    return str(a * b) 

Bạn sẽ có một số xét nghiệm như

def test_add_numbers(): 
    assert add_numbers(3, 5) == "8" 

def test_mult_numbers(): 
    assert mult_numbers(3, 5) == "15" 

Bây giờ, bạn quyết định cấu trúc lại những phần chung của mỗi chức năng (gói đầu ra trong một chuỗi), sử dụng stringify trang trí của bạn .

def stringify(func): 
    @wraps(func) 
    def wrapper(*args): 
     return str(func(*args)) 

    return wrapper 

@stringify 
def add_numbers(a, b): 
    return a + b 

@stringify 
def mult_numbers(a, b): 
    return a * b 

Bạn sẽ nhận thấy rằng các thử nghiệm ban đầu của bạn tiếp tục hoạt động sau khi tái cấu trúc này. Không quan trọng cách bạn triển khai add_numbersmult_numbers; những gì quan trọng là họ tiếp tục làm việc như được định nghĩa: returing một kết quả xâu chuỗi của hoạt động mong muốn.

Các thử nghiệm duy nhất còn lại bạn cần để viết là một trong để xác minh rằng stringify làm những gì được thiết kế để làm: trả về kết quả của hàm được trang trí như một chuỗi, mà test_stringify của bạn không.


vấn đề của bạn có vẻ là mà bạn muốn để điều trị chức năng nào, các trang trí, chức năng bọc như đơn vị. Nhưng nếu đó là trường hợp, sau đó bạn đang thiếu một thử nghiệm đơn vị: một trong đó thực sự chạy add_wrapper và kiểm tra đầu ra của nó, thay vì chỉ add_wrapper.__wrapped__. Nó không thực sự quan trọng nếu bạn xem xét thử nghiệm chức năng bọc như một bài kiểm tra đơn vị hoặc một bài kiểm tra tích hợp, nhưng bất cứ điều gì bạn gọi nó, bạn cần phải viết nó, bởi vì như bạn đã chỉ ra, nó không đủ để kiểm tra chức năng chưa mở trang trí riêng biệt.

+1

Tôi e rằng tôi đã chọn một ví dụ quá đơn giản. Mối quan tâm của tôi là nếu 'stringify()' bằng cách nào đó sai (nói rằng tôi đã nhập sai 'srt' thay vì' str'), bây giờ tất cả các thử nghiệm của các hàm trang trí của tôi đều thất bại, nhưng chúng không được: , nhưng các thử nghiệm của họ thất bại. Điều đó là không thể chấp nhận được vì nó đánh bại toàn bộ quan điểm của việc có các xét nghiệm đơn vị. – ereOn

+1

@ereOn: Sau đó viết một bài kiểm tra riêng biệt cho 'stringify()'. Điều đó cũng sẽ thất bại, và sau đó bạn sẽ biết đó là trang trí đã phá vỡ. – Kevin

+0

@Kevin: Có một số bài kiểm tra không liên quan không thành công cho một thay đổi một dòng rõ ràng là dấu hiệu cho thấy một bài kiểm tra quá mức ở vị trí đầu tiên. Trong ví dụ rất đơn giản này sẽ rõ ràng những gì đã phá vỡ. Trong một phức tạp hơn nhiều, không quá nhiều. – ereOn

Các vấn đề liên quan