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

235

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 prueba. Me gustaría generar una prueba para cada artículo sobre la marcha. ¿Alguna sugerencia?

Peter Hoffmann
fuente
2
Un buen enlace que puede proporcionar una respuesta: eli.thegreenplace.net/2014/04/02/…
gaborous

Respuestas:

173

Esto se llama "parametrización".

Existen varias herramientas que respaldan este enfoque. P.ej:

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)

Lo 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
fuente
24
En realidad, bignose, este código genera un nombre diferente para cada prueba (de lo contrario, no funcionaría). En el ejemplo dado, las pruebas ejecutadas se denominarán "test_foo", "test_bar" y "test_lee" respectivamente. Por lo tanto, el beneficio que menciona (y es importante) se conserva siempre que genere nombres sensibles.
Toji
1
Como dice la respuesta de @codeape, nose maneja esto. Sin embargo, la nariz no parece manejar Unicode; Por lo tanto, para mí esta es una solución preferible. +1
Keith Pinson
55
Entonces, tenga en cuenta que se da una respuesta más adecuada en la pregunta duplicada : stackoverflow.com/a/2799009/322020 : tiene .__name__ =que habilitar la .exact_methodprueba
Nakilon
77
¿Por qué el código que modifica la clase aparece en el if __name__ == '__main__'condicional? Seguramente debería salir de esto para ejecutarse en el momento de la importación (recordando que los módulos de Python solo se importan una vez, incluso si se importan desde varios lugares diferentes)
SpoonMeiser
44
No creo que esta sea una buena solución. El código de una prueba unitaria no debe depender de la forma en que se llama. TestCase debe ser utilizable en nose o pytest o en un entorno de prueba diferente.
guettli
147

Usando unittest (desde 3.4)

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

Ver la documentación:

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():
                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 es compatible con esto .

Ejemplo (el código a continuación 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

La salida 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
fuente
3
Esa es una forma muy limpia de generar dinámicamente casos de prueba.
Gaborous
Pero tenga en cuenta que 'setup ()' no sabrá qué variables se están utilizando como argumentos para obtener. En realidad, setup () no sabrá qué prueba se está ejecutando, o vars configurados dentro de test_generator (). Esto complica la comprobación de cordura dentro de setup (), y es una de las razones por las que algunas personas prefieren py.test.
Scott Prive
1
Votado a favor de la sección de actualización. Exactamente lo que necesitaba. :)
Saurabh Shrivastava
1
¿Hay alguna manera de ejecutar la versión unittest con pytest, para que ejecute todos los casos y no se detenga en el primer parámetro fallido?
kakk11
1
Como mencionó @ kakk11, esta respuesta (y subTest en general) no funciona con pytest. Este es un problema conocido. Hay un complemento desarrollado activamente para que esto funcione: github.com/pytest-dev/pytest-subtests
Jérémie
76

Esto se puede resolver con elegancia usando 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()
Chico
fuente
1
Esto funcionó GENIAL para mí con Selenium. Como nota, en la clase TestSequence, puede definir métodos "estáticos" como setUp (self), is_element_present (self, how, what), ... tearDown (self). Ponerlos DESPUÉS de la instrucción " metaclass = TestSequenceMeta" parece funcionar.
Amor y paz - Joe Codeswell
55
Esta solución es mejor que la seleccionada como aceptada en mi humilde opinión.
petroslamb
2
@petroslamb El __new__método en la metaclase se llama cuando se define la clase en sí, no cuando se crea la primera instancia. Me imagino que este método de crear dinámicamente métodos de prueba es más compatible con la introspección utilizada unittestpara determinar cuántas pruebas hay en una clase (es decir, puede compilar la lista de pruebas antes de crear una instancia de esa clase).
BillyBBone
11
Nota: en Python 3, cambie esto a:class TestSequence(unittest.TestCase, metaclass=TestSequenceMeta):[...]
Mathieu_Du
3
¿Podrías usar en dctlugar de dict? Usar palabras clave como nombres de variables es confuso y propenso a errores.
npfoss
49

A partir de Python 3.4 se han introducido subpruebas en unittest para este propósito. Consulte la documentación para más detalles. TestCase.subTest es un administrador de contexto que permite aislar afirmaciones en una prueba para que se informe un error con la información de los 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
fuente
1
La mejor solución si usa Python 3.4 y superior.
Max Malysh
44
Usando unittest2, esto también está disponible para Python 2.7.
Bernhard
11
Una diferencia importante entre este enfoque y tener pruebas separadas es que el estado de prueba no se restablece cada vez. (Es decir, setUp()y tearDown()no se ejecutan entre las subpruebas.)
Kevin Christopher Henry
1
@KevinChristopherHenry Sí, pero self.setUp()en teoría se puede llamar manualmente desde la subprueba. En cuanto a tearDown, llamarlo automáticamente al final podría ser suficiente.
Acumenus
Creo que esto podría ser poderoso cuando se usa junto con el enfoque de metaclase anterior.
Nathan Chappell
36

load_tests es un mecanismo poco conocido introducido en 2.7 para crear dinámicamente un TestSuite. Con él, puede crear fácilmente pruebas parametrizadas.

Por ejemplo:

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

Ese código ejecutará todos los TestCases en TestSuite devueltos por load_tests. El mecanismo de descubrimiento no ejecuta automáticamente otras pruebas.

Alternativamente, también puede usar la herencia como se muestra en este ticket: http://bugs.python.org/msg151444

Javier
fuente
1
El código anterior falla: TypeError: __init __ () toma como máximo 2 argumentos (4 dados)
máximo
2
Se agregaron valores predeterminados nulos a los parámetros adicionales del constructor.
Javier
Prefiero el código de parametrización nasal en la respuesta de @ mojo , pero para mis clientes es demasiado útil para evitar una dependencia adicional, así que lo usaré para ellos.
sabio
1
Esta solución fue mi favorita en esta página. Tanto Nose , sugerido en la respuesta principal actual, como su fork Nose2 son solo de mantenimiento, y este último sugiere que los usuarios prueben pytest . ¡Qué desastre! ¡Seguiré con un enfoque nativo como este!
Sean
1
bonificación: capacidad de redefinir el método shortDescription para la salida aprobada en params
fun_vit
33

Se puede hacer usando pytest . Simplemente escriba el archivo test_me.pycon contenido:

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

Y ejecuta tu prueba con el comando py.test --tb=short test_me.py. Entonces la salida se verá así:

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

¡Es simple! También PYtest tiene más funciones, como fixtures, mark, assert, etc ...

Sergey Voronezhskiy
fuente
1
Estaba buscando un ejemplo simple y directo de cómo parametrizar casos de prueba con py.test. ¡Muchas gracias!
timgeb
@timgeb Me alegra ayudarte. Verifique la etiqueta py.test , para más ejemplos. También sugiero usar Hamcrest para agregar un poco de azúcar en sus afirmaciones con muthers legibles para humanos, que pueden modificarse, combinarse o crearse a su manera. Además, disponemos de encanto en Python , un aspecto agradable para la generación de informespy.test
Sergey Voronezhskiy
Gracias. Acabo de empezar a pasar unittesta py.test. Solía ​​tener TestCaseclases base que podían crear dinámicamente niños con diferentes argumentos que almacenaban como variables de clase ... lo cual era un poco difícil de manejar.
timgeb
1
@timgeb Sí, tienes razón. La más característica asesino de py.testes yield_fixtures . Lo que puede hacer la configuración , devolver algunos datos útiles a la prueba y, una vez finalizada la prueba, realizar el desmontaje . Los accesorios también se pueden parametirizar .
Sergey Voronezhskiy
12

Usa la biblioteca ddt . Agrega decoradores simples para los métodos de prueba:

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

Esta biblioteca se puede instalar con pip. No requiere nose, y funciona excelente con el unittestmódulo de biblioteca estándar .

Mykhaylo Kopytonenko
fuente
6

Te beneficiaría probar la biblioteca TestScenarios .

testscenarios proporciona una inyección de dependencia limpia para las pruebas de estilo de prueba unitaria de python. Esto se puede usar para probar la interfaz (probar muchas implementaciones a través de un único conjunto de pruebas) o para la inyección de dependencia clásica (proporcionar pruebas con dependencias externas al propio código de prueba, lo que permite realizar pruebas fácilmente en diferentes situaciones).

nariz grande
fuente
4

Puedes usar el plugin nose-ittr (pip install nose-ittr ).

Es muy fácil de integrar con las pruebas existentes, se requieren cambios mínimos (si los hay). También es compatible con la nariz. complemento multiprocesamiento de .

No es que también pueda tener una setupfunción de personalización por prueba.

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

También es posible pasar nosetestparámetros como con su complemento incorporado attrib, de esta manera solo puede ejecutar una prueba específica con un parámetro específico:

nosetest -a number=2
Maroun
fuente
Me gusta este enfoque, especialmente el nivel por método que admite.
Matt
3

Utilizo metaclases y decoradores para generar pruebas. Puedes consultar mi implementación python_wrap_cases . Esta biblioteca no requiere ningún marco de prueba.

Su ejemplo:

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)

Salida de consola:

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

También puedes usar generadores . Por ejemplo, este código genera todas las combinaciones posibles de pruebas con argumentos a__listyb__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)

Salida de consola:

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
Kirill Ermolov
fuente
2

Me encontré con ParamUnittest el otro día cuando miraba el código fuente del radón ( ejemplo de uso en el repositorio de github ). Debería funcionar con otros marcos que extiendan TestCase (como Nose).

Aquí hay un ejemplo:

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

RESULTADO:

>>> 
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)
Arindam Roychowdhury
fuente
1
Problema menor con su def add_test_methodsfunción. Debería ser def _add_test_methods , creo
Raychaser
@Raychaser ... Tienes razón ... Lo arreglé pero no lo actualicé aquí ... Gracias por entender eso.
Arindam Roychowdhury
1

Simplemente use metaclases, como se ve aquí;

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

Salida:

test_sample (ExampleTestCase) ... OK
soñoliento
fuente
1

Puedes usar clases TestSuitepersonalizadas 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)
Max Malysh
fuente
Mientras que TestSuite funciona, los argumentos no se transmiten a la __init__función.
Jadelord
1

He descubierto que esto funciona bien para mis propósitos, especialmente si necesito generar pruebas que diferencien ligeramente los procesos en una recopilación de datos.

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

La TestGeneratorclase se puede utilizar para generar diferentes conjuntos de casos de prueba como TestCluster.

TestClusterpuede considerarse como una implementación de la TestGeneratorinterfaz.

bcdan
fuente
1

Esta solución funciona con unittesty nosepara Python 2 y Python 3:

#!/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()
fregona
fuente
¡Gracias @ guillaume-jacquenot por la versión actualizada <3!
trapeador
0

Había estado teniendo problemas con un estilo muy particular de pruebas parametrizadas. Todas nuestras pruebas de Selenium pueden ejecutarse localmente, pero también deberían poder ejecutarse de forma remota en varias plataformas en SauceLabs. Básicamente, quería tomar una gran cantidad de casos de prueba ya escritos y parametrizarlos con la menor cantidad posible de cambios en el código. Además, necesitaba poder pasar los parámetros al método de configuración, algo para lo que no he visto ninguna solución en ningún otro lado.

Esto es lo que se me ocurrió:

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

Con esto, todo lo que tenía que hacer era agregar un decorador simple @sauce_labs () a cada TestCase antiguo normal, y ahora cuando los ejecuta, se envuelven y se reescriben, de modo que todos los métodos de prueba se parametrizan y cambian de nombre. LoginTests.test_login (self) se ejecuta como LoginTests.test_login_internet_explorer_10.0 (self), LoginTests.test_login_internet_explorer_11.0 (self) y LoginTests.test_login_firefox_43.0 (self), y cada uno tiene el parámetro self.platform para decidir qué navegador self / plataforma. plataforma para ejecutar, incluso en LoginTests.setUp, que es crucial para mi tarea ya que ahí es donde se inicializa la conexión a SauceLabs.

De todos modos, ¡espero que esto pueda ser de ayuda para alguien que busque hacer una parametrización "global" similar de sus pruebas!

Danielle Weisz
fuente
0

Las respuestas basadas en metaclases todavía funcionan en Python3, pero en lugar del __metaclass__atributo uno tiene que usar el metaclassparámetro, como en:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass
Patrick Ohly
fuente
0

La metaprogramación es divertida, pero puede ponerse en camino. La mayoría de las soluciones aquí dificultan:

  • lanzar selectivamente una prueba
  • apunte de nuevo al código dado el nombre de la prueba

Entonces, mi primera sugerencia es seguir la ruta simple / explícita (funciona con cualquier corredor de prueba):

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

Como no deberíamos repetirnos, mi segunda sugerencia se basa en la respuesta de @ Javier: adoptar las pruebas basadas en propiedades. Biblioteca de hipótesis:

  • es "más implacablemente desviado sobre la generación de casos de prueba que nosotros, simples humanos"
  • proporcionará ejemplos de conteo simples
  • funciona con cualquier corredor de prueba
  • tiene muchas más características interesantes (estadísticas, resultados de pruebas adicionales, ...)

    clase TestSequence (unittest.TestCase):

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

Para probar sus ejemplos específicos, simplemente agregue:

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

Para ejecutar solo un ejemplo en particular, puede comentar los otros ejemplos (el ejemplo proporcionado se ejecutará primero). Es posible que desee utilizar @given(st.nothing()). Otra opción es reemplazar todo el bloque por:

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

Ok, no tienes nombres de prueba distintos. Pero tal vez solo necesites:

  • Un nombre descriptivo de la propiedad bajo prueba.
  • qué entrada lleva al fracaso (ejemplo de falsificación).

Ejemplo más divertido

YvesgereY
fuente
0

Súper tarde a la fiesta, pero tuve problemas para hacer que esto funcione setUpClass .

Aquí hay una versión de la respuesta de @ Javier que da setUpClassacceso a los atributos asignados dinámicamente.

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

Salidas

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

OK
hhquark
fuente
0

Solo para lanzar otra solución en la mezcla;)

Esto es efectivamente lo mismo parameterizedque se mencionó anteriormente, pero específico para 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

Ejemplo de uso:

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

Además de usar setattr, podemos usar load_tests desde python 3.2. Consulte la publicación del 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()
pptime
fuente
-1

Lo siguiente es mi solución. Encuentro esto útil cuando: 1. Debería funcionar para unittest. Testcase y unittest discover 2. Tener un conjunto de pruebas para ejecutar para diferentes configuraciones de parámetros. 3. Muy simple sin dependencia de otros paquetes de importación 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
S.Arora
fuente
Esto no responde la pregunta, que se trata de generar pruebas sobre la marcha.
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)
thangaraj1980
fuente
parece que perdiste el formato allí. es realmente difícil de leer tal como está
Arturo