Continuar en la prueba unitaria de Python cuando falla una afirmación

84

EDITAR: cambió a un mejor ejemplo y aclaró por qué esto es un problema real.

Me gustaría escribir pruebas unitarias en Python que continúen ejecutándose cuando falla una afirmación, para poder ver múltiples fallas en una sola prueba. Por ejemplo:

class Car(object):
  def __init__(self, make, model):
    self.make = make
    self.model = make  # Copy and paste error: should be model.
    self.has_seats = True
    self.wheel_count = 3  # Typo: should be 4.

class CarTest(unittest.TestCase):
  def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    self.assertEqual(car.make, make)
    self.assertEqual(car.model, model)  # Failure!
    self.assertTrue(car.has_seats)
    self.assertEqual(car.wheel_count, 4)  # Failure!

Aquí, el propósito de la prueba es asegurarse de que Car's __init__configure sus campos correctamente. Podría dividirlo en cuatro métodos (y eso suele ser una gran idea), pero en este caso creo que es más legible mantenerlo como un método único que prueba un concepto único ("el objeto se inicializa correctamente").

Si asumimos que es mejor no dividir el método, entonces tengo un nuevo problema: no puedo ver todos los errores a la vez. Cuando soluciono el modelerror y vuelvo a ejecutar la prueba, wheel_countaparece el error. Me ahorraría tiempo ver ambos errores cuando ejecute la prueba por primera vez.

A modo de comparación, el marco de pruebas unitarias C ++ de Google distingue entreEXPECT_* afirmaciones no fatales y ASSERT_*afirmaciones fatales :

Las afirmaciones vienen en pares que prueban lo mismo pero tienen diferentes efectos en la función actual. Las versiones ASSERT_ * generan fallas fatales cuando fallan y abortan la función actual. Las versiones EXPECT_ * generan fallas no fatales, que no abortan la función actual. Por lo general, se prefieren EXPECT_ *, ya que permiten informar más de una falla en una prueba. Sin embargo, debe usar ASSERT_ * si no tiene sentido continuar cuando falla la afirmación en cuestión.

¿Hay alguna manera de obtener un EXPECT_*comportamiento similar en Python unittest? Si no está dentro unittest, ¿hay otro marco de prueba unitario de Python que admita este comportamiento?


Por cierto, tenía curiosidad acerca de cuántas pruebas de la vida real podrían beneficiarse de afirmaciones no fatales, así que miré algunos ejemplos de código (editado 2014-08-19 para usar el código de búsqueda en lugar de Google Code Search, RIP). De los 10 resultados seleccionados al azar de la primera página, todos contenían pruebas que realizaban múltiples afirmaciones independientes en el mismo método de prueba. Todos se beneficiarían de afirmaciones no fatales.

Bruce Christensen
fuente
2
lo acabaste haciendo? Estoy interesado en este tema (por razones completamente diferentes que me encantaría discutir en un lugar más espacioso que un comentario) y me gustaría conocer tu experiencia. Por cierto, el enlace "ejemplos de código" termina con "Lamentablemente, este servicio ha sido cerrado", así que si tienes una versión en caché de eso, me interesaría verla también.
Davide
Para referencia futura, creo que esta es la búsqueda equivalente en el sistema actual, pero los resultados ya no son los descritos anteriormente.
ZAD-Man
2
@Davide, no terminé haciendo nada. El enfoque de "hacer una sola afirmación por método" me parece demasiado rígidamente dogmático, pero la única solución viable (y mantenible) parece ser la sugerencia de Anthony de "atrapar y agregar". Sin embargo, eso es demasiado feo para mí, así que simplemente me quedé con múltiples afirmaciones por método, y tendré que vivir con la ejecución de pruebas más veces de las necesarias para encontrar todas las fallas.
Bruce Christensen
El marco de prueba de Python llamado PyTest es bastante intuitivo y, de forma predeterminada, muestra todas las fallas de aserción . Eso podría ser una solución al problema al que se enfrenta.
Surya Shekhar Chakraborty

Respuestas:

9

Lo que probablemente querrá hacer es derivar, unittest.TestCaseya que esa es la clase que lanza cuando falla una aserción. Tendrá que rediseñar su TestCasepara no lanzar (tal vez mantenga una lista de fallas). Rediseñar las cosas puede causar otros problemas que tendrías que resolver. Por ejemplo, puede terminar necesitando derivar TestSuitepara realizar cambios en apoyo de los cambios realizados en su TestCase.

dietbuddha
fuente
1
Pensé que probablemente esta sería la respuesta final, pero quería cubrir mis bases y ver si me faltaba algo. ¡Gracias!
Bruce Christensen
4
Yo diría que es una exageración anularlo TestCasepor el bien de implementar afirmaciones suaves; son especialmente fáciles de hacer en Python: simplemente capture todos sus AssertionErrors (tal vez en un bucle simple) y guárdelos en una lista o un conjunto , luego reprobarlos todos a la vez. Consulte la respuesta de @Anthony Batchelor para obtener más detalles.
dcsordas
2
@dscordas Depende de si se trata de una prueba única o si desea tener esta capacidad para la mayoría de las pruebas.
dietbuddha
43

Otra forma de tener aserciones no fatales es capturar la excepción de aserción y almacenar las excepciones en una lista. Luego afirme que esa lista está vacía como parte del tearDown.

import unittest

class Car(object):
  def __init__(self, make, model):
    self.make = make
    self.model = make  # Copy and paste error: should be model.
    self.has_seats = True
    self.wheel_count = 3  # Typo: should be 4.

class CarTest(unittest.TestCase):
  def setUp(self):
    self.verificationErrors = []

  def tearDown(self):
    self.assertEqual([], self.verificationErrors)

  def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    try: self.assertEqual(car.make, make)
    except AssertionError, e: self.verificationErrors.append(str(e))
    try: self.assertEqual(car.model, model)  # Failure!
    except AssertionError, e: self.verificationErrors.append(str(e))
    try: self.assertTrue(car.has_seats)
    except AssertionError, e: self.verificationErrors.append(str(e))
    try: self.assertEqual(car.wheel_count, 4)  # Failure!
    except AssertionError, e: self.verificationErrors.append(str(e))

if __name__ == "__main__":
    unittest.main()
Anthony Batchelor
fuente
2
Estoy bastante seguro de que estoy de acuerdo contigo. Así es como Selenium trata los errores de verificación en el backend de Python.
Anthony Batchelor
Sí, el problema con esta solución es que todas las afirmaciones se cuentan como errores (no fraudulentos) y la forma de representar los errores no es realmente utilizable. De todos modos es una forma y la función de hacer que se puede mejorar fácilemte
Emarine
Estoy usando esta solución en combinación con la respuesta de dietbudda anulando todas las afirmaciones unittest.TestCasecon bloques try / except.
2015
Para patrones de prueba complejos, esta es la mejor solución para vencer el error de prueba unitaria, pero hace que la prueba se vea bastante fea con todos los intentos / excepciones. es un intercambio entre muchas pruebas y una sola prueba compleja. En su lugar, comencé a devolver un dict de error. Entonces puedo probar todo el patrón de prueba en una prueba y mantener la legibilidad para mis compañeros desarrolladores casuales de Python.
MortenB
Esto es extremadamente inteligente, así que me quito el sombrero.
courtimas
30

Una opción es hacer valer todos los valores a la vez como una tupla.

Por ejemplo:

class CarTest(unittest.TestCase):
  def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    self.assertEqual(
            (car.make, car.model, car.has_seats, car.wheel_count),
            (make, model, True, 4))

El resultado de estas pruebas sería:

======================================================================
FAIL: test_init (test.CarTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\temp\py_mult_assert\test.py", line 17, in test_init
    (make, model, True, 4))
AssertionError: Tuples differ: ('Ford', 'Ford', True, 3) != ('Ford', 'Model T', True, 4)

First differing element 1:
Ford
Model T

- ('Ford', 'Ford', True, 3)
?           ^ -          ^

+ ('Ford', 'Model T', True, 4)
?           ^  ++++         ^

Esto muestra que tanto el modelo como el número de ruedas son incorrectos.

hwiechers
fuente
Esto es inteligente. La mejor solución que he encontrado hasta ahora.
Chen Ni
7

Se considera un anti-patrón tener múltiples afirmaciones en una sola prueba unitaria. Se espera que una prueba de una sola unidad pruebe solo una cosa. Quizás esté probando demasiado. Considere dividir esta prueba en varias pruebas. De esta manera, puede nombrar cada prueba correctamente.

A veces, sin embargo, está bien comprobar varias cosas al mismo tiempo. Por ejemplo, cuando afirma propiedades del mismo objeto. En ese caso, de hecho está afirmando si ese objeto es correcto. Una forma de hacer esto es escribir un método auxiliar personalizado que sepa cómo hacer valer en ese objeto. Puede escribir ese método de tal manera que muestre todas las propiedades defectuosas o, por ejemplo, muestre el estado completo del objeto esperado y el estado completo del objeto real cuando falla una aserción.

Steven
fuente
1
@Bruce: Una aserción debe fallar o tener éxito. Nunca algo intermedio. La prueba debe ser confiable, legible y fácil de mantener. Una afirmación incorrecta que no falla la prueba es una mala idea. Hace que sus pruebas sean demasiado complicadas (lo que reduce la legibilidad y la capacidad de mantenimiento) y tener pruebas que 'pueden fallar' hace que sea fácil ignorarlas, lo que significa que no son confiables.
Steven
8
cualquier motivo por el que el resto de la prueba no se pueda ejecutar y aún así sea fatal. Creo que podría retrasar el regreso de la falla en algún lugar a favor de agregar todas las posibles fallas que puedan ocurrir.
dietbuddha
5
Creo que ambos estamos diciendo lo mismo. Quiero que cada afirmación fallida provoque que la prueba falle; es solo que quiero que la falla ocurra cuando el método de prueba regrese, en lugar de inmediatamente cuando se prueba la aserción, como mencionó @dietbuddha. Esto permitiría probar todas las afirmaciones del método, de modo que pueda ver (y corregir) todas las fallas de una sola vez. La prueba sigue siendo confiable, legible y mantenible (incluso más, en realidad).
Bruce Christensen
10
No está diciendo que la prueba no debería fallar cuando presionas la afirmación, está diciendo que la falla no debería impedir las otras comprobaciones. Por ejemplo, en este momento estoy probando que los directorios particulares sean usuario, grupo y otros modificables. Cada uno es una afirmación separada. Sería útil saber a partir del resultado de la prueba que los tres casos están fallando, por lo que puedo solucionarlos con una llamada chmod, en lugar de obtener "La ruta no es escribible por el usuario", tener que ejecutar la prueba nuevamente para obtener "La ruta es no escribible en grupo "y así sucesivamente. Aunque supongo que acabo de argumentar que deberían ser pruebas separadas ...
Tim Keating
8
El hecho de que la biblioteca se llame unittest, no significa que la prueba sea una prueba unitaria aislada. El módulo unittest, así como pytest y nose y otros, funcionan muy bien para pruebas del sistema, pruebas de integración, etc. Con la única salvedad de que solo puedes fallar una vez. Realmente es molesto. Realmente me gustaría ver que todas las funciones de aserción agreguen un parámetro que le permita continuar con una falla, o una duplicación de las funciones de aserción llamadas esperaBlah, que hacen tal cosa. Entonces sería mucho más fácil escribir pruebas funcionales más grandes con unittest.
Okken
7

Desde Python 3.4 también puede usar subpruebas :

def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    with self.subTest(msg='Car.make check'):
        self.assertEqual(car.make, make)
    with self.subTest(msg='Car.model check'):
        self.assertEqual(car.model, model)
    with self.subTest(msg='Car.has_seats check'):
        self.assertTrue(car.has_seats)
    with self.subTest(msg='Car.wheel_count check'):
        self.assertEqual(car.wheel_count, 4)

(el msgparámetro se usa para determinar más fácilmente qué prueba falló).

Salida:

======================================================================
FAIL: test_init (__main__.CarTest) [Car.model check]
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 23, in test_init
    self.assertEqual(car.model, model)
AssertionError: 'Ford' != 'Model T'
- Ford
+ Model T


======================================================================
FAIL: test_init (__main__.CarTest) [Car.wheel_count check]
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 27, in test_init
    self.assertEqual(car.wheel_count, 4)
AssertionError: 3 != 4

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=2)
Zuku
fuente
1
Esta debería ser ahora la respuesta aceptada como la más fácil de incluir en el código existente.
Michael Scott Cuthbert
5

Haga cada afirmación en un método separado.

class MathTest(unittest.TestCase):
  def test_addition1(self):
    self.assertEqual(1 + 0, 1)

  def test_addition2(self):
    self.assertEqual(1 + 1, 3)

  def test_addition3(self):
    self.assertEqual(1 + (-1), 0)

  def test_addition4(self):
    self.assertEqaul(-1 + (-1), -1)
Lennart Regebro
fuente
5
Me doy cuenta de que esa es una posible solución, pero no siempre es práctica. Estoy buscando algo que funcione sin dividir una prueba anteriormente cohesiva en varios métodos pequeños.
Bruce Christensen
@Bruce Christensen: Si son tan cohesivos, ¿tal vez formen una historia? Y luego se pueden convertir en prueba unitaria, lo que de hecho se continúan incluso después de un fallo.
Lennart Regebro
1
Tengo un conjunto de pruebas, algo como esto: 1. cargar datos, 2. afirmar datos cargados correctamente, 3. modificar datos, 4. afirmar que la modificación funcionó correctamente, 5. guardar datos modificados, 6. afirmar datos guardados correctamente. ¿Cómo puedo hacer eso con este método? no tiene sentido cargar los datos setup(), porque esa es una de las pruebas. Pero si pongo cada afirmación en su propia función, entonces tengo que cargar datos 3 veces, y eso es un gran desperdicio de recursos. ¿Cuál es la mejor manera de lidiar con una situación como esa?
naught101
Bueno, las pruebas que prueban una secuencia específica deben estar en el mismo método de prueba.
Lennart Regebro
4

Hay un paquete de aserción suave en PyPI llamado softestque manejará sus requisitos. Funciona recopilando las fallas, combinando excepciones y datos de seguimiento de pila, e informando todo como parte de la unittestsalida habitual .

Por ejemplo, este código:

import softest

class ExampleTest(softest.TestCase):
    def test_example(self):
        # be sure to pass the assert method object, not a call to it
        self.soft_assert(self.assertEqual, 'Worf', 'wharf', 'Klingon is not ship receptacle')
        # self.soft_assert(self.assertEqual('Worf', 'wharf', 'Klingon is not ship receptacle')) # will not work as desired
        self.soft_assert(self.assertTrue, True)
        self.soft_assert(self.assertTrue, False)

        self.assert_all()

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

... produce esta salida de consola:

======================================================================
FAIL: "test_example" (ExampleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\...\softest_test.py", line 14, in test_example
    self.assert_all()
  File "C:\...\softest\case.py", line 138, in assert_all
    self.fail(''.join(failure_output))
AssertionError: ++++ soft assert failure details follow below ++++

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
The following 2 failures were found in "test_example" (ExampleTest):
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Failure 1 ("test_example" method)
+--------------------------------------------------------------------+
Traceback (most recent call last):
  File "C:\...\softest_test.py", line 10, in test_example
    self.soft_assert(self.assertEqual, 'Worf', 'wharf', 'Klingon is not ship receptacle')
  File "C:\...\softest\case.py", line 84, in soft_assert
    assert_method(*arguments, **keywords)
  File "C:\...\Python\Python36-32\lib\unittest\case.py", line 829, in assertEqual
    assertion_func(first, second, msg=msg)
  File "C:\...\Python\Python36-32\lib\unittest\case.py", line 1203, in assertMultiLineEqual
    self.fail(self._formatMessage(msg, standardMsg))
  File "C:\...\Python\Python36-32\lib\unittest\case.py", line 670, in fail
    raise self.failureException(msg)
AssertionError: 'Worf' != 'wharf'
- Worf
+ wharf
 : Klingon is not ship receptacle

+--------------------------------------------------------------------+
Failure 2 ("test_example" method)
+--------------------------------------------------------------------+
Traceback (most recent call last):
  File "C:\...\softest_test.py", line 12, in test_example
    self.soft_assert(self.assertTrue, False)
  File "C:\...\softest\case.py", line 84, in soft_assert
    assert_method(*arguments, **keywords)
  File "C:\...\Python\Python36-32\lib\unittest\case.py", line 682, in assertTrue
    raise self.failureException(msg)
AssertionError: False is not true


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

FAILED (failures=1)

NOTA : Creé y mantengo softest.

skia.heliou
fuente
3

esperar es muy útil en gtest. Esta es la forma de Python en esencia y código:

import sys
import unittest


class TestCase(unittest.TestCase):
    def run(self, result=None):
        if result is None:
            self.result = self.defaultTestResult()
        else:
            self.result = result

        return unittest.TestCase.run(self, result)

    def expect(self, val, msg=None):
        '''
        Like TestCase.assert_, but doesn't halt the test.
        '''
        try:
            self.assert_(val, msg)
        except:
            self.result.addFailure(self, sys.exc_info())

    def expectEqual(self, first, second, msg=None):
        try:
            self.failUnlessEqual(first, second, msg)
        except:
            self.result.addFailure(self, sys.exc_info())

    expect_equal = expectEqual

    assert_equal = unittest.TestCase.assertEqual
    assert_raises = unittest.TestCase.assertRaises


test_main = unittest.main
Conocido
fuente
2

Me gustó el enfoque de @ Anthony-Batchelor para capturar la excepción AssertionError. Pero una ligera variación de este enfoque utilizando decoradores y también una forma de informar los casos de prueba con pasa / falla.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import unittest

class UTReporter(object):
    '''
    The UT Report class keeps track of tests cases
    that have been executed.
    '''
    def __init__(self):
        self.testcases = []
        print "init called"

    def add_testcase(self, testcase):
        self.testcases.append(testcase)

    def display_report(self):
        for tc in self.testcases:
            msg = "=============================" + "\n" + \
                "Name: " + tc['name'] + "\n" + \
                "Description: " + str(tc['description']) + "\n" + \
                "Status: " + tc['status'] + "\n"
            print msg

reporter = UTReporter()

def assert_capture(*args, **kwargs):
    '''
    The Decorator defines the override behavior.
    unit test functions decorated with this decorator, will ignore
    the Unittest AssertionError. Instead they will log the test case
    to the UTReporter.
    '''
    def assert_decorator(func):
        def inner(*args, **kwargs):
            tc = {}
            tc['name'] = func.__name__
            tc['description'] = func.__doc__
            try:
                func(*args, **kwargs)
                tc['status'] = 'pass'
            except AssertionError:
                tc['status'] = 'fail'
            reporter.add_testcase(tc)
        return inner
    return assert_decorator



class DecorateUt(unittest.TestCase):

    @assert_capture()
    def test_basic(self):
        x = 5
        self.assertEqual(x, 4)

    @assert_capture()
    def test_basic_2(self):
        x = 4
        self.assertEqual(x, 4)

def main():
    #unittest.main()
    suite = unittest.TestLoader().loadTestsFromTestCase(DecorateUt)
    unittest.TextTestRunner(verbosity=2).run(suite)

    reporter.display_report()


if __name__ == '__main__':
    main()

Salida de la consola:

(awsenv)$ ./decorators.py 
init called
test_basic (__main__.DecorateUt) ... ok
test_basic_2 (__main__.DecorateUt) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
=============================
Name: test_basic
Description: None
Status: fail

=============================
Name: test_basic_2
Description: None
Status: pass
Zoro_77
fuente
1

Tuve un problema con la respuesta de @Anthony Batchelor porque me habría obligado a usar try...catchdentro de mis pruebas unitarias. En cambio, encapsulé la try...catchlógica en una anulación del TestCase.assertEqualmétodo. Aquí está el código:

import unittest
import traceback

class AssertionErrorData(object):

    def __init__(self, stacktrace, message):
        super(AssertionErrorData, self).__init__()
        self.stacktrace = stacktrace
        self.message = message

class MultipleAssertionFailures(unittest.TestCase):

    def __init__(self, *args, **kwargs):
        self.verificationErrors = []
        super(MultipleAssertionFailures, self).__init__( *args, **kwargs )

    def tearDown(self):
        super(MultipleAssertionFailures, self).tearDown()

        if self.verificationErrors:
            index = 0
            errors = []

            for error in self.verificationErrors:
                index += 1
                errors.append( "%s\nAssertionError %s: %s" % ( 
                        error.stacktrace, index, error.message ) )

            self.fail( '\n\n' + "\n".join( errors ) )
            self.verificationErrors.clear()

    def assertEqual(self, goal, results, msg=None):

        try:
            super( MultipleAssertionFailures, self ).assertEqual( goal, results, msg )

        except unittest.TestCase.failureException as error:
            goodtraces = self._goodStackTraces()
            self.verificationErrors.append( 
                    AssertionErrorData( "\n".join( goodtraces[:-2] ), error ) )

    def _goodStackTraces(self):
        """
            Get only the relevant part of stacktrace.
        """
        stop = False
        found = False
        goodtraces = []

        # stacktrace = traceback.format_exc()
        # stacktrace = traceback.format_stack()
        stacktrace = traceback.extract_stack()

        # /programming/54499367/how-to-correctly-override-testcase
        for stack in stacktrace:
            filename = stack.filename

            if found and not stop and \
                    not filename.find( 'lib' ) < filename.find( 'unittest' ):
                stop = True

            if not found and filename.find( 'lib' ) < filename.find( 'unittest' ):
                found = True

            if stop and found:
                stackline = '  File "%s", line %s, in %s\n    %s' % ( 
                        stack.filename, stack.lineno, stack.name, stack.line )
                goodtraces.append( stackline )

        return goodtraces

# class DummyTestCase(unittest.TestCase):
class DummyTestCase(MultipleAssertionFailures):

    def setUp(self):
        self.maxDiff = None
        super(DummyTestCase, self).setUp()

    def tearDown(self):
        super(DummyTestCase, self).tearDown()

    def test_function_name(self):
        self.assertEqual( "var", "bar" )
        self.assertEqual( "1937", "511" )

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

Salida de resultado:

F
======================================================================
FAIL: test_function_name (__main__.DummyTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\User\Downloads\test.py", line 77, in tearDown
    super(DummyTestCase, self).tearDown()
  File "D:\User\Downloads\test.py", line 29, in tearDown
    self.fail( '\n\n' + "\n\n".join( errors ) )
AssertionError: 

  File "D:\User\Downloads\test.py", line 80, in test_function_name
    self.assertEqual( "var", "bar" )
AssertionError 1: 'var' != 'bar'
- var
? ^
+ bar
? ^
 : 

  File "D:\User\Downloads\test.py", line 81, in test_function_name
    self.assertEqual( "1937", "511" )
AssertionError 2: '1937' != '511'
- 1937
+ 511
 : 

Se pueden publicar más soluciones alternativas para la captura correcta de seguimiento de pila en ¿Cómo anular correctamente TestCase.assertEqual (), produciendo el seguimiento de pila correcto?

usuario
fuente
0

No creo que haya una forma de hacer esto con PyUnit y no me gustaría ver PyUnit extendido de esta manera.

Prefiero ceñirme a una afirmación por función de prueba ( o más específicamente afirmar un concepto por prueba ) y reescribiría test_addition()como cuatro funciones de prueba separadas. Esto brindaría información más útil sobre fallas, a saber :

.FF.
======================================================================
FAIL: test_addition_with_two_negatives (__main__.MathTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_addition.py", line 10, in test_addition_with_two_negatives
    self.assertEqual(-1 + (-1), -1)
AssertionError: -2 != -1

======================================================================
FAIL: test_addition_with_two_positives (__main__.MathTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_addition.py", line 6, in test_addition_with_two_positives
    self.assertEqual(1 + 1, 3)  # Failure!
AssertionError: 2 != 3

----------------------------------------------------------------------
Ran 4 tests in 0.000s

FAILED (failures=2)

Si decide que este enfoque no es para usted, puede encontrar útil esta respuesta .

Actualizar

Parece que está probando dos conceptos con su pregunta actualizada y los dividiría en dos pruebas unitarias. El primero es que los parámetros se almacenan en la creación de un nuevo objeto. Esto tendría dos afirmaciones, una para makey otra para model. Si el primero falla, lo que claramente debe arreglarse, si el segundo pasa o falla es irrelevante en este momento.

El segundo concepto es más cuestionable ... Está probando si se inicializan algunos valores predeterminados. ¿Por qué ? Sería más útil probar estos valores en el punto en que realmente se utilizan (y si no se utilizan, ¿por qué están ahí?).

Ambas pruebas fallan, y ambas deberían. Cuando estoy haciendo pruebas unitarias, estoy mucho más interesado en el fracaso que en el éxito, ya que ahí es donde necesito concentrarme.

FF
======================================================================
FAIL: test_creation_defaults (__main__.CarTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_car.py", line 25, in test_creation_defaults
    self.assertEqual(self.car.wheel_count, 4)  # Failure!
AssertionError: 3 != 4

======================================================================
FAIL: test_creation_parameters (__main__.CarTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_car.py", line 20, in test_creation_parameters
    self.assertEqual(self.car.model, self.model)  # Failure!
AssertionError: 'Ford' != 'Model T'

----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (failures=2)
Johnsyweb
fuente
Entonces, ¿dividiría Car.test_init en cuatro funciones?
Bruce Christensen
@Bruce Christensen: Probablemente lo dividiría en dos. Pero incluso entonces, no estoy seguro de que sus afirmaciones sean útiles. Ver actualización para responder.
Johnsyweb