¿Cómo se generan pruebas unitarias dinámicas (parametrizadas) en Python?

Resuelto Peter Hoffmann asked hace 16 años • 25 respuestas

Tengo algún tipo de datos de prueba y quiero crear una prueba unitaria para cada elemento. Mi primera idea fue hacerlo así:

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

La desventaja de esto es que maneja todos los datos en una sola prueba. Me gustaría generar una prueba para cada elemento sobre la marcha. ¿Alguna sugerencia?

Peter Hoffmann avatar Aug 29 '08 00:08 Peter Hoffmann
Aceptado

Esto se llama "parametrización".

Hay varias herramientas que apoyan este enfoque. P.ej:

  • decorador de pytest
  • parametrizado

El código resultante se ve así:

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)

Que generará las pruebas:

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'

Por razones históricas, dejaré la respuesta original alrededor de 2008):

Yo uso algo como esto:

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()
Dmitry Mukhin avatar Aug 28 '2008 18:08 Dmitry Mukhin

Usando unittest (desde 3.4)

Desde Python 3.4, el unittestpaquete de biblioteca estándar tiene el subTestadministrador de contexto.

Ver la documentación:

  • 26.4.7. Distinguir iteraciones de pruebas mediante subpruebas
  • subprueba

Ejemplo:

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(p1, p2):
                self.assertEqual(p1, p2)

También puede especificar un mensaje personalizado y valores de parámetros para subTest():

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

usando la nariz

El marco de prueba de nariz admite esto .

Ejemplo (el siguiente código es el contenido completo del archivo que contiene la prueba):

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

El resultado del comando 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)
codeape avatar Aug 29 '2008 07:08 codeape

Esto se puede resolver elegantemente usando Metaclases:

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()
Guy avatar Jan 01 '2014 16:01 Guy

A partir de Python 3.4, se han introducido subpruebas en unittest para este propósito. Consulte la documentación para obtener más detalles. TestCase.subTest es un administrador de contexto que permite aislar afirmaciones en una prueba para que se informe una falla con información de parámetros, pero no detiene la ejecución de la prueba. Aquí está el ejemplo de la documentación:

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)

El resultado de una ejecución de prueba sería:

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

Esto también es parte de unittest2 , por lo que está disponible para versiones anteriores de Python.

Bernhard avatar Apr 01 '2015 06:04 Bernhard