2009-11-04 27 views
26

thể trùng lặp:
How to generate dynamic (parametrized) unit tests in python?Viết một tái sử dụng (parametrized) phương pháp unittest.TestCase

Tôi đang viết bài kiểm tra bằng cách sử dụng gói unittest, và tôi muốn tránh mã lặp đi lặp lại . Tôi sẽ thực hiện một số bài kiểm tra mà tất cả đều yêu cầu một phương pháp rất giống nhau, nhưng chỉ với một giá trị khác nhau mỗi lần. Một ví dụ đơn giản và vô ích sẽ là:

class ExampleTestCase(unittest.TestCase): 

    def test_1(self): 
     self.assertEqual(self.somevalue, 1) 

    def test_2(self): 
     self.assertEqual(self.somevalue, 2) 

    def test_3(self): 
     self.assertEqual(self.somevalue, 3) 

    def test_4(self): 
     self.assertEqual(self.somevalue, 4) 

Có cách nào để viết ví dụ trên mà không lặp lại tất cả mã mỗi lần, nhưng thay vào đó viết một phương pháp chung, ví dụ:

def test_n(self, n): 
     self.assertEqual(self.somevalue, n) 

và yêu cầu thử nghiệm này với các yếu tố đầu vào khác nhau?

+0

Bạn đã tìm thấy một cách để làm điều này? Hoặc có thể bạn đã tìm thấy một công cụ khác cho công việc này? Tôi cũng cần hành vi như vậy. – legesh

+0

http://thebongtraveller.blogspot.sg/2015/12/art-of-unittest-writing-auto-generation.html Đây có phải là điều tương tự không? –

Trả lời

0

Có lẽ cái gì đó như:

def test_many(self): 
    for n in range(0,1000): 
     self.assertEqual(self.somevalue, n) 
+1

Đây không phải là những gì tôi đang tìm kiếm vì nó sẽ dừng ngay sau khi một trong các bài kiểm tra thất bại. Tôi đang tìm một giải pháp mà một thử nghiệm thất bại không ngăn cản những người khác bị xử tử. – astrofrog

+0

@Morgoth: Tại sao? Tại sao chạy thử nghiệm nhiều hơn khi bạn biết bạn đã có một thất bại? –

+3

Vì không có gì để nói các bài kiểm tra khác cũng sẽ thất bại. Điều quan trọng là phải biết nếu tất cả các bài kiểm tra thất bại, hoặc chỉ một hoặc hai, vì điều này có thể giúp chẩn đoán vấn đề. Thật tuyệt khi biết ngay từ đầu bạn đã có bao nhiêu thất bại, bạn không muốn phải sửa chúng từng cái một cho đến khi chúng dừng lại. – astrofrog

1

Tôi đoán những gì bạn muốn là "kiểm tra tham số".

Tôi không nghĩ rằng mô-đun unittest hỗ trợ này (không may), nhưng nếu tôi đã bổ sung thêm tính năng này nó sẽ giống như thế này:

# Will run the test for all combinations of parameters 
@RunTestWith(x=[0, 1, 2, 3], y=[-1, 0, 1]) 
def testMultiplication(self, x, y): 
    self.assertEqual(multiplication.multiply(x, y), x*y) 

Với module unittest hiện có, một trang trí đơn giản như thế này sẽ không thể "tái tạo" phép thử nhiều lần, nhưng tôi nghĩ điều này có thể thực hiện được bằng cách kết hợp trang trí và metaclass (metaclass nên quan sát tất cả các phương thức 'test *' và sao chép (theo các tên được tạo tự động khác nhau) có trang trí được áp dụng).

3

Nếu bạn thực sự muốn có nhiều unitttest thì bạn cần nhiều phương pháp. Cách duy nhất để có được điều đó là thông qua một số loại tạo mã. Bạn có thể làm điều đó thông qua một metaclasses, hoặc bằng cách tinh chỉnh lớp sau khi định nghĩa, bao gồm (nếu bạn đang sử dụng Python 2.6) thông qua một trình trang trí lớp.

Đây là giải pháp tìm kiếm các thành viên 'multitest' và 'multitest_values' đặc biệt và sử dụng các thành viên đó để xây dựng các phương pháp thử nghiệm nhanh chóng. Không tao nhã, nhưng nó gần như những gì bạn muốn:

import unittest 
import inspect 

class SomeValue(object): 
    def __eq__(self, other): 
     return other in [1, 3, 4] 

class ExampleTestCase(unittest.TestCase): 
    somevalue = SomeValue() 

    multitest_values = [1, 2, 3, 4] 
    def multitest(self, n): 
     self.assertEqual(self.somevalue, n) 

    multitest_gt_values = "ABCDEF" 
    def multitest_gt(self, c): 
     self.assertTrue(c > "B", c) 


def add_test_cases(cls): 
    values = {} 
    functions = {} 
    # Find all the 'multitest*' functions and 
    # matching list of test values. 
    for key, value in inspect.getmembers(cls): 
     if key.startswith("multitest"): 
      if key.endswith("_values"): 
       values[key[:-7]] = value 
      else: 
       functions[key] = value 

    # Put them together to make a list of new test functions. 
    # One test function for each value 
    for key in functions: 
     if key in values: 
      function = functions[key] 
      for i, value in enumerate(values[key]): 
       def test_function(self, function=function, value=value): 
        function(self, value) 
       name ="test%s_%d" % (key[9:], i+1) 
       test_function.__name__ = name 
       setattr(cls, name, test_function) 

add_test_cases(ExampleTestCase) 

if __name__ == "__main__": 
    unittest.main() 

Đây là sản phẩm từ khi tôi chạy nó

% python stackoverflow.py 
.F..FF.... 
====================================================================== 
FAIL: test_2 (__main__.ExampleTestCase) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "stackoverflow.py", line 34, in test_function 
    function(self, value) 
    File "stackoverflow.py", line 13, in multitest 
    self.assertEqual(self.somevalue, n) 
AssertionError: <__main__.SomeValue object at 0xd9870> != 2 

====================================================================== 
FAIL: test_gt_1 (__main__.ExampleTestCase) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "stackoverflow.py", line 34, in test_function 
    function(self, value) 
    File "stackoverflow.py", line 17, in multitest_gt 
    self.assertTrue(c > "B", c) 
AssertionError: A 

====================================================================== 
FAIL: test_gt_2 (__main__.ExampleTestCase) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "stackoverflow.py", line 34, in test_function 
    function(self, value) 
    File "stackoverflow.py", line 17, in multitest_gt 
    self.assertTrue(c > "B", c) 
AssertionError: B 

---------------------------------------------------------------------- 
Ran 10 tests in 0.001s 

FAILED (failures=3) 

Bạn có thể thấy ngay một số vấn đề xảy ra với thế hệ mã. "Test_gt_1" xuất phát từ đâu? Tôi có thể đổi tên thành "test_multitest_gt_1" dài hơn nhưng sau đó thử nghiệm nào là 1? Tốt hơn ở đây sẽ bắt đầu từ _0 thay vì _1, và có lẽ trong trường hợp của bạn, bạn biết các giá trị có thể được sử dụng như một tên hàm Python.

Tôi không thích phương pháp này. Tôi đã làm việc trên các cơ sở mã mà các phương pháp thử tự động (trong một trường hợp sử dụng một metaclass) và thấy nó khó hiểu hơn rất nhiều so với các phương thức hữu ích. Khi một bài kiểm tra thất bại, thật khó để tìm ra nguồn gốc của trường hợp lỗi, và thật khó để dính vào mã gỡ lỗi để thăm dò lý do cho sự thất bại.

(Lỗi gỡ lỗi trong ví dụ tôi đã viết ở đây không phải là khó khăn như cách tiếp cận metaclass cụ thể mà tôi đã phải làm việc với.)

0

Viết một phương pháp thử nghiệm duy nhất thực hiện tất cả các bài kiểm tra của bạn và bắt tất cả các kết quả, viết tin nhắn chẩn đoán của riêng bạn để stderr, và không kiểm tra nếu có của phép thử phụ của nó thất bại:

def test_with_multiple_parameters(self): 
    failed = False 
    for k in sorted(self.test_parameters.keys()): 
     if not self.my_test(self.test_parameters[k]): 
      print >> sys.stderr, "Test {0} failed.".format(k) 
      failed = True 
    self.assertFalse(failed)    

Note tất nhiên tên của my_test() không thể bắt đầu bằng test.

1

Một cách tiếp cận hướng dữ liệu hơn có thể được rõ ràng hơn so với cái được sử dụng trong Andrew Dalke 's answer:

"""Parametrized unit test. 

Builds a single TestCase class which tests if its 
    `somevalue` method is equal to the numbers 1 through 4. 

This is accomplished by 
    creating a list (`cases`) 
    of dictionaries which contain test specifications 
    and then feeding the list to a function which creates a test case class. 

When run, the output shows that three of the four cases fail, 
    as expected: 

>>> import sys 
>>> from unittest import TextTestRunner 
>>> run_tests(TextTestRunner(stream=sys.stdout, verbosity=9)) 
... # doctest: +ELLIPSIS 
Test if self.somevalue equals 4 ... FAIL 
Test if self.somevalue equals 1 ... FAIL 
Test if self.somevalue equals 3 ... FAIL 
Test if self.somevalue equals 2 ... ok 
<BLANKLINE> 
====================================================================== 
FAIL: Test if self.somevalue equals 4 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    ... 
AssertionError: 2 != 4 
<BLANKLINE> 
====================================================================== 
FAIL: Test if self.somevalue equals 1 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    ... 
AssertionError: 2 != 1 
<BLANKLINE> 
====================================================================== 
FAIL: Test if self.somevalue equals 3 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    ... 
AssertionError: 2 != 3 
<BLANKLINE> 
---------------------------------------------------------------------- 
Ran 4 tests in ...s 
<BLANKLINE> 
FAILED (failures=3) 
""" 

from unittest import TestCase, TestSuite, defaultTestLoader 

cases = [{'name': "somevalue_equals_one", 
      'doc': "Test if self.somevalue equals 1", 
      'value': 1}, 
     {'name': "somevalue_equals_two", 
      'doc': "Test if self.somevalue equals 2", 
      'value': 2}, 
     {'name': "somevalue_equals_three", 
      'doc': "Test if self.somevalue equals 3", 
      'value': 3}, 
     {'name': "somevalue_equals_four", 
      'doc': "Test if self.somevalue equals 4", 
      'value': 4}] 

class BaseTestCase(TestCase): 
    def setUp(self): 
     self.somevalue = 2 

def test_n(self, n): 
    self.assertEqual(self.somevalue, n) 

def make_parametrized_testcase(class_name, base_classes, test_method, cases): 
    def make_parametrized_test_method(name, value, doc=None): 
     def method(self): 
      return test_method(self, value) 
     method.__name__ = "test_" + name 
     method.__doc__ = doc 
     return (method.__name__, method) 

    test_methods = (make_parametrized_test_method(**case) for case in cases) 
    class_dict = dict(test_methods) 
    return type(class_name, base_classes, class_dict) 


TestCase = make_parametrized_testcase('TestOneThroughFour', 
             (BaseTestCase,), 
             test_n, 
             cases) 

def make_test_suite(): 
    load = defaultTestLoader.loadTestsFromTestCase 
    return TestSuite(load(TestCase)) 

def run_tests(runner): 
    runner.run(make_test_suite()) 

if __name__ == '__main__': 
    from unittest import TextTestRunner 
    run_tests(TextTestRunner(verbosity=9)) 

Tôi không chắc chắn những gì voodoo có liên quan trong việc xác định thứ tự mà các bài kiểm tra được điều hành, nhưng doctest thường xuyên vượt qua tôi.

Đối với các tình huống phức tạp hơn, có thể thay thế thành phần values của cases từ điển với một bộ chứa danh sách các đối số và chỉ số của đối số từ khóa. Mặc dù vào thời điểm đó bạn về cơ bản mã hóa lisp trong python.

11

Một số trong những công cụ có sẵn để thực hiện các bài kiểm tra parametrized bằng Python là:

+0

[Các trường hợp kiểm tra tham số] (http://eli.thegreenplace.net/2011/08/02/python-unit-testing-parametrized-test-cases) bởi Eli Bendersky đã làm việc rất tốt cho tôi. –

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