Frage

Ich habe einige Art von test Daten und möchten, erstellen Sie einen unit-test für jedes Einzelteil.Meine erste Idee war, es zu tun wie diese:

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

Der Nachteil ist, dass es behandelt alle Daten in einem test.Ich möchte so generieren Sie einen test für jedes Element auf der fliege.Irgendwelche Vorschläge?

War es hilfreich?

Lösung

ich benutze so etwas:

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

Die parameterized Paket kann verwendet werden, um diesen Prozess zu automatisieren:

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)

Die Generierung der tests:

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'

Andere Tipps

Mit unittest (seit 3.4)

Seit Python 3.4, die standard-Bibliothek unittest Paket subTest Kontext-manager.

Siehe die Dokumentation:

Beispiel:

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)

Sie können auch festlegen, eine benutzerdefinierte Nachricht und parameter-Werte subTest():

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

Mit Nase

Die Nase Test-framework dies unterstützt.

Beispiel (der code unten ist der gesamte Inhalt der Datei mit den test):

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

Die Ausgabe der nosetests-Befehl:

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

Dieses Problem kann gelöst werden elegant mit Metaklassen:

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

Ab Python 3.4 subtests wurden eingeführt, um unittest für diesen Zweck.Finden die Dokumentation für die details.TestCase.Aufgabengruppe ist ein Kontext-manager, die es einem ermöglicht zu isolieren, behauptet, in einem test, so dass ein Ausfall gemeldet werden mit dem parameter-Informationen aber nicht zu stoppen die test-Durchführung.Hier ist das Beispiel aus der Dokumentation:

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)

Der Ausgang eines Tests verlaufen wäre:

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

Dies ist auch Teil der unittest2, also es ist verfügbar für frühere Versionen von Python.

load_tests ist eine wenig bekannte Mechanismus eingeführt 2.7 dynamisch erstellen einer TestSuite.Mit es, können Sie leicht erstellen-parametrisierten tests.

Zum Beispiel:

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

Dieser code wird ausgeführt, alle Testfälle der TestSuite zurückgegeben load_tests.Keine andere tests laufen automatisch durch das discovery-Mechanismus.

Alternativ können Sie auch die Vererbung verwenden, wie gezeigt, in diesem ticket: http://bugs.python.org/msg151444

Es kann getan werden, durch die Verwendung von pytest.Schreiben Sie einfach die Datei test_me.py mit Inhalt:

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

Und führen Sie den test mit Befehl py.test --tb=short test_me.py.Dann wird die Ausgabe sieht wie folgt aus:

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

It simple!.Auch pytest hat mehr Funktionen, wie fixtures, mark, assert, usw ...

Verwenden Sie die ddt Bibliothek.Es wird einfach Dekorateure für den test Methoden:

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

Diese Bibliothek kann mit installiert werden pip.Es nicht erfordern nose, und funktioniert sehr gut mit der standard-Bibliothek unittest Modul.

Sie würde davon profitieren, versuchen die TestScenarios Bibliothek.

testscenarios bietet saubere dependency injection for python unittest tests Stil.Dies kann verwendet werden, für interface-Tests (Tests zahlreiche Implementierungen über eine einzelne test-suite) oder für klassische dependency injection (tests mit Abhängigkeiten, die von außen auf den test-code selbst, so dass Sie leicht testen in verschiedenen Situationen).

Es gibt auch die Hypothese, die fügt fuzz-oder property-based testing: https://pypi.python.org/pypi/hypothesis

Dies ist eine sehr leistungsfähige Test-Methode.

Sie können verwenden Nase-ittr plugin (pip install nose-ittr).

Es ist sehr leicht zu integrieren mit bestehenden tests, minimale änderungen (falls vorhanden) sind erforderlich.Es auch unterstützt Nase multiprocessing-plugin.

Nicht, dass Sie können auch anpassen, setup Funktion pro-test.

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

Es ist auch möglich, den pass nosetest Parameter, wie mit Ihren build-in plugin attrib, auf diese Weise Sie kann nur ausgeführt werden, einen bestimmten test mit spezifischen parameter:

nosetest -a number=2

Ich kam in ParamUnittest den anderen Tag, wenn Sie sich den Quellcode zu radon (Beispiel für die Benutzung auf dem github-repo).Es sollte mit anderen frameworks, die sich TestCase (wie die Nase).

Hier ist ein Beispiel:

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)

Ich benutze Metaklassen und Dekorateure für den generate-tests.Sie können überprüfen Sie meine Implementierung python_wrap_cases.Diese Bibliothek erfordert keine test-frameworks.

Dein Beispiel:

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)

Die Ausgabe der Konsole:

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

Auch Sie können verwenden Generatoren.Zum Beispiel dieser code generieren aller möglichen Kombinationen von tests mit Argumenten a__list und 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)

Die Ausgabe der Konsole:

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

Verwenden Sie einfach Metaklassen, wie hier zu sehen;

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

Ausgabe:

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)

ERGEBNIS:

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

Sie können verwenden TestSuite und benutzerdefinierte TestCase Klassen.

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)

Ich hatte Probleme mit einer sehr besonderen Stil von parametrisierten tests.Alle unsere Selen-tests können lokal ausgeführt werden, aber Sie sollten auch in der Lage sein zu laufen, aus der Ferne gegen mehrere Plattformen, auf SauceLabs.Im Grunde wollte ich eine große Menge von bereits geschriebenen Testfälle und parametrieren Sie diese mit den wenigsten änderungen von code möglich.Außerdem brauchte ich, um in der Lage sein, um pass-Parameter in den setUp-Methode etwas, was ich noch nicht gesehen, alle Lösungen für anderswo.

Hier ist, was ich mir ausgedacht habe:

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

Mit diesem, alles, was ich tun musste, war fügen Sie eine einfache decorator @sauce_labs() für jeden regulären alten TestCase, und nun beim laufen, Sie sind eingewickelt und umgeschrieben, so dass alle die Prüfung Methoden sind parametriert und umbenannt.LoginTests.test_login(self) wird als LoginTests.test_login_internet_explorer_10.0(self), LoginTests.test_login_internet_explorer_11.0(self), und LoginTests.test_login_firefox_43.0(self), und jeder hat die parameter selbst.Plattform, um zu entscheiden, welche browser/Plattform laufen gegen, auch in LoginTests.setUp, die ist wichtig für meine Aufgabe, da hier die Verbindung zu SauceLabs initialisiert wird.

Wie auch immer, ich hoffe, das könnte helfen, jemanden, der eine ähnliche "Globale" Parametrisierung von tests!

Diese Lösung funktioniert mit unittest und 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()

Die Metaklasse-basierten Antworten, die immer noch funktionieren in Python ist3, aber statt der __metaclass__ Attribut verwenden Sie den metaclass parameter, wie in:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

- Meta-Programmierung die Spaß macht, aber kann man auf dem Weg.Die meisten Lösungen hier schwierig machen:

  • selektiv starten Sie einen test
  • Punkt zurück, um den code-test-name

So, mein Erster Vorschlag ist, Folgen Sie den einfachen/expliziter Pfad (funktioniert mit jedem test-runner):

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

Da sollten wir nicht wiederholen, mein zweiter Vorschlag baut auf @Javier Antwort:umarmen property-based testing.Hypothese-Bibliothek:

  • "mehr unerbittlich devious über der test-case generation als uns bloße Menschen"
  • bieten einfache Zählung-Beispiele
  • funktioniert mit jedem test-runner
  • hat viele weitere interessante features (statistics, zusätzliche test-Ausgaben, ...)

    Klasse TestSequence(unittest.TestCase):

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

Testen Sie Ihre spezifische Beispiele, fügen Sie einfach:

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

Nur ein spezielles Beispiel, können Sie kommentieren, die anderen Beispiele (Beispiel werden zuerst ausgeführt).Sie möchten möglicherweise verwenden Sie @given(st.nothing()).Eine andere option ist zu ersetzen, der ganze block:

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

Ok, Sie müssen nicht unterschiedliche test-Namen.Aber vielleicht brauchen Sie nur:

  • einen beschreibenden Namen der Eigenschaft, unter test.
  • die Eingang führt zum Ausfall (verfälschend Beispiel).

Lustiger Beispiel

Super spät zur party, aber ich hatte Schwierigkeiten damit, diese arbeiten für setUpClass.

Hier ist eine version des @Javier Antwort das gibt setUpClass Zugriff auf dynamisch zugewiesenen Attribute.

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

Ausgänge

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

OK

Neben der Verwendung setattr, wir verwenden können load_tests da python 3.2.Bitte beziehen sich auf blog post 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()

Folgende ist meine Lösung.Ich finde das nützlich, wenn:1.Sollten für die unittest.Testcase und unittest entdecken 2.Sie haben eine Reihe von tests ausgeführt werden, die für verschiedene parameter-Einstellungen.3.Sehr einfach, keine Abhängigkeit von anderen Paketen import 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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top