คุณจะสร้างการทดสอบหน่วยแบบไดนามิก (พารามิเตอร์) ในงูใหญ่ได้อย่างไร?


234

ฉันมีข้อมูลการทดสอบบางอย่างและต้องการสร้างการทดสอบหน่วยสำหรับแต่ละรายการ ความคิดแรกของฉันคือทำสิ่งนี้:

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()

ข้อเสียของมันคือมันจัดการข้อมูลทั้งหมดในการทดสอบเดียว ฉันต้องการสร้างการทดสอบหนึ่งรายการสำหรับแต่ละรายการในทันที ข้อเสนอแนะใด ๆ



2
ลิงค์ที่ดีที่อาจให้คำตอบ: eli.thegreenplace.net/2014/04/02/..
gaborous

คำตอบ:


173

สิ่งนี้เรียกว่า "parametrization"

มีเครื่องมือหลายอย่างที่สนับสนุนวิธีนี้ เช่น:

โค้ดผลลัพธ์มีลักษณะดังนี้:

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)

ซึ่งจะสร้างการทดสอบ:

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'

ด้วยเหตุผลทางประวัติศาสตร์ฉันจะทิ้งคำตอบเดิมไว้ที่ 2008):

ฉันใช้สิ่งนี้:

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
ที่จริงแล้วรหัสนี้จะสร้างชื่อที่แตกต่างกันสำหรับการทดสอบแต่ละครั้ง (จริง ๆ แล้วมันจะไม่ทำงานอย่างอื่น) ในตัวอย่างที่กำหนดการทดสอบที่ดำเนินการจะมีชื่อว่า "test_foo", "test_bar" และ "test_lee" ตามลำดับ ดังนั้นผลประโยชน์ที่คุณพูดถึง (และเป็นชื่อที่ยิ่งใหญ่) จะถูกเก็บรักษาไว้ตราบใดที่คุณสร้างชื่อที่สมเหตุสมผล
Toji

1
ตามคำตอบที่ระบุโดย @ceapeape รัฐจมูกจัดการสิ่งนี้ อย่างไรก็ตามดูเหมือนว่าจมูกจะไม่รองรับ Unicode ดังนั้นสำหรับฉันนี้เป็นทางออกที่ดีกว่า +1
Keith Pinson

5
ดังนั้นโปรดทราบว่าจะได้รับคำตอบที่ถูกต้องมากกว่าในคำถามซ้ำ : stackoverflow.com/a/2799009/322020 - คุณใช้.__name__ =เพื่อเปิดใช้.exact_methodการทดสอบ
Nakilon

7
ทำไมรหัสที่แก้ไขคลาสปรากฏในif __name__ == '__main__'เงื่อนไข แน่นอนมันควรจะออกไปข้างนอกนี้ในการทำงานในเวลานำเข้า (จำได้ว่าโมดูลหลามจะถูกนำเข้าเพียงครั้งเดียวแม้ว่าที่นำเข้ามาจากสถานที่ที่แตกต่างกัน)
SpoonMeiser

4
ฉันไม่คิดว่านี่เป็นทางออกที่ดี รหัสของ unittest ไม่ควรขึ้นอยู่กับวิธีการเรียกใช้ TestCase ควรใช้ในจมูกหรือ pytest หรือสภาพแวดล้อมการทดสอบที่แตกต่างกัน
guettli

146

ใช้ unittest (ตั้งแต่ 3.4)

ตั้งแต่ Python 3.4 unittestแพ็คเกจไลบรารีมาตรฐานมีตัวsubTestจัดการบริบท

ดูเอกสารประกอบ:

ตัวอย่าง:

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)

คุณยังสามารถระบุข้อความที่กำหนดเองและค่าพารามิเตอร์เป็นsubTest():

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

การใช้จมูก

จมูกกรอบการทดสอบนี้สนับสนุน

ตัวอย่าง (รหัสด้านล่างคือเนื้อหาทั้งหมดของไฟล์ที่มีการทดสอบ):

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

เอาต์พุตของคำสั่ง 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
นั่นเป็นวิธีที่สะอาดมากในการสร้างกรณีทดสอบแบบไดนามิก
gaborous

แต่ระวัง 'setup ()' จะไม่ทราบว่าตัวแปรใดที่ถูกใช้เป็นอาร์กิวเมนต์เพื่อให้ได้ผลลัพธ์ การตั้งค่าจริง () จะไม่ทราบว่ากำลังทดสอบอะไรอยู่หรือตั้งค่า vars ไว้ใน test_generator () วิธีนี้จะทำให้การตรวจสติมีความซับซ้อนภายในการตั้งค่า () และเป็นหนึ่งในเหตุผลที่บางคนชอบ py.test
Scott Prive

1
โหวตขึ้นสำหรับส่วนการปรับปรุง สิ่งที่ฉันต้องการ :)
Saurabh Shrivastava

1
มีวิธีรันเวอร์ชัน unittest ด้วย pytest หรือไม่เพื่อให้สามารถรันเคสทั้งหมดและไม่หยุดบนพารามิเตอร์ที่ล้มเหลวตัวแรก?
kakk11

1
ดังที่ @ kakk11 คำตอบนี้ (และการทดสอบย่อยโดยทั่วไป) ไม่ทำงานกับ pytest นี่เป็นปัญหาที่ทราบกันดี มีปลั๊กอินที่พัฒนาขึ้นอย่างแข็งขันเพื่อใช้งานนี้: github.com/pytest-dev/pytest-subtests
Jérémie

76

สิ่งนี้สามารถแก้ไขได้อย่างสง่างามโดยใช้ Metaclasses:

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
สิ่งนี้ทำงานได้ดีมากสำหรับฉันกับซีลีเนียม ในฐานะที่เป็นโน้ตในคลาส TestSequence คุณสามารถกำหนดวิธีการ "คงที่" เช่น setUp (ตัวเอง), is_element_present (ตัวเองอย่างไรสิ่งที่), ... tearDown (ตัวเอง) ทำให้พวกเขาหลังจากคำสั่ง " metaclass = TestSequenceMeta" ดูเหมือนว่าจะทำงาน
ความรักและความสงบสุข - Joe Codeswell

5
โซลูชันนี้ดีกว่าโซลูชันที่เลือกไว้อย่าง IMHO
petroslamb

2
@petroslamb __new__เมธอดใน metaclass ได้รับการเรียกเมื่อมีการกำหนดคลาสเองไม่ใช่เมื่อมีการสร้างอินสแตนซ์แรก ฉันคิดว่าวิธีการสร้างวิธีทดสอบแบบไดนามิกนี้จะเข้ากันได้กับวิปัสสนาที่ใช้โดยunittestเพื่อกำหนดจำนวนการทดสอบในชั้นเรียน (เช่นมันอาจรวบรวมรายชื่อของการทดสอบก่อนที่มันจะสร้างตัวอย่างของคลาสนั้น)
BillyBBone

11
หมายเหตุ: ใน python 3 เปลี่ยนสิ่งนี้เป็น:class TestSequence(unittest.TestCase, metaclass=TestSequenceMeta):[...]
Mathieu_Du

3
คุณกรุณาใช้dctแทนได้dictมั้ย การใช้คำหลักเป็นชื่อตัวแปรทำให้เกิดความสับสนและเกิดข้อผิดพลาดได้ง่าย
npfoss

49

ในการทดสอบย่อย Python 3.4 ได้รับการแนะนำให้รู้จักกับจุดประสงค์นี้ ดูเอกสารประกอบสำหรับรายละเอียด TestCase.subTest เป็นตัวจัดการบริบทซึ่งอนุญาตให้หนึ่งแยก asserts ในการทดสอบเพื่อที่จะรายงานความล้มเหลวด้วยข้อมูลพารามิเตอร์ แต่ไม่หยุดการดำเนินการทดสอบ นี่คือตัวอย่างจากเอกสาร:

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)

ผลลัพธ์ของการทดสอบการทำงานจะเป็น:

======================================================================
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

นี่เป็นส่วนหนึ่งของunittest2ดังนั้นจึงมีให้สำหรับ Python รุ่นก่อนหน้า


1
ทางออกที่ดีที่สุดถ้าคุณใช้ python 3.4 ขึ้นไป
Max Malysh

4
การใช้ unittest2 สามารถใช้กับ Python 2.7 ได้เช่นกัน
แบร์นฮาร์ด

11
ความแตกต่างที่สำคัญอย่างหนึ่งระหว่างวิธีการนี้กับการทดสอบแยกกันคือสถานะการทดสอบจะไม่ถูกรีเซ็ตในแต่ละครั้ง (นั่นคือsetUp()และtearDown()ไม่ได้ทำงานระหว่างการทดสอบย่อย)
Kevin Christopher Henry

1
@KevinChristopherHenry ใช่ แต่self.setUp()ในทางทฤษฎีสามารถเรียกได้ด้วยตนเองจากภายในการทดสอบย่อย สำหรับtearDownการมีมันเรียกโดยอัตโนมัติในตอนท้ายอาจพอเพียง
คิวเมนตัส

ฉันคิดว่านี่อาจมีประสิทธิภาพเมื่อใช้ร่วมกับวิธี metaclass ด้านบน
Nathan Chappell

36

load_testsเป็นกลไกที่รู้จักกันน้อยใน 2.7 เพื่อสร้าง TestSuite แบบไดนามิก ด้วยคุณสามารถสร้างการทดสอบแบบพาราเมตริก

ตัวอย่างเช่น:

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

รหัสนั้นจะเรียกใช้ TestCases ทั้งหมดใน TestSuite ที่ส่งคืนโดย load_tests ไม่มีการทดสอบอื่นใดที่ดำเนินการโดยกลไกการค้นพบโดยอัตโนมัติ

หรือคุณสามารถใช้การสืบทอดตามที่แสดงในตั๋วนี้: http://bugs.python.org/msg151444


1
โค้ดด้านบนล้มเหลว: TypeError: __init __ () รับได้สูงสุด 2 ข้อ (กำหนด 4 ข้อ)
สูงสุด

2
เพิ่มค่าเริ่มต้นเป็นโมฆะในพารามิเตอร์เสริมของตัวสร้าง
Javier

ฉันชอบรหัสจมูก - พารามิเตอร์ในคำตอบของ @ mojoแต่สำหรับลูกค้าของฉันมันมีประโยชน์เกินไปที่จะหลีกเลี่ยงการพึ่งพาพิเศษดังนั้นฉันจะใช้สิ่งนี้กับพวกเขา
ปราชญ์

1
วิธีนี้เป็นวิธีที่ฉันโปรดปรานในหน้านี้ ทั้งจมูก , ข้อเสนอแนะในคำตอบที่ด้านบนในปัจจุบันและส้อมของNose2มีการบำรุงรักษาเท่านั้นและหลังแสดงให้เห็นผู้ใช้แทนที่จะพยายามpytest ช่างเป็นเรื่องยุ่งเหยิง - ข้าจะยึดติดกับแนวทางแบบนี้!
Sean

1
โบนัส: ความสามารถในการกำหนดวิธีการคำอธิบาย shortDescription ใหม่สำหรับเอาต์พุตที่ส่งผ่านใน params
fun_vit

33

ก็สามารถทำได้โดยการใช้pytest เพียงเขียนไฟล์ที่test_me.pyมีเนื้อหา:

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

py.test --tb=short test_me.pyและเรียกใช้การทดสอบของคุณด้วยคำสั่ง จากนั้นผลลัพธ์จะเป็นดังนี้:

=========================== 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 ====================

มันง่าย! นอกจากนี้ยังpytestมีคุณสมบัติมากขึ้นเช่นfixtures, mark, assertฯลฯ ...


1
ฉันกำลังมองหาตัวอย่างง่ายๆที่เรียบง่ายตรงไปตรงมาถึงวิธีการทดสอบความผิดพลาดด้วย py.test ขอบคุณมาก!
timgeb

@timgeb ฉันยินดีที่จะช่วยคุณ ตรวจสอบแท็กpy.testเพื่อดูตัวอย่างเพิ่มเติม นอกจากนี้ฉันขอแนะนำให้ใช้hamcrestเพื่อเพิ่มน้ำตาลลงใน asserts ของคุณด้วย mutchers ที่มนุษย์สามารถอ่านได้ซึ่งสามารถแก้ไขรวมหรือสร้างโดยวิธีของคุณเอง นอกจากนี้เรายังมีเสน่ห์ - หลามการสร้างรายงานที่ดูดีสำหรับpy.test
Sergey Voronezhskiy

ขอบคุณ ฉันเพิ่งเริ่มย้ายจากunittestไปยัง py.test ฉันเคยมีTestCaseคลาสพื้นฐานที่สามารถสร้างเด็กที่มีข้อโต้แย้งต่าง ๆ ซึ่งพวกเขาจะเก็บไว้เป็นตัวแปรคลาส ... ซึ่งค่อนข้างไม่แน่นอน
timgeb

1
@timgeb ใช่คุณพูดถูก ส่วนใหญ่คุณสมบัตินักฆ่าของpy.testเป็นyield_fixtures ซึ่งสามารถทำติดตั้งกลับข้อมูลที่เป็นประโยชน์บางอย่างในการทดสอบและหลังจากการทดสอบจะสิ้นสุดลงทำให้teardown การติดตั้งสามารถทำให้เป็นพารามิเตอร์ได้
Sergey Voronezhskiy

12

ใช้ไลบรารีddt มันเพิ่มการตกแต่งที่เรียบง่ายสำหรับวิธีการทดสอบ:

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))

ไลบรารีนี้สามารถติดตั้งpipได้ ไม่ต้องการnoseและทำงานได้ดีกับunittestโมดูลไลบรารีมาตรฐาน


6

คุณจะได้ประโยชน์จากการลองใช้ห้องสมุดTestScenarios

testscenarios ให้การฉีดขึ้นต่อกันที่สะอาดสำหรับการทดสอบแบบ pitt unittest สิ่งนี้สามารถใช้สำหรับการทดสอบส่วนต่อประสาน (การทดสอบการใช้งานหลายอย่างผ่านชุดทดสอบเดียว) หรือสำหรับการฉีดแบบพึ่งพาอาศัยแบบคลาสสิก


5

นอกจากนี้ยังมีสมมุติฐานซึ่งเพิ่มการทดสอบฟัซซี่หรือคุณสมบัติตาม: https://pypi.python.org/pypi/hypothesis

นี่เป็นวิธีทดสอบที่ทรงพลังมาก


ฉันไม่สามารถใช้@given()มาโครในชั้นเรียนที่ไม่ค่อยได้
John Greene

1
ลองดูนี่: hypothesis.readthedocs.io/en/master/ …
Javier

4

คุณสามารถใช้ปลั๊กอินเสริมจมูก ( pip install nose-ittr)

ง่ายมากที่จะรวมเข้ากับการทดสอบที่มีอยู่จำเป็นต้องมีการเปลี่ยนแปลงเล็กน้อย (ถ้ามี) นอกจากนี้ยังสนับสนุนจมูกปลั๊กอิน multiprocessing

ไม่ใช่ว่าคุณสามารถมีsetupฟังก์ชั่นปรับแต่งต่อการทดสอบ

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

นอกจากนี้ยังเป็นไปได้ที่จะส่งnosetestพารามิเตอร์เช่นเดียวกับปลั๊กอินattribในตัวด้วยวิธีนี้คุณสามารถเรียกใช้การทดสอบเฉพาะด้วยพารามิเตอร์ที่เฉพาะเจาะจง:

nosetest -a number=2

ฉันชอบวิธีนี้โดยเฉพาะระดับต่อวิธีที่รองรับ
Matt

3

ฉันใช้ metaclasses และเครื่องมือตกแต่งเพื่อสร้างการทดสอบ คุณสามารถตรวจสอบการใช้งานของฉันpython_wrap_cases python_wrap_casesไลบรารีนี้ไม่ต้องการกรอบการทดสอบใด ๆ

ตัวอย่างของคุณ:

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)

เอาต์พุตคอนโซล:

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

นอกจากนี้คุณอาจจะใช้เครื่องกำเนิดไฟฟ้า ตัวอย่างเช่นรหัสนี้สร้างชุดการทดสอบที่เป็นไปได้ทั้งหมดพร้อมอาร์กิวเมนต์a__listและb__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)

เอาต์พุตคอนโซล:

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

ฉันเจอParamUnittestเมื่อวันก่อนเมื่อมองดูซอร์สโค้ดให้เรดอน ( ตัวอย่างการใช้งานบน repo github ) ควรทำงานกับกรอบงานอื่น ๆ ที่ขยาย TestCase (เช่น Nose)

นี่คือตัวอย่าง:

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)

ผลลัพธ์:

>>> 
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
ปัญหาเล็กน้อยกับการdef add_test_methodsทำงานของคุณ ฉันควรจะdef _add_test_methods คิดอย่างนั้น
ไหม

@ Raychaser ... คุณถูกต้อง .. ฉันแก้ไขแล้ว แต่ไม่ได้อัปเดตที่นี่ .... ขอบคุณที่รับทราบ
Arindam Roychowdhury

1

เพียงใช้ metaclasses ดังที่นี่

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

เอาท์พุท:

test_sample (ExampleTestCase) ... OK

1

คุณสามารถใช้TestSuiteและTestCaseคลาสที่กำหนดเองได้

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)

ขณะที่ TestSuite ใช้งานได้อาร์กิวเมนต์จะไม่ถูกส่งผ่านไปยัง__init__ฟังก์ชัน
jadelord

1

ฉันพบว่าสิ่งนี้ทำงานได้ดีสำหรับวัตถุประสงค์ของฉันโดยเฉพาะอย่างยิ่งถ้าฉันต้องการสร้างการทดสอบที่ทำกระบวนการต่างกันเล็กน้อยในการรวบรวมข้อมูล

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()

TestGeneratorระดับสามารถใช้ในการวางไข่ชุดที่แตกต่างของกรณีทดสอบเช่นTestClusterระดับสามารถใช้ในการวางไข่ชุดที่แตกต่างของกรณีทดสอบเช่น

TestClusterสามารถนำมาใช้เป็นTestGeneratorอินเตอร์เฟสได้


1

วิธีนี้ใช้ได้กับunittestและnoseสำหรับ Python 2 และ 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()

ขอบคุณ @ guillaume-jacquenot สำหรับรุ่นอัพเกรด <3!
ซับ

0

ฉันมีปัญหากับการทดสอบแบบกำหนดพารามิเตอร์ การทดสอบซีลีเนียมทั้งหมดของเราสามารถเรียกใช้ในพื้นที่ได้ แต่พวกเขาก็ควรจะสามารถเรียกใช้จากระยะไกลกับแพลตฟอร์มที่หลากหลายบน SauceLabs โดยทั่วไปฉันต้องการที่จะใช้จำนวนมากกรณีทดสอบที่เขียนแล้วและพารามิเตอร์พวกเขากับการเปลี่ยนแปลงน้อยที่สุดที่เป็นไปได้รหัส นอกจากนี้ฉันต้องสามารถส่งพารามิเตอร์ไปยังวิธีการ setUp ซึ่งเป็นสิ่งที่ฉันไม่ได้เห็นวิธีแก้ปัญหาสำหรับที่อื่น

นี่คือสิ่งที่ฉันเกิดขึ้นกับ:

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

ด้วยสิ่งนี้สิ่งที่ฉันต้องทำก็คือเพิ่ม decorator แบบง่าย @sauce_labs () ลงใน TestCase เก่า ๆ แต่ละเครื่องและตอนนี้เมื่อเรียกใช้มันจะถูกห่อและเขียนใหม่เพื่อให้วิธีการทดสอบทั้งหมดเปลี่ยนพารามิเตอร์และเปลี่ยนชื่อ LoginTests.test_login (ตัวเอง) ทำงานเป็น LoginTests.test_login_internet_explorer_10.0 (ตัวเอง), ตัวเลือก LoginTests.test_login_internet_explorer_11.0 (ตัวเอง) และแต่ละตัวมีพารามิเตอร์ แพลตฟอร์มที่จะเรียกใช้แม้กระทั่งใน LoginTests.setUp ซึ่งเป็นสิ่งสำคัญสำหรับงานของฉันเนื่องจากเป็นจุดเริ่มต้นของการเชื่อมต่อกับ SauceLabs

อย่างไรก็ตามฉันหวังว่านี่อาจเป็นประโยชน์กับคนที่กำลังมองหาการกำหนดพารามิเตอร์แบบ "ทั่วโลก" ที่คล้ายกันของการทดสอบของพวกเขา!


0

คำตอบที่ใช้ metaclass ยังคงใช้งานได้ใน Python3 แต่แทนที่จะใช้แอ__metaclass__ททริบิวหนึ่งจะต้องใช้metaclassพารามิเตอร์ดังเช่นใน:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

0

การเขียนโปรแกรม Meta นั้นสนุก แต่ก็สามารถไปได้ โซลูชันส่วนใหญ่ที่นี่ทำให้ยากต่อการ:

  • เลือกเปิดตัวการทดสอบ
  • ชี้ไปที่รหัสที่กำหนดชื่อการทดสอบ

ดังนั้นคำแนะนำแรกของฉันคือการปฏิบัติตามเส้นทางที่เรียบง่าย / ชัดเจน (ทำงานร่วมกับนักวิ่งทดสอบใด ๆ ):

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()

เนื่องจากเราไม่ควรทำซ้ำตัวเองคำแนะนำที่สองของฉันสร้างขึ้นบนคำตอบของ @ Javier: ยอมรับการทดสอบตามคุณสมบัติ ห้องสมุดสมมติฐาน:

  • เป็น "คดเคี้ยวอย่างไม่ลดละมากขึ้นเกี่ยวกับการสร้างกรณีทดสอบมากกว่ามนุษย์เท่านั้น"
  • จะให้ตัวอย่างนับอย่างง่าย
  • ทำงานร่วมกับนักวิ่งทดสอบใด ๆ
  • มีคุณสมบัติที่น่าสนใจมากมาย (สถิติ, ผลการทดสอบเพิ่มเติม, ... )

    คลาส TestSequence (unittest.TestCase):

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

หากต้องการทดสอบตัวอย่างเฉพาะของคุณเพียงเพิ่ม:

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

หากต้องการเรียกใช้ตัวอย่างเฉพาะเพียงตัวอย่างเดียวคุณสามารถแสดงความคิดเห็นตัวอย่างอื่น (ตัวอย่างที่ให้มาจะถูกเรียกใช้ก่อน) @given(st.nothing())คุณอาจต้องการที่จะใช้ ตัวเลือกอื่นคือการแทนที่ทั้งบล็อกโดย:

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

ตกลงคุณไม่มีชื่อทดสอบที่แตกต่างกัน แต่บางทีคุณแค่ต้องการ:

  • ชื่อที่เป็นคำอธิบายของคุณสมบัติภายใต้การทดสอบ
  • อินพุตใดนำไปสู่ความล้มเหลว (ตัวอย่างการปลอมแปลง)

ตัวอย่างที่สนุกกว่า


0

สุดสายไปงานเลี้ยง แต่ฉันมีปัญหาในการทำงานเหล่านี้เพื่อ setUpClassแต่ผมมีปัญหาในการทำงานเหล่านี้

ต่อไปนี้เป็นเวอร์ชันของคำตอบของ@ Javierที่ให้setUpClassการเข้าถึงแอตทริบิวต์ที่จัดสรรแบบไดนามิก

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

เอาท์พุท

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

OK

0

เพียงโยนโซลูชันอื่นลงในส่วนผสม;)

สิ่งนี้มีประสิทธิภาพเช่นเดียวกับparameterizedที่กล่าวข้างต้น แต่เฉพาะกับ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

ตัวอย่างการใช้งาน:

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

-1

นอกจากการใช้ setattr เราสามารถใช้ load_tests ตั้งแต่ python 3.2 โปรดดูที่บล็อกโพสต์blog.livreuro.com/en/coding/python/how-to-generate-discoverable-unit-tests-in-python-dynamically/

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

ต่อไปนี้เป็นวิธีแก้ปัญหาของฉัน ฉันพบว่ามีประโยชน์เมื่อ: 1. ควรใช้กับ unittest.Testcase และ unittest Discover 2. มีชุดทดสอบที่จะเรียกใช้สำหรับการตั้งค่าพารามิเตอร์ที่แตกต่างกัน 3. ง่ายมากไม่ขึ้นกับแพ็คเกจอื่นที่นำเข้าไม่ได้

    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

นี่ไม่ได้ตอบคำถามซึ่งเกี่ยวกับการสร้างการทดสอบได้ทันที
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)

ดูเหมือนว่าคุณจะสูญเสียการจัดรูปแบบที่นั่น มันยากที่จะอ่านอย่างที่มันยืน
Arturo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.