كيف يمكنك توليد ديناميكية (معلمات) وحدة الاختبارات في الثعبان ؟

StackOverflow https://stackoverflow.com/questions/32899

سؤال

لدي نوع من بيانات الاختبار و ترغب في إنشاء وحدة اختبار كل بند.كانت فكرتي الأولى أن تفعل ذلك من هذا القبيل:

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

الجانب السلبي من هذا هو أنه يتعامل مع جميع البيانات في اختبار واحد.أود أن تولد اختبار واحد لكل بند على الطاير.أي اقتراحات ؟

هل كانت مفيدة؟

المحلول

يمكنني استخدام شيء من هذا القبيل:

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

على parameterized الحزمة يمكن استخدامها لأتمتة هذه العملية:

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'

نصائح أخرى

باستخدام unittest (منذ 3.4)

منذ بايثون 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)

هذا يمكن حلها بشكل أنيق باستخدام 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()

كما من بايثون 3.4 الفحوصات الفرعية قد أدخلت إلى unittest لهذا الغرض.انظر الوثائق للحصول على التفاصيل.TestCase.سوبتيست هو السياق المدير الذي يسمح عزل يؤكد في الاختبار حيث أن الفشل سيكون ذكرت مع المعلمة المعلومات ولكن لا يوقف تنفيذ اختبار.هنا مثال من الوثائق:

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, لذا فمن متاحة للإصدارات السابقة من بيثون.

load_tests هو قليلا المعروفة آلية قدم في 2.7 بشكل حيوي إنشاء TestSuite.مع أنه يمكنك بسهولة إنشاء parametrized الاختبارات.

على سبيل المثال:

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

ويمكن أن يتم ذلك باستخدام 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, الخ ...

استخدام دي. دي. تي المكتبة.وتضيف بسيطة الديكور على طرق الاختبار:

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 وحدة نمطية.

يمكنك أن تستفيد من محاولة TestScenarios المكتبة.

testscenarios يوفر نظيفة حقن التبعية بايثون unittest أسلوب الاختبارات.هذا يمكن أن تستخدم واجهة اختبار (اختبار العديد من التطبيقات عبر اختبار واحد جناح) أو الكلاسيكية حقن التبعية (تقديم اختبارات مع تبعيات خارجيا إلى اختبار القانون نفسه ، مما يتيح سهولة الاختبار في حالات مختلفة).

هناك أيضا فرضية الذي يضيف زغب أو الممتلكات على أساس الاختبار: https://pypi.python.org/pypi/hypothesis

هذا هو قوي جدا طريقة الاختبار.

يمكنك استخدام الأنف-ittr المساعد (pip install nose-ittr).

فإنه من السهل جدا على الاندماج مع الاختبارات الحالية, الحد الأدنى من التغييرات (إن وجدت) المطلوبة.كما أنها تدعم الأنف المعالجة المتعددة المساعد.

لا يمكنك أيضا تخصيص setup وظيفة لكل اختبار.

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

فمن الممكن أيضا أن تمر nosetest المعلمات مع بناء في البرنامج المساعد attrib, هذه الطريقة يمكنك تشغيل فقط اختبار معينة محددة المعلمة:

nosetest -a number=2

جئت عبر ParamUnittest عندما تبحث في التعليمات البرمجية المصدر الرادون (مثال الاستخدام على جيثب الريبو).وينبغي أن تعمل مع غيرها من الأطر التي تمتد TestCase (مثل الأنف).

هنا مثال:

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)

يمكنني استخدام metaclasses والديكور لتوليد الاختبارات.يمكنك التحقق من تنفيذ 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

مجرد استخدام 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
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)

يمكنك استخدام 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)

كنت تواجه مشكلة مع نمط معين من معلمات الاختبارات.لدينا جميع السيلينيوم الاختبارات يمكن تشغيل محليا ولكن أيضا أنها ينبغي أن تكون قادرة على تشغيل عن بعد ضد عدة منصات على SauceLabs.في الأساس, أردت أن تأخذ كمية كبيرة بالفعل-اختبار كتابي الحالات parameterize لهم مع أقل عدد من التغييرات على قانون ممكن.وعلاوة على ذلك, كنت بحاجة إلى أن تكون قادرة على تمرير المعلمات إلى طريقة إعداد شيء أنا لم أر أي حلول أخرى.

هنا هو ما كنت تأتي مع:

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

مع هذا, كل ما كان علي فعله هو إضافة بسيطة الديكور @sauce_labs() لكل العادية القديمة TestCase و الآن عند تشغيل منهم, أنهم ملفوفة وإعادة كتابة كل اختبار أساليب معلمات و إعادة تسميته.LoginTests.test_login(النفس) يعمل LoginTests.test_login_internet_explorer_10.0(النفس) ، LoginTests.test_login_internet_explorer_11.0(النفس) ، LoginTests.test_login_firefox_43.0(النفس) و كل واحد له المعلمة النفس.منصة تقرر ما المتصفح/منصة تشغيل ، حتى في LoginTests.الإعداد الذي يعتبر حاسما في مهمتي منذ ذلك حيث اتصال SauceLabs يتم تهيئة.

على أي حال, آمل أن يكون هذا قد يكون شخص يبحث فعل مماثلة "العالمية" والثوابت من الاختبارات!

هذا الحل يعمل مع unittest و nose:

#!/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()

على metaclass القائم على إجابات لا تزال تعمل في Python3, ولكن بدلا من __metaclass__ سمة واحدة إلى استخدام metaclass المعلمة ، كما في:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

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

وبما أننا لا يجب أن نكرر أنفسنا ، اقتراحي الثاني يبني على @خافيير الجواب:تبني العقار على أساس الاختبار.الفرضية المكتبة:

  • هو "أكثر بلا هوادة الملتوية حول حالة اختبار الجيل من مجرد البشر"
  • سوف توفر لعدد بسيط-أمثلة
  • يعمل مع أي اختبار عداء
  • لديها العديد من الميزات المثيرة للاهتمام (إحصاءات إضافية اختبار الإخراج،...)

    فئة 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"))

حسنا, لا يجب متميزة اختبار الأسماء.ولكن ربما كنت فقط بحاجة الى:

  • وصفي اسم المنشأة بموجب اختبار.
  • المدخلات التي تؤدي إلى الفشل (تزوير سبيل المثال).

تسلية سبيل المثال

سوبر وقت متأخر إلى الحزب ، ولكن كان لدي مشكلة في جعل هذه العمل setUpClass.

وهنا نسخة من @خافيير الجواب أن يعطي 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

إلى جانب استخدام setattr ، يمكننا استخدام load_tests منذ الثعبان 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.يجب أن تعمل على unittest.Testcase و unittest اكتشاف 2.مجموعة من اختبارات تشغيل مختلفة إعدادات المعلمة.3.بسيطة جدا لا الاعتماد على الحزم الأخرى استيراد unittest

    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
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top