He leído algunos consejos contradictorios sobre el uso de assert en el método setUp de una prueba unitaria de Python. No puedo ver el daño en reprobar una prueba si falla una condición previa en la que se basa la prueba.

Por ejemplo:

import unittest

class MyProcessor():
    """
    This is the class under test
    """

    def __init__(self):
        pass

    def ProcessData(self, content):
        return ['some','processed','data','from','content'] # Imagine this could actually pass

class Test_test2(unittest.TestCase):

    def LoadContentFromTestFile(self):
        return None # Imagine this is actually doing something that could pass.

    def setUp(self):
        self.content = self.LoadContentFromTestFile()
        self.assertIsNotNone(self.content, "Failed to load test data")
        self.processor = MyProcessor()

    def test_ProcessData(self):
        results = self.processor.ProcessData(self.content)
        self.assertGreater(results, 0, "No results returned")

if __name__ == '__main__':
    unittest.main()

Esto parece algo razonable para mí, es decir, asegurarse de que la prueba pueda ejecutarse. Cuando esto falla debido a la condición de configuración, obtenemos:

F
======================================================================
FAIL: test_ProcessData (__main__.Test_test2)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Projects\Experiments\test2.py", line 21, in setUp
    self.assertIsNotNone(self.content, "Failed to load test data")
AssertionError: unexpectedly None : Failed to load test data

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)
8
Jon Cage 10 may. 2016 a las 12:24

5 respuestas

La mejor respuesta

El propósito de setUp es reducir Código de repeticiones que se crea entre las pruebas en la prueba clase durante la fase de Arreglos.

En la fase Organizar, usted: configura todo lo necesario para ejecutar el código probado. Esto incluye cualquier inicialización de dependencias, simulaciones y datos necesarios para que se ejecute la prueba.

Según los párrafos anteriores, no debe afirmar nada en su método setUp.

Así como se mencionó anteriormente; Si no puede crear la condición previa de la prueba, entonces su prueba está rota. Para evitar situaciones como esta, Roy Osherove escribió un gran libro llamado: The Art Of Unit Testing (Para una revelación completa, Lior Friedman (era el jefe de Roy) es un amigo mío y trabajé estrechamente con ellos durante más de 2 años, así que estoy un poco parcial ...)

Básicamente, solo hay algunas razones para tener una interacción con recursos externos durante la fase Organizar (o con cosas que pueden causar una excepción), la mayoría de ellas (si no todas) están relacionadas en las pruebas de integración.

De vuelta a tu ejemplo; Hay un patrón para estructurar las pruebas en las que necesita cargar un recurso externo (para todas / la mayoría de ellas). Solo una nota al margen; antes de decidir aplicar este patrón, asegúrese de que no pueda tener este contenido como un recurso estático en la clase de su UT, si otras clases de prueba necesitan usar este recurso, extraiga este recurso en un módulo.

El siguiente patrón disminuye la posibilidad de falla, ya que tiene menos llamadas al recurso externo:

class TestClass(unittest.TestCase):

    def setUpClass(self):
        # since external resources such as other servers can provide a bad content
        # you can verify that the content is valid
        # then prevent from the tests to run  
        # however, in most cases you shouldn't.
        self.externalResourceContent = loadContentFromExternalResource()


    def setUp(self):
        self.content = self.copyContentForTest()

Pros:

  1. menos posibilidades de fracaso
  2. evitar el comportamiento inconsistente (1. algo / uno ha editado el recurso externo. 2. no pudo cargar el recurso externo en algunas de sus pruebas)
  3. ejecución más rápida

Contras:

  1. el código es más complejo
10
Old Fox 22 may. 2016 a las 13:23

Del Documentación de la biblioteca estándar de Python:

"Si el método setUp () genera una excepción mientras se ejecuta la prueba, el marco considerará que la prueba ha sufrido un error, y el método runTest () no se ejecutará. Si setUp () tuvo éxito, el El método tearDown () se ejecutará tanto si runTest () tuvo éxito como si no. Tal un entorno de trabajo para el código de prueba se llama accesorio ".

Una excepción de aserción en el método setUp () sería considerada como un error por el marco de prueba de unidad. La prueba no se ejecutará.

3
user3271619 20 may. 2016 a las 15:41

Hay una razón por la que desea evitar aserciones en un setUp(). Si la configuración falla, su tearDown no se ejecutará.

Si configura un conjunto de registros de la base de datos, por ejemplo, y su desmontaje elimina estos registros, estos registros no se eliminarán.

Con este fragmento:

import unittest

class Test_test2(unittest.TestCase):

    def setUp(self):
        print 'setup'
        assert False

    def test_ProcessData(self):
        print 'testing'

    def tearDown(self):
        print 'teardown'

if __name__ == '__main__':
    unittest.main()

Solo ejecutas la setUp():

$ python t.py 
setup
E
======================================================================
ERROR: test_ProcessData (__main__.Test_test2)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "t.py", line 7, in setUp
    assert False
AssertionError

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)
2
Philippe Ombredanne 23 may. 2016 a las 07:56

setUp no es para afirmar condiciones previas, sino crearlas . Si su prueba no puede crear el accesorio necesario, está roto, no falla.

5
user5547025user5547025 10 may. 2016 a las 09:27

Aquí no hay una respuesta correcta o incorrecta, depende de lo que esté probando y de lo costoso que sea configurar sus pruebas. Algunas pruebas son demasiado peligrosas para permitir intentos de ejecución si los datos no son los esperados, algunos necesitan trabajar con esos datos.

Puede usar aserciones en la configuración si necesita verificar entre pruebas para condiciones particulares, esto puede ayudar a reducir el código repetido en sus pruebas. Sin embargo, también hace que mover los métodos de prueba entre clases o archivos sea un poco más complicado, ya que dependerán de tener una configuración equivalente. También puede empujar los límites de la complejidad para los probadores menos expertos en código.

Es un poco más limpio tener una prueba que verifique estas condiciones de inicio individualmente y la ejecute primero, puede que no sean necesarias entre cada prueba. Si lo define como test_01_check_preconditions, se realizará antes que cualquiera de los otros métodos de prueba, incluso si el resto es aleatorio. También puede usar decoradores unittest2.skip para ciertas condiciones.

Un mejor enfoque es usar addCleanup para garantizar que se restablezca el estado, la ventaja aquí es que incluso si la prueba falla, todavía se ejecuta, también puede hacer que la limpieza sea más consciente de la situación específica a medida que la define en el contexto de su método de prueba.

Tampoco hay nada que le impida definir métodos para hacer comprobaciones comunes en la clase unittest y llamarlos en setUp o en test_methods, esto puede ayudar a mantener la complejidad encerrada en áreas definidas y administradas.

Tampoco tenga la tentación de subclasificar unittest2 más allá de una simple definición de prueba, he visto a personas que intentan hacer eso para simplificar las pruebas e introducir un comportamiento totalmente inesperado.

Supongo que lo mejor es llevarlo a casa, si lo hace sabe por qué quiere usarlo y se asegura de documentar sus razones, es probable que esté bien, si no está seguro, busque la opción más fácil de entender porque las pruebas son inútiles si son No es fácil de entender.

2
Amias 18 may. 2016 a las 12:10