كيف يمكنك توليد ديناميكية (معلمات) وحدة الاختبارات في الثعبان ؟
-
09-06-2019 - |
سؤال
لدي نوع من بيانات الاختبار و ترغب في إنشاء وحدة اختبار كل بند.كانت فكرتي الأولى أن تفعل ذلك من هذا القبيل:
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