¿Puede la prueba de la unidad de Python probar en paralelo, como la nariz?

El marco de pruebas NOSE de Python tiene el concepto de ejecutar varias pruebas en paralelo .

El propósito de esto no es probar la concurrencia en el código, sino hacer pruebas para el código que “no tiene efectos secundarios, no hay problemas de pedido y no hay dependencias externas” que se ejecuten más rápido. La ganancia de rendimiento proviene de la espera simultánea de E / S cuando están accediendo a diferentes dispositivos, mejor uso de múltiples CPU / núcleos y ejecutando sentencias time.sleep () en paralelo.

Creo que lo mismo se podría hacer con el marco de pruebas de prueba de unidad de Python, al tener un complemento Run Runner.

¿Alguien ha tenido alguna experiencia con semejante bestia, y puede hacer alguna recomendación?

El testrunner incorporado de Python unittest no ejecuta pruebas en paralelo. Probablemente no sería demasiado difícil escribir uno que lo hiciera. He escrito el mío solo para reformatear la salida y el tiempo de cada prueba. Eso tomó tal vez la mitad de un día. Creo que puedes intercambiar la clase TestSuite que se usa con una derivada que usa multiproceso sin muchos problemas.

El paquete testtools es una extensión de unittest que admite la ejecución simultánea de pruebas. Se puede usar con las clases de prueba anteriores que heredan unittest.TestCase .

Por ejemplo:

 import unittest import testtools class MyTester(unittest.TestCase): # Tests... suite = unittest.TestLoader().loadTestsFromTestCase(MyTester) concurrent_suite = testtools.ConcurrentStreamTestSuite(lambda: ((case, None) for case in suite)) concurrent_suite.run(testtools.StreamResult()) 

Utilice pytest-xdist , si desea ejecutar en paralelo.

El complemento pytest-xdist amplía py.test con algunos modos de ejecución de prueba únicos:

  • paralelización de ejecución de prueba: si tiene múltiples CPU o hosts, puede usarlos para una ejecución de prueba combinada. Esto permite acelerar el desarrollo o utilizar recursos especiales de máquinas remotas.

[…]

Más información: blog de Rohan Dunham

Otra opción que podría ser más fácil, si no tiene tantos casos de prueba y no son dependientes, es iniciar cada caso de prueba manualmente en un proceso separado.

Por ejemplo, abra un par de sesiones tmux y luego inicie un caso de prueba en cada sesión usando algo como:

 python -m unittest -v MyTestModule.MyTestClass.test_n 

Si solo necesita el soporte de Python3, considere usar mi unidad rápida .

Acabo de cambiar algunos códigos de unittest, haciendo que el caso de prueba se ejecute como corrutinas.

Realmente me salvó el tiempo.

Acabo de terminar la semana pasada, y es posible que no esté probando lo suficiente, si ocurre algún error, hágamelo saber para que pueda mejorarlo, ¡gracias!

Si esto es lo que hiciste inicialmente

 runner = unittest.TextTestRunner() runner.run(suite) 

—————————————–

reemplazarlo con

 from concurrencytest import ConcurrentTestSuite, fork_for_tests concurrent_suite = ConcurrentTestSuite(suite, fork_for_tests(4)) runner.run(concurrent_suite) 

Puede anular unittest.TestSuite e implementar algún paradigma de concurrencia. Luego, usas tu clase personalizada TestSuite como una unittest TestSuite normal. En el siguiente ejemplo, implemento mi clase personalizada TestSuite usando async :

 import unittest import asyncio class CustomTestSuite(unittest.TestSuite): def run(self, result, debug=False): """ We override the 'run' routine to support the execution of unittest in parallel :param result: :param debug: :return: """ topLevel = False if getattr(result, '_testRunEntered', False) is False: result._testRunEntered = topLevel = True asyncMethod = [] loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) for index, test in enumerate(self): asyncMethod.append(self.startRunCase(index, test, result)) if asyncMethod: loop.run_until_complete(asyncio.wait(asyncMethod)) loop.close() if topLevel: self._tearDownPreviousClass(None, result) self._handleModuleTearDown(result) result._testRunEntered = False return result async def startRunCase(self, index, test, result): def _isnotsuite(test): "A crude way to tell apart testcases and suites with duck-typing" try: iter(test) except TypeError: return True return False loop = asyncio.get_event_loop() if result.shouldStop: return False if _isnotsuite(test): self._tearDownPreviousClass(test, result) self._handleModuleFixture(test, result) self._handleClassSetUp(test, result) result._previousTestClass = test.__class__ if (getattr(test.__class__, '_classSetupFailed', False) or getattr(result, '_moduleSetUpFailed', False)): return True await loop.run_in_executor(None, test, result) if self._cleanup: self._removeTestAtIndex(index) class TestStringMethods(unittest.TestCase): def test_upper(self): self.assertEqual('foo'.upper(), 'FOO') def test_isupper(self): self.assertTrue('FOO'.isupper()) self.assertFalse('Foo'.isupper()) def test_split(self): s = 'hello world' self.assertEqual(s.split(), ['hello', 'world']) # check that s.split fails when the separator is not a string with self.assertRaises(TypeError): s.split(2) if __name__ == '__main__': suite = CustomTestSuite() suite.addTest(TestStringMethods('test_upper')) suite.addTest(TestStringMethods('test_isupper')) suite.addTest(TestStringMethods('test_split')) unittest.TextTestRunner(verbosity=2).run(suite) 

En main , solo construyo mi clase personalizada TestSuite , agrego todos los casos de prueba y finalmente lo ejecuto.