Làm thế nào để bạn tạo các bài kiểm tra đơn vị động (tham số hóa) trong python?


234

Tôi có một số loại dữ liệu thử nghiệm và muốn tạo một thử nghiệm đơn vị cho từng mục. Ý tưởng đầu tiên của tôi là làm nó như thế này:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequence(unittest.TestCase):
    def testsample(self):
        for name, a,b in l:
            print "test", name
            self.assertEqual(a,b)

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

Nhược điểm của việc này là nó xử lý tất cả dữ liệu trong một thử nghiệm. Tôi muốn tạo một thử nghiệm cho mỗi mục khi đang bay. Bất kỳ đề xuất?



2
Một liên kết tốt có thể cung cấp câu trả lời: eli.thegreenplace.net/2014/04/02/ trên
gabious 29/07/2015

Câu trả lời:


173

Điều này được gọi là "tham số".

Có một số công cụ hỗ trợ phương pháp này. Ví dụ:

Mã kết quả trông như thế này:

from parameterized import parameterized

class TestSequence(unittest.TestCase):
    @parameterized.expand([
        ["foo", "a", "a",],
        ["bar", "a", "b"],
        ["lee", "b", "b"],
    ])
    def test_sequence(self, name, a, b):
        self.assertEqual(a,b)

Mà sẽ tạo ra các bài kiểm tra:

test_sequence_0_foo (__main__.TestSequence) ... ok
test_sequence_1_bar (__main__.TestSequence) ... FAIL
test_sequence_2_lee (__main__.TestSequence) ... ok

======================================================================
FAIL: test_sequence_1_bar (__main__.TestSequence)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/parameterized/parameterized.py", line 233, in <lambda>
    standalone_func = lambda *a: func(*(a + p.args), **p.kwargs)
  File "x.py", line 12, in test_sequence
    self.assertEqual(a,b)
AssertionError: 'a' != 'b'

Vì lý do lịch sử, tôi sẽ để lại câu trả lời ban đầu vào khoảng năm 2008):

Tôi sử dụng một cái gì đó như thế này:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequense(unittest.TestCase):
    pass

def test_generator(a, b):
    def test(self):
        self.assertEqual(a,b)
    return test

if __name__ == '__main__':
    for t in l:
        test_name = 'test_%s' % t[0]
        test = test_generator(t[1], t[2])
        setattr(TestSequense, test_name, test)
    unittest.main()

24
Trên thực tế, bignose, mã này KHÔNG tạo ra một tên khác nhau cho mỗi thử nghiệm (thực tế nó sẽ không hoạt động khác). Trong ví dụ đã cho, các bài kiểm tra được thực hiện sẽ có tên lần lượt là "test_foo", "test_bar" và "test_lee". Do đó, lợi ích bạn đề cập (và nó là một lợi ích lớn) được bảo tồn miễn là bạn tạo ra các tên hợp lý.
Toji

1
Như câu trả lời được đưa ra bởi các quốc gia @codeape, mũi xử lý việc này. Tuy nhiên, mũi dường như không xử lý Unicode; do đó đối với tôi đây là một giải pháp tốt hơn. +1
Keith Pinson

5
Vì vậy, lưu ý rằng câu trả lời thích hợp hơn được đưa ra trong câu hỏi trùng lặp : stackoverflow.com/a/2799009/322020 - bạn có thể sử dụng .__name__ =để bật .exact_methodthử nghiệm
Nakilon

7
Tại sao mã sửa đổi lớp xuất hiện trong if __name__ == '__main__'điều kiện? Chắc chắn nó sẽ đi ra ngoài này để chạy lúc nhập khẩu (ghi nhớ rằng các module python chỉ nhập khẩu một lần ngay cả khi nhập khẩu từ nhiều nơi khác nhau)
SpoonMeiser

4
Tôi không nghĩ rằng đây là một giải pháp tốt. Mã của một số ít nhất không nên phụ thuộc vào cách nó được gọi. TestCase nên được sử dụng trong mũi hoặc pytest hoặc môi trường thử nghiệm khác.
guettli

146

Sử dụng không đáng kể (kể từ 3,4)

Kể từ Python 3.4, unittestgói thư viện chuẩn có trình subTestquản lý bối cảnh.

Xem tài liệu:

Thí dụ:

from unittest import TestCase

param_list = [('a', 'a'), ('a', 'b'), ('b', 'b')]

class TestDemonstrateSubtest(TestCase):
    def test_works_as_expected(self):
        for p1, p2 in param_list:
            with self.subTest():
                self.assertEqual(p1, p2)

Bạn cũng có thể chỉ định một thông báo và giá trị tham số tùy chỉnh để subTest():

with self.subTest(msg="Checking if p1 equals p2", p1=p1, p2=p2):

Dùng mũi

Các mũi testing framework hỗ trợ này .

Ví dụ (mã bên dưới là toàn bộ nội dung của tệp chứa kiểm tra):

param_list = [('a', 'a'), ('a', 'b'), ('b', 'b')]

def test_generator():
    for params in param_list:
        yield check_em, params[0], params[1]

def check_em(a, b):
    assert a == b

Đầu ra của lệnh nosetests:

> nosetests -v
testgen.test_generator('a', 'a') ... ok
testgen.test_generator('a', 'b') ... FAIL
testgen.test_generator('b', 'b') ... ok

======================================================================
FAIL: testgen.test_generator('a', 'b')
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.5/site-packages/nose-0.10.1-py2.5.egg/nose/case.py", line 203, in runTest
    self.test(*self.arg)
  File "testgen.py", line 7, in check_em
    assert a == b
AssertionError

----------------------------------------------------------------------
Ran 3 tests in 0.006s

FAILED (failures=1)

3
Đó là một cách rất sạch để tự động tạo ra các trường hợp thử nghiệm.
gabious

Nhưng hãy lưu ý, 'setup ()' sẽ không biết biến nào đang được sử dụng làm đối số để mang lại. Trên thực tế, thiết lập () sẽ không biết thử nghiệm nào đang chạy hoặc các vars được đặt bên trong test_generator (). Điều này làm phức tạp việc kiểm tra độ tỉnh táo trong thiết lập () và đó là một trong những lý do mà một số người thích py.test.
Scott Prive

1
Nâng cấp cho phần cập nhật. Chính xác những gì tôi cần. :)
Saurabh Shrivastava

1
Có cách nào để chạy phiên bản nhỏ nhất với pytest, để nó chạy tất cả các trường hợp và không dừng lại ở tham số thất bại đầu tiên?
kakk11

1
Như được đề cập bởi @ kakk11, câu trả lời này (và subTest nói chung) không hoạt động với pytest. Đây là một vấn đề được biết đến. Có một plugin được phát triển tích cực để thực hiện công việc này: github.com/pytest-dev/pytest-subtests
Jérémie

76

Điều này có thể được giải quyết một cách tao nhã bằng cách sử dụng Metaclass:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequenceMeta(type):
    def __new__(mcs, name, bases, dict):

        def gen_test(a, b):
            def test(self):
                self.assertEqual(a, b)
            return test

        for tname, a, b in l:
            test_name = "test_%s" % tname
            dict[test_name] = gen_test(a,b)
        return type.__new__(mcs, name, bases, dict)

class TestSequence(unittest.TestCase):
    __metaclass__ = TestSequenceMeta

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

1
Điều này làm việc TUYỆT VỜI cho tôi với Selenium. Như một lưu ý, trong lớp TestSequence, bạn có thể định nghĩa các phương thức "tĩnh" như setUp (self), is_element_present (self, how, what), ... tornDown (self). Đặt chúng SAU KHI câu lệnh " metaclass = TestSequenceMeta" dường như hoạt động.
Tình yêu và hòa bình - Joe Codwell

5
Giải pháp này tốt hơn giải pháp được chọn là IMHO được chấp nhận.
petroslamb

2
@petroslamb __new__Phương thức trong siêu dữ liệu được gọi khi chính lớp được định nghĩa, không phải khi cá thể đầu tiên được tạo. Tôi sẽ tưởng tượng phương thức tạo phương thức kiểm tra động này tương thích hơn với phần hướng nội được sử dụng unittestđể xác định có bao nhiêu bài kiểm tra trong một lớp (tức là nó có thể biên dịch danh sách các bài kiểm tra trước khi nó tạo ra một thể hiện của lớp đó).
BillyBBone

11
Lưu ý: trong python 3, thay đổi này để:class TestSequence(unittest.TestCase, metaclass=TestSequenceMeta):[...]
Mathieu_Du

3
Bạn có thể vui lòng sử dụng dctthay vì dict? Sử dụng từ khóa làm tên biến là khó hiểu và dễ bị lỗi.
npfoss

49

Kể từ Python 3,4, các bài kiểm tra đã được giới thiệu là không đáng tin cậy cho mục đích này. Xem tài liệu để biết chi tiết. TestCase.subTest là một trình quản lý bối cảnh cho phép một người cô lập các xác nhận trong một thử nghiệm để một lỗi sẽ được báo cáo với thông tin tham số nhưng không dừng việc thực hiện kiểm tra. Đây là ví dụ từ tài liệu:

class NumbersTest(unittest.TestCase):

def test_even(self):
    """
    Test that numbers between 0 and 5 are all even.
    """
    for i in range(0, 6):
        with self.subTest(i=i):
            self.assertEqual(i % 2, 0)

Đầu ra của một lần chạy thử sẽ là:

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=3)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=5)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

Đây cũng là một phần của unittest2 , vì vậy nó có sẵn cho các phiên bản trước của Python.


1
Giải pháp tốt nhất nếu bạn sử dụng python 3,4 trở lên.
Max Malysh

4
Sử dụng unittest2, điều này cũng có sẵn cho Python 2.7.
Bernhard

11
Một điểm khác biệt chính giữa phương pháp này và có các thử nghiệm riêng biệt là trạng thái thử nghiệm không được đặt lại mỗi lần. (Đó là setUp()tearDown()không chạy giữa các bài kiểm tra phụ.)
Kevin Christopher Henry

1
@KevinChristopherHenry Có, nhưng self.setUp()về lý thuyết có thể được gọi thủ công từ bên trong phép trừ. Đối với tearDown, có nó được gọi tự động ở cuối có thể đủ.
Acumenus

Tôi nghĩ rằng điều này có thể mạnh mẽ khi được sử dụng cùng với phương pháp siêu dữ liệu ở trên.
Nathan Chappell

36

load_tests là một cơ chế ít được biết đến được giới thiệu trong 2.7 để tự động tạo TestSuite. Với nó, bạn có thể dễ dàng tạo các bài kiểm tra tham số.

Ví dụ:

import unittest

class GeneralTestCase(unittest.TestCase):
    def __init__(self, methodName, param1=None, param2=None):
        super(GeneralTestCase, self).__init__(methodName)

        self.param1 = param1
        self.param2 = param2

    def runTest(self):
        pass  # Test that depends on param 1 and 2.


def load_tests(loader, tests, pattern):
    test_cases = unittest.TestSuite()
    for p1, p2 in [(1, 2), (3, 4)]:
        test_cases.addTest(GeneralTestCase('runTest', p1, p2))
    return test_cases

Mã đó sẽ chạy tất cả các TestCase trong TestSuite được trả về bởi load_tests. Không có thử nghiệm khác được tự động chạy bởi cơ chế khám phá.

Ngoài ra, bạn cũng có thể sử dụng quyền thừa kế như được hiển thị trong vé này: http://bugs.python.org/msg151444


1
Đoạn mã trên không thành công: TypeError: __init __ () mất
tối đa

2
Đã thêm null mặc định cho các tham số phụ của hàm tạo.
Javier

Tôi thích mã tham số hóa mũi trong câu trả lời của @ mojo , nhưng đối với khách hàng của tôi, nó quá hữu ích để tránh phụ thuộc thêm nên tôi sẽ sử dụng mã này cho họ.
hiền triết

1
Giải pháp này là yêu thích của tôi trên trang này. Cả Mũi , được đề xuất trong câu trả lời hàng đầu hiện tại và ngã ba Mũi của nó chỉ là bảo trì và sau đó gợi ý người dùng thay vì thử pytest . Thật là một mớ hỗn độn - Tôi sẽ tuân theo cách tiếp cận bản địa như thế này!
Sean

1
phần thưởng: khả năng xác định lại phương pháp mô tả ngắn cho đầu ra được truyền trong params
fun_vit

33

Nó có thể được thực hiện bằng cách sử dụng pytest . Chỉ cần viết tệp test_me.pycó nội dung:

import pytest

@pytest.mark.parametrize('name, left, right', [['foo', 'a', 'a'],
                                               ['bar', 'a', 'b'],
                                               ['baz', 'b', 'b']])
def test_me(name, left, right):
    assert left == right, name

Và chạy thử nghiệm của bạn với lệnh py.test --tb=short test_me.py. Sau đó, đầu ra sẽ trông như sau:

=========================== test session starts ============================
platform darwin -- Python 2.7.6 -- py-1.4.23 -- pytest-2.6.1
collected 3 items

test_me.py .F.

================================= FAILURES =================================
_____________________________ test_me[bar-a-b] _____________________________
test_me.py:8: in test_me
    assert left == right, name
E   AssertionError: bar
==================== 1 failed, 2 passed in 0.01 seconds ====================

Thật đơn giản!. Cũng pytest có thêm nhiều tính năng như fixtures, mark, assert, vv ...


1
Tôi đang tìm kiếm một ví dụ đơn giản, đơn giản về cách làm thế nào để tham số các trường hợp thử nghiệm với py.test. Cảm ơn rât nhiều!
hồ bấm giờ

@timolt Tôi rất vui lòng giúp bạn. Kiểm tra thẻ py.test , để biết thêm ví dụ. Ngoài ra tôi đề nghị sử dụng hamcrest để thêm một ít đường vào khẳng định của bạn với những cái nạng có thể đọc được của con người, có thể được sửa đổi, kết hợp hoặc tạo theo cách riêng của bạn. Thêm vào đó, chúng ta có allure-python , một thế hệ báo cáo dễ nhìn chopy.test
Serge Voronezhskiy

Cảm ơn. Tôi mới bắt đầu chuyển từ unittestpy.test. Tôi đã từng có TestCasecác lớp cơ sở có khả năng tự động tạo ra các con với các đối số khác nhau mà chúng sẽ lưu trữ dưới dạng các biến lớp ... điều này hơi khó sử dụng.
hồ bấm giờ

1
@timride Yep bạn nói đúng. Hầu hết tính năng sát thủ của py.testyield_fixtures . Mà có thể làm thiết lập , quay trở lại một số dữ liệu hữu ích vào thử nghiệm và sau khi thử nghiệm kết thúc làm teardown . Lịch thi đấu cũng có thể được parametirized .
Serge Voronezhskiy

12

Sử dụng thư viện ddt . Nó thêm các trang trí đơn giản cho các phương pháp thử nghiệm:

import unittest
from ddt import ddt, data
from mycode import larger_than_two

@ddt
class FooTestCase(unittest.TestCase):

    @data(3, 4, 12, 23)
    def test_larger_than_two(self, value):
        self.assertTrue(larger_than_two(value))

    @data(1, -3, 2, 0)
    def test_not_larger_than_two(self, value):
        self.assertFalse(larger_than_two(value))

Thư viện này có thể được cài đặt với pip. Nó không yêu cầu nosevà hoạt động tuyệt vời với unittestmô-đun thư viện tiêu chuẩn .


6

Bạn sẽ được hưởng lợi từ việc thử thư viện TestScenario .

testscenario cung cấp tiêm phụ thuộc sạch cho các thử nghiệm kiểu python unittest. Điều này có thể được sử dụng để kiểm tra giao diện (kiểm tra nhiều triển khai thông qua một bộ kiểm tra duy nhất) hoặc để tiêm phụ thuộc cổ điển (cung cấp các kiểm tra với các phụ thuộc bên ngoài vào chính mã kiểm tra, cho phép kiểm tra dễ dàng trong các tình huống khác nhau).



4

Bạn có thể sử dụng plugin mũi-ittr ( pip install nose-ittr).

Rất dễ tích hợp với các thử nghiệm hiện tại, cần có những thay đổi tối thiểu (nếu có). Nó cũng hỗ trợ plugin đa xử lý mũi .

Không phải là bạn cũng có thể có một setupchức năng tùy chỉnh cho mỗi bài kiểm tra.

@ittr(number=[1, 2, 3, 4])   
def test_even(self):   
    assert_equal(self.number % 2, 0)

Cũng có thể truyền nosetestcác tham số như với plugin tích hợp của chúng attrib, theo cách này bạn chỉ có thể chạy thử nghiệm cụ thể với tham số cụ thể:

nosetest -a number=2

Tôi thích cách tiếp cận này, đặc biệt là mức độ phương pháp mà nó hỗ trợ.
Matt

3

Tôi sử dụng siêu dữ liệu và trang trí để tạo các bài kiểm tra. Bạn có thể kiểm tra python_wrap_case thực hiện của tôi . Thư viện này không yêu cầu bất kỳ khung kiểm tra.

Ví dụ của bạn:

import unittest
from python_wrap_cases import wrap_case


@wrap_case
class TestSequence(unittest.TestCase):

    @wrap_case("foo", "a", "a")
    @wrap_case("bar", "a", "b")
    @wrap_case("lee", "b", "b")
    def testsample(self, name, a, b):
        print "test", name
        self.assertEqual(a, b)

Bảng điều khiển đầu ra:

testsample_u'bar'_u'a'_u'b' (tests.example.test_stackoverflow.TestSequence) ... test bar
FAIL
testsample_u'foo'_u'a'_u'a' (tests.example.test_stackoverflow.TestSequence) ... test foo
ok
testsample_u'lee'_u'b'_u'b' (tests.example.test_stackoverflow.TestSequence) ... test lee
ok

Ngoài ra, bạn có thể sử dụng máy phát điện . Ví dụ: mã này tạo ra tất cả các kết hợp kiểm tra có thể có với các đối số a__listb__list

import unittest
from python_wrap_cases import wrap_case


@wrap_case
class TestSequence(unittest.TestCase):

    @wrap_case(a__list=["a", "b"], b__list=["a", "b"])
    def testsample(self, a, b):
        self.assertEqual(a, b)

Bảng điều khiển đầu ra:

testsample_a(u'a')_b(u'a') (tests.example.test_stackoverflow.TestSequence) ... ok
testsample_a(u'a')_b(u'b') (tests.example.test_stackoverflow.TestSequence) ... FAIL
testsample_a(u'b')_b(u'a') (tests.example.test_stackoverflow.TestSequence) ... FAIL
testsample_a(u'b')_b(u'b') (tests.example.test_stackoverflow.TestSequence) ... ok

2

Tôi đã tình cờ gặp ParamUnittest vào một ngày khác khi nhìn vào mã nguồn để radon ( ví dụ sử dụng trên repo github ). Nó nên hoạt động với các khung khác mở rộng TestCase (như Mũi).

Đây là một ví dụ:

import unittest
import paramunittest


@paramunittest.parametrized(
    ('1', '2'),
    #(4, 3),    <---- uncomment to have a failing test
    ('2', '3'),
    (('4', ), {'b': '5'}),
    ((), {'a': 5, 'b': 6}),
    {'a': 5, 'b': 6},
)
class TestBar(TestCase):
    def setParameters(self, a, b):
        self.a = a
        self.b = b

    def testLess(self):
        self.assertLess(self.a, self.b)

2
import unittest

def generator(test_class, a, b):
    def test(self):
        self.assertEqual(a, b)
    return test

def add_test_methods(test_class):
    #First element of list is variable "a", then variable "b", then name of test case that will be used as suffix.
    test_list = [[2,3, 'one'], [5,5, 'two'], [0,0, 'three']]
    for case in test_list:
        test = generator(test_class, case[0], case[1])
        setattr(test_class, "test_%s" % case[2], test)


class TestAuto(unittest.TestCase):
    def setUp(self):
        print 'Setup'
        pass

    def tearDown(self):
        print 'TearDown'
        pass

_add_test_methods(TestAuto)  # It's better to start with underscore so it is not detected as a test itself

if __name__ == '__main__':
    unittest.main(verbosity=1)

KẾT QUẢ:

>>> 
Setup
FTearDown
Setup
TearDown
.Setup
TearDown
.
======================================================================
FAIL: test_one (__main__.TestAuto)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/inchowar/Desktop/PyTrash/test_auto_3.py", line 5, in test
    self.assertEqual(a, b)
AssertionError: 2 != 3

----------------------------------------------------------------------
Ran 3 tests in 0.019s

FAILED (failures=1)

1
Vấn đề nhỏ với def add_test_methodschức năng của bạn . def _add_test_methods Tôi nên nghĩ
Raychaser

@Raychaser ... Bạn đã đúng..Tôi đã sửa nhưng không cập nhật ở đây .... Cảm ơn vì đã nắm bắt được điều đó.
Arindam Roychowdhury

1

Chỉ cần sử dụng siêu dữ liệu, như đã thấy ở đây;

class DocTestMeta(type):
    """
    Test functions are generated in metaclass due to the way some
    test loaders work. For example, setupClass() won't get called
    unless there are other existing test methods, and will also
    prevent unit test loader logic being called before the test
    methods have been defined.
    """
    def __init__(self, name, bases, attrs):
        super(DocTestMeta, self).__init__(name, bases, attrs)

    def __new__(cls, name, bases, attrs):
        def func(self):
            """Inner test method goes here"""
            self.assertTrue(1)

        func.__name__ = 'test_sample'
        attrs[func.__name__] = func
        return super(DocTestMeta, cls).__new__(cls, name, bases, attrs)

class ExampleTestCase(TestCase):
    """Our example test case, with no methods defined"""
    __metaclass__ = DocTestMeta

Đầu ra:

test_sample (ExampleTestCase) ... OK

1

Bạn có thể sử dụng TestSuitevà các TestCaselớp tùy chỉnh .

import unittest

class CustomTest(unittest.TestCase):
    def __init__(self, name, a, b):
        super().__init__()
        self.name = name
        self.a = a
        self.b = b

    def runTest(self):
        print("test", self.name)
        self.assertEqual(self.a, self.b)

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTest(CustomTest("Foo", 1337, 1337))
    suite.addTest(CustomTest("Bar", 0xDEAD, 0xC0DE))
    unittest.TextTestRunner().run(suite)

Trong khi TestSuite hoạt động, các đối số không được truyền xuống __init__chức năng.
jadelord

1

Tôi đã thấy rằng điều này hoạt động tốt cho mục đích của tôi, đặc biệt là nếu tôi cần tạo các thử nghiệm thực hiện các quy trình hơi khác nhau trên một bộ sưu tập dữ liệu.

import unittest

def rename(newName):
    def renamingFunc(func):
        func.__name__ == newName
        return func
    return renamingFunc

class TestGenerator(unittest.TestCase):

    TEST_DATA = {}

    @classmethod
    def generateTests(cls):
        for dataName, dataValue in TestGenerator.TEST_DATA:
            for func in cls.getTests(dataName, dataValue):
                setattr(cls, "test_{:s}_{:s}".format(func.__name__, dataName), func)

    @classmethod
    def getTests(cls):
        raise(NotImplementedError("This must be implemented"))

class TestCluster(TestGenerator):

    TEST_CASES = []

    @staticmethod
    def getTests(dataName, dataValue):

        def makeTest(case):

            @rename("{:s}".format(case["name"]))
            def test(self):
                # Do things with self, case, data
                pass

            return test

        return [makeTest(c) for c in TestCluster.TEST_CASES]

TestCluster.generateTests()

Các TestGeneratorlớp có thể được sử dụng để sinh ra các bộ trường hợp thử nghiệm khác nhau như thế nào TestCluster.

TestClustercó thể được coi là một thực hiện của TestGeneratorgiao diện.


1

Giải pháp này hoạt động với unittestnosecho Python 2 và Python 3:

#!/usr/bin/env python
import unittest

def make_function(description, a, b):
    def ghost(self):
        self.assertEqual(a, b, description)
    print(description)
    ghost.__name__ = 'test_{0}'.format(description)
    return ghost


class TestsContainer(unittest.TestCase):
    pass

testsmap = {
    'foo': [1, 1],
    'bar': [1, 2],
    'baz': [5, 5]}

def generator():
    for name, params in testsmap.iteritems():
        test_func = make_function(name, params[0], params[1])
        setattr(TestsContainer, 'test_{0}'.format(name), test_func)

generator()

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

Cảm ơn bạn @ guillaume-jacquenot cho phiên bản nâng cấp <3!
lau

0

Tôi đã gặp rắc rối với một phong cách kiểm tra tham số rất đặc biệt. Tất cả các bài kiểm tra Selen của chúng tôi có thể chạy cục bộ, nhưng chúng cũng có thể được chạy từ xa đối với một số nền tảng trên SauceLabs. Về cơ bản, tôi muốn lấy một lượng lớn các trường hợp thử nghiệm đã được viết và tham số hóa chúng với ít thay đổi nhất về mã có thể. Hơn nữa, tôi cần có khả năng truyền các tham số vào phương thức setUp, điều mà tôi chưa thấy giải pháp nào cho nơi khác.

Đây là những gì tôi nghĩ ra:

import inspect
import types

test_platforms = [
    {'browserName': "internet explorer", 'platform': "Windows 7", 'version': "10.0"},
    {'browserName': "internet explorer", 'platform': "Windows 7", 'version': "11.0"},
    {'browserName': "firefox", 'platform': "Linux", 'version': "43.0"},
]


def sauce_labs():
    def wrapper(cls):
        return test_on_platforms(cls)
    return wrapper


def test_on_platforms(base_class):
    for name, function in inspect.getmembers(base_class, inspect.isfunction):
        if name.startswith('test_'):
            for platform in test_platforms:
                new_name = '_'.join(list([name, ''.join(platform['browserName'].title().split()), platform['version']]))
                new_function = types.FunctionType(function.__code__, function.__globals__, new_name,
                                                  function.__defaults__, function.__closure__)
                setattr(new_function, 'platform', platform)
                setattr(base_class, new_name, new_function)
            delattr(base_class, name)

    return base_class

Với điều này, tất cả những gì tôi phải làm là thêm một trình trang trí đơn giản @sauce_labs () vào mỗi TestCase cũ thông thường, và bây giờ khi chạy chúng, chúng được gói và viết lại, để tất cả các phương thức kiểm tra được tham số hóa và đổi tên. LoginTests.test_login (self) chạy dưới dạng LoginTests.test_login_iNET_explorer_10.0 (self), LoginTests.test_login_iNET_explorer_11.0 (self) và LoginTests.test_login_firefox_43.0 (tự quyết định) nền tảng để chạy đua, ngay cả trong LoginTests.setUp, điều này rất quan trọng cho nhiệm vụ của tôi vì đó là nơi kết nối với SauceLabs được khởi tạo.

Dù sao, tôi hy vọng điều này có thể giúp ích cho ai đó muốn thực hiện tham số hóa "toàn cầu" tương tự trong các thử nghiệm của họ!


0

Các câu trả lời dựa trên siêu dữ liệu vẫn hoạt động trong Python3, nhưng thay vì __metaclass__thuộc tính người ta phải sử dụng metaclasstham số, như trong:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

0

Lập trình meta là thú vị, nhưng có thể đi trên đường. Hầu hết các giải pháp ở đây gây khó khăn cho:

  • chọn lọc khởi động một bài kiểm tra
  • quay lại mã đã cho

Vì vậy, đề xuất đầu tiên của tôi là đi theo đường dẫn đơn giản / rõ ràng (hoạt động với bất kỳ người chạy thử nào):

import unittest

class TestSequence(unittest.TestCase):

    def _test_complex_property(self, a, b):
        self.assertEqual(a,b)

    def test_foo(self):
        self._test_complex_property("a", "a")
    def test_bar(self):
        self._test_complex_property("a", "b")
    def test_lee(self):
        self._test_complex_property("b", "b")

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

Vì chúng ta không nên lặp lại chính mình, đề xuất thứ hai của tôi dựa trên câu trả lời của @ Javier: chấp nhận thử nghiệm dựa trên tài sản. Thư viện giả thuyết:

  • là "không ngừng quanh co về việc tạo ra trường hợp thử nghiệm hơn chúng ta chỉ là con người"
  • sẽ cung cấp các ví dụ đếm đơn giản
  • làm việc với bất kỳ người chạy thử
  • có nhiều tính năng thú vị hơn (thống kê, đầu ra thử nghiệm bổ sung, ...)

    lớp TestSequence (unittest.TestCase):

    @given(st.text(), st.text())
    def test_complex_property(self, a, b):
        self.assertEqual(a,b)

Để kiểm tra các ví dụ cụ thể của bạn, chỉ cần thêm:

    @example("a", "a")
    @example("a", "b")
    @example("b", "b")

Để chỉ chạy một ví dụ cụ thể, bạn có thể nhận xét các ví dụ khác (ví dụ được cung cấp sẽ được chạy trước). Bạn có thể muốn sử dụng @given(st.nothing()). Một lựa chọn khác là thay thế toàn bộ khối bằng cách:

    @given(st.just("a"), st.just("b"))

Ok, bạn không có tên thử nghiệm riêng biệt. Nhưng có lẽ bạn chỉ cần:

  • một tên mô tả của tài sản được thử nghiệm.
  • đầu vào nào dẫn đến thất bại (ví dụ giả mạo).

Ví dụ hài hước


0

Siêu muộn đến bữa tiệc, nhưng tôi gặp khó khăn khi làm những việc này cho setUpClass .

Đây là phiên bản câu trả lời của @ Javier cho phép setUpClasstruy cập vào các thuộc tính được phân bổ động.

import unittest


class GeneralTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print ''
        print cls.p1
        print cls.p2

    def runTest1(self):
        self.assertTrue((self.p2 - self.p1) == 1)

    def runTest2(self):
        self.assertFalse((self.p2 - self.p1) == 2)


def load_tests(loader, tests, pattern):
    test_cases = unittest.TestSuite()
    for p1, p2 in [(1, 2), (3, 4)]:
        clsname = 'TestCase_{}_{}'.format(p1, p2)
        dct = {
            'p1': p1,
            'p2': p2,
        }
        cls = type(clsname, (GeneralTestCase,), dct)
        test_cases.addTest(cls('runTest1'))
        test_cases.addTest(cls('runTest2'))
    return test_cases

Đầu ra

1
2
..
3
4
..
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

0

Chỉ cần ném một giải pháp khác trong hỗn hợp;)

Điều này có hiệu quả tương tự parameterizednhư đã đề cập ở trên, nhưng cụ thể đối với unittest:

def sub_test(param_list):
    """Decorates a test case to run it as a set of subtests."""

    def decorator(f):

        @functools.wraps(f)
        def wrapped(self):
            for param in param_list:
                with self.subTest(**param):
                    f(self, **param)

        return wrapped

    return decorator

Ví dụ sử dụng:

class TestStuff(unittest.TestCase):
    @sub_test([
        dict(arg1='a', arg2='b'),
        dict(arg1='x', arg2='y'),
    ])
    def test_stuff(self, a, b):
        ...

-1

Bên cạnh việc sử dụng setattr, chúng ta có thể sử dụng load_tests kể từ python 3.2. Vui lòng tham khảo bài đăng blog blog.livreuro.com/en/coding/python/how-to-generate-discoverable-unit-tests-in-python-dynamical/

class Test(unittest.TestCase):
    pass

def _test(self, file_name):
    open(file_name, 'r') as f:
        self.assertEqual('test result',f.read())

def _generate_test(file_name):
    def test(self):
        _test(self, file_name)
    return test

def _generate_tests():
    for file in files:
        file_name = os.path.splitext(os.path.basename(file))[0]
        setattr(Test, 'test_%s' % file_name, _generate_test(file))

test_cases = (Test,)

def load_tests(loader, tests, pattern):
    _generate_tests()
    suite = TestSuite()
    for test_class in test_cases:
        tests = loader.loadTestsFromTestCase(test_class)
        suite.addTests(tests)
    return suite

if __name__ == '__main__':
    _generate_tests()
    unittest.main()

-1

Sau đây là giải pháp của tôi. Tôi thấy điều này hữu ích khi: 1. Nên hoạt động cho unittest.Testcase và unittest Discover 2. Có một bộ thử nghiệm được chạy cho các cài đặt tham số khác nhau. 3. Rất đơn giản, không phụ thuộc vào các gói nhập khác

    class BaseClass(unittest.TestCase):
        def setUp(self):
            self.param = 2
            self.base = 2

        def test_me(self):
            self.assertGreaterEqual(5, self.param+self.base)

        def test_me_too(self):
            self.assertLessEqual(3, self.param+self.base)



     class Child_One(BaseClass):
        def setUp(self):
            BaseClass.setUp(self)
            self.param = 4


     class Child_Two(BaseClass):
        def setUp(self):
            BaseClass.setUp(self)
            self.param = 1

Điều này không trả lời câu hỏi, đó là về việc tạo ra các bài kiểm tra một cách nhanh chóng.
lenz

-1
import unittest

def generator(test_class, a, b,c,d,name):
    def test(self):
        print('Testexecution=',name)
        print('a=',a)
        print('b=',b)
        print('c=',c)
        print('d=',d)

    return test

def add_test_methods(test_class):
    test_list = [[3,3,5,6, 'one'], [5,5,8,9, 'two'], [0,0,5,6, 'three'],[0,0,2,3,'Four']]
    for case in test_list:
        print('case=',case[0], case[1],case[2],case[3],case[4])
        test = generator(test_class, case[0], case[1],case[2],case[3],case[4])
        setattr(test_class, "test_%s" % case[4], test)


class TestAuto(unittest.TestCase):
    def setUp(self):
        print ('Setup')
        pass

    def tearDown(self):
        print ('TearDown')
        pass

add_test_methods(TestAuto)

if __name__ == '__main__':
    unittest.main(verbosity=1)

Có vẻ như bạn bị mất định dạng ở đó. thật sự rất khó để đọc khi nó đứng
Arturo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.