Python'da dinamik (parametreli) birim testleri nasıl üretiyorsunuz?


234

Test verilerinin bir tür var ve her öğe için bir birim testi oluşturmak istiyorum. İlk fikrim böyle yapmaktı:

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

Bunun dezavantajı, tüm verileri tek bir testte ele almasıdır. Anında her bir öğe için bir test oluşturmak istiyorum. Baska öneri?



2
Yanıt verebilecek iyi bir bağlantı: eli.thegreenplace.net/2014/04/02/…
gaborous

Yanıtlar:


173

Buna "parametrelendirme" denir.

Bu yaklaşımı destekleyen birkaç araç vardır. Örneğin:

Ortaya çıkan kod şöyle görünür:

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)

Hangi testleri üretecek:

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'

Tarihsel nedenlerle orijinal cevabı 2008 civarında bırakacağım):

Böyle bir şey kullanıyorum:

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
Aslında, bignose, bu kod her test için farklı bir isim üretir (aslında başka türlü işe yaramaz). Verilen örnekte yürütülen testler sırasıyla "test_foo", "test_bar" ve "test_lee" olarak adlandırılacaktır. Böylece, bahsettiğiniz fayda (ve büyük bir kazançtır) mantıklı isimler ürettiğiniz sürece korunur.
Toji

1
@Codeape durumlarının verdiği yanıt olarak burun bunu halleder. Bununla birlikte, burun Unicode ile başa çıkmıyor gibi görünüyor; bu yüzden benim için bu tercih edilebilir bir çözüm. +1
Keith Pinson

5
Bu nedenle, yinelenen soruda daha doğru bir cevap verildiğini unutmayın : stackoverflow.com/a/2799009/322020 - testi .__name__ =etkinleştirmek için kullanmanız gerekir.exact_method
Nakilon

7
Sınıfı değiştiren kod neden if __name__ == '__main__'şartlı olarak görünüyor ? Şüphesiz, içe aktarma zamanında çalışmak için bunun dışına çıkmalıdır (python modüllerinin birkaç farklı yerden içe
aktarılsa

4
Bunun iyi bir çözüm olduğunu düşünmüyorum. Birim testin kodu, çağrılma şekline bağlı olmamalıdır. TestCase burun veya pestili veya farklı bir test ortamında kullanılabilir olmalıdır.
guettli

146

Unittest kullanma (3.4'ten beri)

Python 3.4'ten bu yana, standart kütüphane unittestpaketinin subTestiçerik yöneticisi vardır.

Belgelere bakın:

Misal:

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)

Ayrıca aşağıdakilere özel bir mesaj ve parametre değerleri belirleyebilirsiniz subTest():

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

Burun kullanımı

Burun test çerçevesi bu destekler .

Örnek (aşağıdaki kod, testi içeren dosyanın tüm içeriğidir):

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 komutunun çıktısı:

> 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
Bu, test senaryolarını dinamik olarak oluşturmanın çok temiz bir yoludur.
gaborous

Ancak unutmayın, 'setup ()' hangi değişkenlerin verim argümanı olarak kullanıldığını bilemez. Aslında setup () hangi testin çalıştığını bilmez veya test_generator () içinde ayarlanan değişkenleri bilmez. Bu, setup () içindeki akıl sağlığı kontrolünü zorlaştırır ve bazı kişilerin py.test'i tercih etmelerinin nedenlerinden biridir.
Scott Prive

1
Güncelleme bölümü için seçildi. Tam olarak ihtiyacım olan şey. :)
Saurabh Shrivastava

1
Unittest sürümünü pytest ile çalıştırmanın bir yolu var mı, böylece tüm vakaları çalıştıracak ve ilk başarısız parametrede durmayacak mı?
kakk11

1
@ Kakk11 tarafından belirtildiği gibi, bu cevap (ve genel olarak subTest) pytest ile çalışmaz. Bu bilinen bir sorundur. Bu işi yapmak için aktif olarak geliştirilmiş bir eklenti var: github.com/pytest-dev/pytest-subtests
Jérémie

76

Bu Metaclasses kullanarak zarif bir şekilde çözülebilir:

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
Bu benim için Selenyum ile BÜYÜK çalıştı. Bir not olarak, TestSequence sınıfında setUp (self), is_element_present (self, how, what), ... tearDown (self) gibi "statik" yöntemleri tanımlayabilirsiniz. Onları " metaclass = TestSequenceMeta" ifadesinden SONRA koymak işe yarıyor gibi görünüyor.
Sevgi ve barış - Joe Codeswell

5
Bu çözüm, kabul edilen IMHO olarak seçilen çözümden daha iyidir.
16'da petroslamb

2
@petroslamb Metasınıftaki __new__yöntem, ilk örnek oluşturulduğunda değil, sınıfın kendisi tanımlandığında çağrılır. Dinamik olarak test yöntemleri oluşturma yönteminin unittestbir sınıfta kaç test olduğunu belirlemek için kullanılan içgözlem ile daha uyumlu olduğunu düşünürdüm (yani, o sınıfın bir örneğini oluşturmadan önce test listesini derleyebilir).
BillyBBone

11
Not: python 3'te bunu şu şekilde değiştirin:class TestSequence(unittest.TestCase, metaclass=TestSequenceMeta):[...]
Mathieu_Du

3
dctBunun yerine lütfen kullanabilir misiniz dict? Anahtar kelimeleri değişken adları olarak kullanmak kafa karıştırıcı ve hataya açıktır.
18:26, npfoss

49

Python 3.4 itibariyle bu amaçla unittest'e alt testler yapılmıştır. Ayrıntılar için belgelere bakın. TestCase.subTest, bir testte eklerin izole edilmesine izin veren bir bağlam yöneticisidir, böylece bir arıza parametre bilgileriyle rapor edilir, ancak test yürütülmesini durdurmaz. Belgelerden örnek:

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)

Bir test çalıştırmasının çıktısı şöyle olacaktır:

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

Bu ayrıca unittest2'nin bir parçasıdır , bu nedenle Python'un önceki sürümleri için kullanılabilir.


1
Python 3.4 ve üzerini kullanıyorsanız en iyi çözüm.
Max Malysh

4
Unittest2 kullanıldığında, bu Python 2.7 için de kullanılabilir.
Bernhard

11
Bu yaklaşım ile ayrı testlerin yapılması arasındaki en büyük fark, test durumunun her seferinde sıfırlanmamasıdır. (Yani setUp()ve tearDown()alt testler arasında çalıştırılmaz.)
Kevin Christopher Henry

1
@KevinChristopherHenry Evet, ancak self.setUp()teorik olarak alt testin içinden manuel olarak çağrılabilir. Gelince tearDown, otomatik olarak sonunda çağırmak yeterli olabilir.
Acumenus

Yukarıdaki metasınıf yaklaşımı ile birlikte kullanıldığında bunun güçlü olabileceğini düşünüyorum.
Nathan Chappell

36

load_tests , dinamik olarak bir TestSuite oluşturmak için 2.7'de tanıtılan az bilinen bir mekanizmadır. Bununla kolayca parametreli testler oluşturabilirsiniz.

Örneğin:

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

Bu kod, load_tests tarafından döndürülen TestSuite içindeki tüm TestCas'leri çalıştıracaktır. Keşif mekanizması tarafından başka hiçbir test otomatik olarak yapılmaz.

Alternatif olarak, bu bilette gösterildiği gibi kalıtım da kullanabilirsiniz: http://bugs.python.org/msg151444


1
Yukarıdaki kod başarısız olur: TypeError: __init __ () en fazla 2 bağımsız değişken alır (4 tane verilir)
en fazla

2
Yapıcı ekstra parametrelerine null varsayılanları eklendi.
Javier

@ Mojo'nun cevabında burun parametresi kodunu tercih ediyorum , ancak müşterilerim için ekstra bağımlılıktan kaçınmak çok yararlı, bu yüzden onlar için kullanacağım.
adaçayı

1
Bu çözüm, bu sayfadaki favorimdi. Hem mevcut üst yanıtta önerilen Burun hem de çatalı Burun2 yalnızca bakımdır ve ikincisi, kullanıcıların bunun yerine pytest'i denemelerini önerir . Ne dağınıklık - Böyle doğal bir yaklaşıma bağlı kalacağım!
Sean

1
bonus: kısa parametrelerini yeniden tanımlama yeteneği Paramlarda iletilen çıktı için açıklama yöntemi
fun_vit

33

Pytest kullanılarak yapılabilir . Dosyayı test_me.pyiçerikle yazmanız yeterlidir :

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

Ve testinizi komutla çalıştırın py.test --tb=short test_me.py. Sonra çıktı şöyle görünecektir:

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

Çok basit !. Ayrıca pytest gibi başka özelliklere sahiptir fixtures, mark, assert, vb ...


1
Test senaryolarını py.test ile nasıl parametrelendireceğinize dair basit ve doğrudan bir örnek arıyordum. Çok teşekkür ederim!
timgeb

@timgeb Size yardımcı olmaktan memnuniyet duyarız. Kontrol py.test fazla örnek için, etiketi. Ayrıca , kendi yolunuzla değiştirilebilen, birleştirilebilen veya oluşturulabilen, insan tarafından okunabilen kıskaçlarla, şekerlerinize biraz şeker eklemek için hamcrest kullanmanızı tavsiye ederim . Artı allure-python , güzel görünümlü bir rapor üretimi içinpy.test
Sergey Voronezhskiy

Teşekkürler. Sadece unittestpy.test'e geçmeye başladım . Eskiden yaşadığım TestCasedinamik onlar biraz hantal oldu sınıf değişkenleri ... olarak saklamak istiyorum farklı argümanlarla çocukları oluşturmak başardık taban sınıfları.
timgeb

1
@timgeb Evet, haklısın. En katil özelliği ait py.testolduğu yield_fixtures . Hangi kurulum yapabilir , bazı yararlı verileri teste geri döndürür ve test bittikten sonra yıkılır . Fikstürler de parametrelendirilebilir .
Sergey Voronezhskiy

12

Ddt kütüphanesini kullanın . Test yöntemleri için basit dekoratörler ekler:

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

Bu kütüphane ile kurulabilir pip. noseStandart kütüphane unittestmodülünü gerektirmez ve mükemmel çalışır .


6

TestScenarios kütüphanesini denemekten faydalanırsınız .

testscenarios, python unittest stil testleri için temiz bağımlılık enjeksiyonu sağlar. Bu, arayüz testi (tek bir test paketi aracılığıyla birçok uygulamayı test etmek) veya klasik bağımlılık enjeksiyonu için (test kodunun dışında harici olarak bağımlılıklar içeren testler sağlayarak farklı durumlarda kolay test yapılmasına olanak tanır) kullanılabilir.



4

Sen kullanabilirsiniz burun ittr eklenti ( pip install nose-ittr).

Mevcut testlerle entegre etmek çok kolaydır, minimum değişiklikler (varsa) gereklidir. Ayrıca burun çoklu işleme eklentisini de destekler .

setupTest başına bir özelleştirme fonksiyonuna da sahip olabileceğinizden değil .

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

Ayrıca, nosetestyerleşik eklentileri gibi parametreleri iletmek de mümkündür attrib, bu şekilde yalnızca belirli parametrelerle belirli bir testi çalıştırabilirsiniz:

nosetest -a number=2

Bu yaklaşımı seviyorum, özellikle desteklediği yöntem başına.
Matt

3

Testler oluşturmak için metasınıflar ve dekoratörler kullanıyorum. Benim uygulama python_wrap_cases kontrol edebilirsiniz . Bu kütüphane için test çerçevesi gerekmez.

Örneğiniz:

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)

Konsol çıkışı:

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

Ayrıca jeneratör kullanabilirsiniz . Örneğin, bu kod tüm olası test kombinasyonlarını argümanlarla oluşturur a__listveb__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)

Konsol çıkışı:

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

Ben rastladım ParamUnittest için kaynak koduna bakarken Geçen gün radon ( github repo örnek kullanımı ). TestCase'i (Burun gibi) genişleten diğer çerçevelerle çalışmalıdır.

İşte bir örnek:

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)

SONUÇ:

>>> 
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İşlevinizle ilgili küçük bir sorun . def _add_test_methods Sanırım olmalı
Raychaser

@Raychaser ... Haklısın ... Bunu düzelttim ama burada güncellemedim .... Onu yakaladığın için teşekkürler.
Arindam Roychowdhury

1

Burada görüldüğü gibi sadece metasınıflar kullanın;

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

Çıktı:

test_sample (ExampleTestCase) ... OK

1

TestSuiteVe özel TestCasesınıfları kullanabilirsiniz .

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 çalışırken, bağımsız değişkenler __init__işlevine geçirilmez .
jadelord

1

Özellikle veri toplanması üzerinde biraz fark yaratan işlemler yapmak için testler üretmem gerekirse, bunun benim amacım için iyi çalıştığını gördüm.

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

TestGeneratorSınıf gibi test olguların farklı setleri yumurtlamaya için kullanılabilir TestCluster.

TestClusterTestGeneratorarayüzün bir uygulaması olarak düşünülebilir .


1

Bu çözüm ile çalışır unittestve nosePython 2 ve Python 3 için:

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

Yükseltilmiş sürüm <3 için @ guillaume-jacquenot teşekkür ederiz!
Paspas

0

Parametreli testlerin çok özel bir tarzı ile sorun yaşıyordum. Tüm Selenyum testlerimiz yerel olarak yapılabilir, ancak SauceLabs'daki çeşitli platformlara karşı uzaktan da çalıştırılabilmelidir. Temel olarak, önceden yazılmış test senaryolarının büyük bir miktarını almak ve bunları mümkün olan en az kod değişikliği ile parametreleştirmek istedim. Ayrıca, parametreleri başka bir yerde herhangi bir çözüm görmedim setUp yöntemine geçmek gerekir.

İşte ben geldim:

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

Bununla, tek yapmam gereken her normal eski TestCase'e basit bir dekoratör @sauce_labs () eklemekti ve şimdi bunları çalıştırırken, sarılıp yeniden yazıldılar, böylece tüm test yöntemleri parametrelendirildi ve yeniden adlandırıldı. LoginTests.test_login (self) LoginTests.test_login_internet_explorer_10.0 (self), LoginTests.test_login_internet_explorer_11.0 (self) ve LoginTests.test_login_firefox_43.0 (self) olarak çalışır ve her birinin hangi tarayıcı / SauceLabs ile bağlantının başlatıldığı yer olan görevim için çok önemli olan LoginTests.setUp'ta bile çalıştırılacak bir platform.

Her neyse, umarım bu, testlerinin benzer bir "global" parametreleştirmesini yapmak isteyen birine yardımcı olabilir!


0

Metasınıf tabanlı yanıtlar hala Python3'te çalışır, ancak __metaclass__öznitelik yerine metaclassparametreyi aşağıdaki gibi kullanmalıdır :

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

0

Meta-programlama eğlencelidir, ancak yola çıkabilir. Buradaki çoğu çözüm aşağıdakileri zorlaştırır:

  • seçici olarak bir test başlat
  • testin koduna geri dönün

Yani, benim ilk önerim basit / açık yolu (herhangi bir test çalıştırıcısı ile çalışır) takip etmektir:

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

Kendimizi tekrar etmememiz gerektiğinden, ikinci önerim @ Javier'in cevabına dayanıyor: mülk tabanlı testi kucaklamak. Hipotez Kütüphanesi:

  • "test senaryosu üretme konusunda bizden daha acımasız bir şekilde sapkın"
  • basit sayım örnekleri sağlayacaktır
  • herhangi bir test koşucusu ile çalışır
  • çok daha ilginç özelliklere sahiptir (istatistikler, ek test çıktısı, ...)

    sınıf TestSequence (unittest.TestCase):

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

Belirli örneklerinizi test etmek için şunu ekleyin:

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

Yalnızca belirli bir örneği çalıştırmak için diğer örnekleri yorumlayabilirsiniz (verilen örnek önce çalıştırılacaktır). Kullanmak isteyebilirsiniz@given(st.nothing()) . Başka bir seçenek, tüm bloğu şu şekilde değiştirmektir:

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

Tamam, farklı test adlarınız yok. Ama belki de sadece ihtiyacınız var:

  • test edilen mülkün açıklayıcı adı.
  • hangi giriş başarısızlığa yol açar (tahrif edici örnek).

Daha komik örnek


0

Partiye çok geç kaldım, ama bu işi yapmakta zorlandım setUpClass .

Dinamik olarak tahsis edilmiş özelliklere erişim sağlayan @ Javier'in cevabının bir sürümü 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

çıktılar

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

OK

0

Sadece karışıma başka bir çözüm atmak;)

Bu, parameterizedyukarıda belirtildiği gibi etkili bir şekilde aynıdır , ancak aşağıdakilere spesifiktir 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

Örnek kullanım:

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

-1

Setattr kullanmanın yanı sıra, python 3.2'den bu yana load_tests kullanabiliriz. Lütfen blog yazısına bakın 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

İşte benim çözümüm. Bunu şu durumlarda yararlı buluyorum: 1. unittest için çalışmalı. Test ve unittest keşfetmek 3. Çok basit diğer paketler ithalat unittest bağımlılık yok

    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

Bu, anında testler üretmeyle ilgili soruya cevap vermiyor.
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)

orada biçimlendirmeyi kaybettiniz. olduğu gibi okumak gerçekten zor
Arturo
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.