Question

J'ai une sorte de données de test et je souhaite créer un test unitaire pour chaque élément.Ma première idée était de procéder ainsi :

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

L’inconvénient est qu’il gère toutes les données en un seul test.Je voudrais générer un test pour chaque élément à la volée.Aucune suggestion?

Était-ce utile?

La solution

j'utilise quelque chose comme ceci :

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

Le parameterized Le package peut être utilisé pour automatiser ce processus :

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)

Ce qui générera les 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'

Autres conseils

Utilisation de unittest (depuis 3.4)

Depuis Python 3.4, la bibliothèque standard unittest le paquet a le subTest gestionnaire de contexte.

Voir la documentation :

Exemple:

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)

Vous pouvez également spécifier un message personnalisé et des valeurs de paramètres à subTest():

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

Utiliser le nez

Le nez cadre de test soutient cela.

Exemple (le code ci-dessous est l'intégralité du contenu du fichier contenant le 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

Le résultat de la commande 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)

Cela peut être résolu avec élégance en utilisant les métaclasses :

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

Depuis Python 3.4, des sous-tests ont été introduits dans unittest à cet effet.Voir La documentation pour plus de détails.TestCase.subTest est un gestionnaire de contexte qui permet d'isoler les assertions dans un test afin qu'un échec soit signalé avec les informations sur les paramètres mais n'arrête pas l'exécution du test.Voici l'exemple de la documentation :

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)

Le résultat d’un test serait :

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

Cela fait également partie de test unitaire2, il est donc disponible pour les versions antérieures de Python.

chargement_tests est un mécanisme peu connu introduit dans la version 2.7 pour créer dynamiquement une TestSuite.Avec lui, vous pouvez facilement créer des tests paramétrés.

Par exemple:

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

Ce code exécutera tous les TestCases de la TestSuite renvoyés par load_tests.Aucun autre test n'est automatiquement exécuté par le mécanisme de découverte.

Alternativement, vous pouvez également utiliser l'héritage comme indiqué dans ce ticket : http://bugs.python.org/msg151444

Cela peut être fait en utilisant test py.Écrivez simplement le fichier test_me.py avec contenu :

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

Et lancez votre test avec la commande py.test --tb=short test_me.py.Le résultat ressemblera alors à :

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

C'est simple !.Aussi test py a plus de fonctionnalités comme fixtures, mark, assert, etc ...

Utilisez le ddt bibliothèque.Il ajoute des décorateurs simples pour les méthodes de test :

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

Cette bibliothèque peut être installée avec pip.Cela ne nécessite pas nose, et fonctionne parfaitement avec la bibliothèque standard unittest module.

Vous gagneriez à essayer le Scénarios de test bibliothèque.

testscenarios fournit une injection de dépendance propre pour les tests de style python unittest.Cela peut être utilisé pour les tests d'interface (tester de nombreuses implémentations via une seule suite de tests) ou pour l'injection de dépendances classique (fournir des tests avec des dépendances externes au code de test lui-même, permettant des tests faciles dans différentes situations).

Il existe également Hypothesis qui ajoute des tests fuzz ou basés sur des propriétés : https://pypi.python.org/pypi/hypothesis

Il s'agit d'une méthode de test très puissante.

Vous pouvez utiliser nez-ittr brancher (pip install nose-ittr).

Il est très facile à intégrer aux tests existants, des modifications minimes (le cas échéant) sont nécessaires.Il prend également en charge nez plugin multitraitement.

Non pas que vous puissiez également personnaliser setup fonction par test.

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

Il est également possible de passer nosetest paramètres comme avec leur plugin intégré attrib, de cette façon, vous pouvez exécuter uniquement un test spécifique avec un paramètre spécifique :

nosetest -a number=2

Je suis tombé sur ParamUnittest l'autre jour, en regardant le code source pour radon (exemple d'utilisation sur le dépôt github).Il devrait fonctionner avec d'autres frameworks qui étendent TestCase (comme Nose).

Voici un exemple:

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)

J'utilise des métaclasses et des décorateurs pour générer des tests.Vous pouvez vérifier ma mise en œuvre python_wrap_cases.Cette bibliothèque ne nécessite aucun framework de test.

Votre exemple :

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)

Sortie de la console :

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

Vous pouvez également utiliser générateurs.Par exemple ce code génère toutes les combinaisons possibles de tests avec des arguments a__list et 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)

Sortie de la console :

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

Utilisez simplement des métaclasses, comme vu ici ;

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

Sortir:

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)

RÉSULTAT:

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

Vous pouvez utiliser TestSuite et personnalisé TestCase Des classes.

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)

J'avais des problèmes avec un style très particulier de tests paramétrés.Tous nos tests Selenium peuvent être exécutés localement, mais ils devraient également pouvoir être exécutés à distance sur plusieurs plates-formes sur SauceLabs.Fondamentalement, je voulais prendre une grande quantité de cas de test déjà écrits et les paramétrer avec le moins de modifications de code possible.De plus, je devais pouvoir transmettre les paramètres dans la méthode setUp, ce pour lequel je n'ai vu aucune solution ailleurs.

Voici ce que j'ai trouvé :

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

Avec cela, tout ce que j'avais à faire était d'ajouter un simple décorateur @sauce_labs() à chaque ancien TestCase standard, et maintenant, lors de leur exécution, ils sont enveloppés et réécrits, de sorte que toutes les méthodes de test soient paramétrées et renommées.LoginTests.test_login(self) s'exécute sous les noms LoginTests.test_login_internet_explorer_10.0(self), LoginTests.test_login_internet_explorer_11.0(self) et LoginTests.test_login_firefox_43.0(self), et chacun a le paramètre self.platform pour décider quel navigateur/ plate-forme sur laquelle exécuter, même dans LoginTests.setUp, ce qui est crucial pour ma tâche puisque c'est là que la connexion à SauceLabs est initialisée.

Quoi qu'il en soit, j'espère que cela pourra être utile à quelqu'un qui cherche à faire un paramétrage "global" similaire de ses tests !

Cette solution fonctionne avec unittest et 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()

Les réponses basées sur les métaclasses fonctionnent toujours en Python3, mais au lieu de __metaclass__ attribut, il faut utiliser le metaclass paramètre, comme dans :

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

La méta-programmation est amusante, mais elle peut prendre du chemin.La plupart des solutions ici rendent difficile :

  • lancer sélectivement un test
  • pointer vers le code donné le nom du test

Donc, ma première suggestion est de suivre le chemin simple/explicite (fonctionne avec n'importe quel programme d'exécution de test) :

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

Puisqu'il ne faut pas se répéter, ma deuxième suggestion s'appuie sur la réponse de @Javier :adopter des tests basés sur les propriétés.Bibliothèque d'hypothèses :

  • est "plus sournois en matière de génération de cas de test que nous, simples humains"
  • fournira des exemples simples
  • fonctionne avec n'importe quel lanceur de test
  • possède de nombreuses fonctionnalités plus intéressantes (statistiques, sorties de tests supplémentaires, ...)

    classe TestSequence (unittest.TestCase) :

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

Pour tester vos exemples spécifiques, ajoutez simplement :

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

Pour exécuter un seul exemple particulier, vous pouvez commenter les autres exemples (à condition que l'exemple soit exécuté en premier).Vous voudrez peut-être utiliser @given(st.nothing()).Une autre option consiste à remplacer le bloc entier par :

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

Ok, vous n'avez pas de noms de tests distincts.Mais peut-être avez-vous juste besoin de :

  • un nom descriptif de la propriété testée.
  • quelle entrée conduit à l’échec (exemple falsifiant).

Exemple plus drôle

Très tard à la fête, mais j'ai eu du mal à les faire fonctionner pour setUpClass.

Voici une version de @ Réponse de Javier ça donne setUpClass accès aux attributs alloués dynamiquement.

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

Les sorties

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

OK

En plus d'utiliser setattr, nous pouvons utiliser load_tests depuis python 3.2.Veuillez vous référer à l'article du blog 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()

Voici ma solution.Je trouve cela utile lorsque :1.Devrait fonctionner pour unittest.testcase et unittest Discover 2.Avoir un ensemble de tests à exécuter pour différents réglages de paramètres.3.Très simple aucune dépendance à l'égard des autres packages importent un 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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top