Prueba unitaria de Python con base y subclase

Actualmente tengo algunas pruebas unitarias que comparten un conjunto común de pruebas. Aquí hay un ejemplo:

import unittest class BaseTest(unittest.TestCase): def testCommon(self): print 'Calling BaseTest:testCommon' value = 5 self.assertEquals(value, 5) class SubTest1(BaseTest): def testSub1(self): print 'Calling SubTest1:testSub1' sub = 3 self.assertEquals(sub, 3) class SubTest2(BaseTest): def testSub2(self): print 'Calling SubTest2:testSub2' sub = 4 self.assertEquals(sub, 4) if __name__ == '__main__': unittest.main() 

La salida de lo anterior es:

 Calling BaseTest:testCommon .Calling BaseTest:testCommon .Calling SubTest1:testSub1 .Calling BaseTest:testCommon .Calling SubTest2:testSub2 . ---------------------------------------------------------------------- Ran 5 tests in 0.000s OK 

¿Hay alguna forma de volver a escribir lo anterior para que no se testCommon primer testCommon ?

EDITAR: En lugar de ejecutar las 5 pruebas anteriores, quiero que solo ejecute 4 pruebas, 2 de la SubTest1 y otras 2 de la SubTest2. Parece que Python unittest está ejecutando el BaseTest original por sí solo y necesito un mecanismo para evitar que eso suceda.

Utilice la herencia múltiple, por lo que su clase con pruebas comunes no hereda de TestCase.

 import unittest class CommonTests(object): def testCommon(self): print 'Calling BaseTest:testCommon' value = 5 self.assertEquals(value, 5) class SubTest1(unittest.TestCase, CommonTests): def testSub1(self): print 'Calling SubTest1:testSub1' sub = 3 self.assertEquals(sub, 3) class SubTest2(unittest.TestCase, CommonTests): def testSub2(self): print 'Calling SubTest2:testSub2' sub = 4 self.assertEquals(sub, 4) if __name__ == '__main__': unittest.main() 

No uses herencia múltiple, te morderá más tarde .

En su lugar, simplemente puede mover su clase base al módulo separado o envolverlo con la clase en blanco:

 import unittest class BaseTestCases: class BaseTest(unittest.TestCase): def testCommon(self): print 'Calling BaseTest:testCommon' value = 5 self.assertEquals(value, 5) class SubTest1(BaseTestCases.BaseTest): def testSub1(self): print 'Calling SubTest1:testSub1' sub = 3 self.assertEquals(sub, 3) class SubTest2(BaseTestCases.BaseTest): def testSub2(self): print 'Calling SubTest2:testSub2' sub = 4 self.assertEquals(sub, 4) if __name__ == '__main__': unittest.main() 

La salida:

 Calling BaseTest:testCommon .Calling SubTest1:testSub1 .Calling BaseTest:testCommon .Calling SubTest2:testSub2 . ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK 

Puedes resolver este problema con un solo comando:

 del(BaseTest) 

Entonces el código se vería así:

 import unittest class BaseTest(unittest.TestCase): def testCommon(self): print 'Calling BaseTest:testCommon' value = 5 self.assertEquals(value, 5) class SubTest1(BaseTest): def testSub1(self): print 'Calling SubTest1:testSub1' sub = 3 self.assertEquals(sub, 3) class SubTest2(BaseTest): def testSub2(self): print 'Calling SubTest2:testSub2' sub = 4 self.assertEquals(sub, 4) del(BaseTest) if __name__ == '__main__': unittest.main() 

La respuesta de Matthew Marshall es excelente, pero requiere que herede de dos clases en cada uno de sus casos de prueba, que es propenso a errores. En su lugar, uso esto (python> = 2.7):

 class BaseTest(unittest.TestCase): @classmethod def setUpClass(cls): if cls is BaseTest: raise unittest.SkipTest("Skip BaseTest tests, it's a base class") super(BaseTest, cls).setUpClass() 

¿Qué estás intentando lograr? Si tiene un código de prueba común (aserciones, pruebas de plantilla, etc.), colóquelos en métodos que no tengan el prefijo de test para que unittest no los cargue.

 import unittest class CommonTests(unittest.TestCase): def common_assertion(self, foo, bar, baz): # whatever common code self.assertEqual(foo(bar), baz) class BaseTest(CommonTests): def testCommon(self): print 'Calling BaseTest:testCommon' value = 5 self.assertEquals(value, 5) class SubTest1(CommonTests): def testSub1(self): print 'Calling SubTest1:testSub1' sub = 3 self.assertEquals(sub, 3) class SubTest2(CommonTests): def testSub2(self): print 'Calling SubTest2:testSub2' sub = 4 self.assertEquals(sub, 4) if __name__ == '__main__': unittest.main() 

La respuesta de Matthew es la que necesitaba usar ya que todavía estoy en 2.5. Pero a partir de la versión 2.7 puede usar el decorador @ unittest.skip () en cualquier método de prueba que desee omitir.

http://docs.python.org/library/unittest.html#skipping-tests-and-expected-failures

Tendrá que implementar su propio decorador de saltos para verificar el tipo de base. No he usado esta función antes, pero desde la parte superior de mi cabeza puedes usar BaseTest como un tipo de marcador para condicionar el salto:

 def skipBaseTest(obj): if type(obj) is BaseTest: return unittest.skip("BaseTest tests skipped") return lambda func: func 

Una forma en que he pensado resolver esto es ocultando los métodos de prueba si se utiliza la clase base. De esta manera, las pruebas no se omiten, por lo que los resultados de las pruebas pueden ser verdes en lugar de amarillos en muchas herramientas de informes de pruebas.

En comparación con el método mixin, ide como PyCharm no se quejará de que faltan métodos de prueba de unidad en la clase base.

Si una clase base se hereda de esta clase, deberá reemplazar los métodos setUpClass y tearDownClass .

 class BaseTest(unittest.TestCase): @classmethod def setUpClass(cls): cls._test_methods = [] if cls is BaseTest: for name in dir(cls): if name.startswith('test') and callable(getattr(cls, name)): cls._test_methods.append((name, getattr(cls, name))) setattr(cls, name, lambda self: None) @classmethod def tearDownClass(cls): if cls is BaseTest: for name, method in cls._test_methods: setattr(cls, name, method) cls._test_methods = [] 

Otra opción es no ejecutar.

 unittest.main() 

En lugar de eso puedes usar

 suite = unittest.TestLoader().loadTestsFromTestCase(TestClass) unittest.TextTestRunner(verbosity=2).run(suite) 

Entonces solo ejecutas las pruebas en la clase TestClass

Hice lo mismo que @Vladim P. ( https://stackoverflow.com/a/25695512/2451329 ) pero ligeramente modificado:

 import unittest2 from some_module import func1, func2 def make_base_class(func): class Base(unittest2.TestCase): def test_common1(self): print("in test_common1") self.assertTrue(func()) def test_common2(self): print("in test_common1") self.assertFalse(func(42)) return Base class A(make_base_class(func1)): pass class B(make_base_class(func2)): def test_func2_with_no_arg_return_bar(self): self.assertEqual("bar", func2()) 

y ahí vamos.

Simplemente cambie el nombre del método testCommon a otra cosa. Unittest (generalmente) se salta cualquier cosa que no tenga “prueba”.

Rápido y sencillo

  import unittest class BaseTest(unittest.TestCase): def methodCommon(self): print 'Calling BaseTest:testCommon' value = 5 self.assertEquals(value, 5) class SubTest1(BaseTest): def testSub1(self): print 'Calling SubTest1:testSub1' sub = 3 self.assertEquals(sub, 3) class SubTest2(BaseTest): def testSub2(self): print 'Calling SubTest2:testSub2' sub = 4 self.assertEquals(sub, 4) if __name__ == '__main__': unittest.main()` 

Así que este es un tipo de hilo viejo, pero encontré este problema hoy y pensé en mi propio truco. Utiliza un decorador que realiza los valores de las funciones Ninguna cuando se accede a través de la clase base. No debe preocuparse por la configuración y la clase de configuración porque si la clase de base no tiene pruebas, no se ejecutarán.

 import types import unittest class FunctionValueOverride(object): def __init__(self, cls, default, override=None): self.cls = cls self.default = default self.override = override def __get__(self, obj, klass): if klass == self.cls: return self.override else: if obj: return types.MethodType(self.default, obj) else: return self.default def fixture(cls): for t in vars(cls): if not callable(getattr(cls, t)) or t[:4] != "test": continue setattr(cls, t, FunctionValueOverride(cls, getattr(cls, t))) return cls @fixture class BaseTest(unittest.TestCase): def testCommon(self): print('Calling BaseTest:testCommon') value = 5 self.assertEqual(value, 5) class SubTest1(BaseTest): def testSub1(self): print('Calling SubTest1:testSub1') sub = 3 self.assertEqual(sub, 3) class SubTest2(BaseTest): def testSub2(self): print('Calling SubTest2:testSub2') sub = 4 self.assertEqual(sub, 4) if __name__ == '__main__': unittest.main() 

Puede agregar __test_ = False en la clase BaseTest, pero si lo agrega, tenga en cuenta que debe agregar __test__ = True en las clases derivadas para poder ejecutar pruebas.

 import unittest class BaseTest(unittest.TestCase): __test__ = False def testCommon(self): print 'Calling BaseTest:testCommon' value = 5 self.assertEquals(value, 5) class SubTest1(BaseTest): __test__ = True def testSub1(self): print 'Calling SubTest1:testSub1' sub = 3 self.assertEquals(sub, 3) class SubTest2(BaseTest): __test__ = True def testSub2(self): print 'Calling SubTest2:testSub2' sub = 4 self.assertEquals(sub, 4) if __name__ == '__main__': unittest.main() 

A partir de Python 3.2, puede agregar una función test_loader a un módulo para controlar qué pruebas (si las hay) encuentra el mecanismo de descubrimiento de pruebas.

Por ejemplo, lo siguiente solo cargará los casos de prueba SubTest1 y SubTest2 del póster original, ignorando la Base :

 def load_tests(loader, standard_tests, pattern): suite = TestSuite() suite.addTests([SubTest1, SubTest2]) return suite 

Debería ser posible iterar sobre las pruebas standard_tests (un TestSuite contiene las pruebas que encontró el cargador predeterminado) y copiar todo menos la Base a la suite , pero la naturaleza anidada de TestSuite.__iter__ hace mucho más complicado.

Cambie el nombre del método BaseTest a setUp:

 class BaseTest(unittest.TestCase): def setUp(self): print 'Calling BaseTest:testCommon' value = 5 self.assertEquals(value, 5) class SubTest1(BaseTest): def testSub1(self): print 'Calling SubTest1:testSub1' sub = 3 self.assertEquals(sub, 3) class SubTest2(BaseTest): def testSub2(self): print 'Calling SubTest2:testSub2' sub = 4 self.assertEquals(sub, 4) 

Salida:

Ran 2 pruebas en 0.000s

Calling BaseTest: testCommon Calling
SubTest1: testSub1 Calling
Prueba de base: TestCommon Calling
SubTest2: testSub2

De la documentación :

TestCase.setUp ()
Método llamado para preparar el dispositivo de prueba. Esto se llama inmediatamente antes de llamar al método de prueba; Cualquier excepción provocada por este método se considerará un error en lugar de un error de prueba. La implementación por defecto no hace nada.