Продолжение в Python unittest, когда утверждение не выполняется

84

РЕДАКТИРОВАТЬ: переключился на лучший пример и пояснил, почему это настоящая проблема.

Я хотел бы написать модульные тесты на Python, которые продолжат выполнение при сбое утверждения, чтобы я мог видеть несколько сбоев в одном тесте. Например:

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!

Здесь цель теста - убедиться, что Car __init__правильно устанавливает свои поля. Я мог бы разбить его на четыре метода (и это часто отличная идея), но в данном случае я думаю, что более читабельным будет оставить его как единственный метод, который проверяет одну концепцию («объект инициализирован правильно»).

Если мы предположим, что здесь лучше не разбивать метод, тогда у меня возникнет новая проблема: я не могу видеть сразу все ошибки. Когда я исправляю modelошибку и повторно запускаю тест, wheel_countпоявляется ошибка. Я бы сэкономил время, увидев обе ошибки при первом запуске теста.

Для сравнения, фреймворк модульного тестирования C ++ от Google различает нефатальные EXPECT_*утверждения и фатальные ASSERT_*утверждения:

Утверждения приходят парами, которые проверяют одно и то же, но по-разному влияют на текущую функцию. Версии ASSERT_ * генерируют фатальные отказы, когда они терпят неудачу, и прерывают текущую функцию. Версии EXPECT_ * генерируют нефатальные сбои, которые не прерывают текущую функцию. Обычно предпочтительнее EXPECT_ *, поскольку они позволяют сообщать о более чем одном сбое в тесте. Однако вы должны использовать ASSERT_ *, если нет смысла продолжать, когда рассматриваемое утверждение не выполняется.

Есть ли способ добиться EXPECT_*подобного поведения в Python unittest? Если нет unittest, то существует ли другая среда модульного тестирования Python, которая поддерживает такое поведение?


Кстати, мне было любопытно, сколько реальных тестов могут выиграть от нефатальных утверждений, поэтому я просмотрел несколько примеров кода (отредактировано 2014-08-19 для использования кода поиска вместо Google Code Search, RIP). Из 10 случайно выбранных результатов с первой страницы все содержали тесты, которые делали несколько независимых утверждений в одном методе тестирования. Все выиграют от несмертельных утверждений.

Брюс Кристенсен
источник
2
Чем ты закончил? Меня интересует эта тема (по совершенно другим причинам, которые я буду рад обсудить в более просторном месте, чем комментарий) и хотел бы узнать ваш опыт. Кстати, ссылка «Примеры кода» заканчивается словами «К сожалению, эта служба была отключена», поэтому, если у вас есть кешированная версия этого сервиса, мне было бы интересно ее увидеть.
Davide
Для справки в будущем, я считаю, что это эквивалентный поиск в текущей системе, но результаты уже не такие, как описано выше.
ZAD-Man
2
@ Дэвид, я ничего не делал. Подход «только одно утверждение на метод» кажется мне слишком жестко догматичным, но единственным работоспособным (и поддерживаемым) решением, похоже, является предложение Энтони «поймать и добавить». Однако для меня это слишком уродливо, поэтому я просто придерживался нескольких утверждений для каждого метода, и мне придется проводить тесты чаще, чем необходимо, чтобы найти все сбои.
Брюс Кристенсен
Фреймворк тестирования Python под названием PyTest довольно интуитивно понятен и по умолчанию показывает все сбои утверждения. Это может быть решением проблемы, с которой вы столкнулись.
Сурья Шекхар Чакраборти

Ответы:

9

Что вы, вероятно, захотите сделать, так это унаследовать, unittest.TestCaseпоскольку это класс, который выдает, когда утверждение не выполняется. Вам придется переделать свою архитектуру, TestCaseчтобы не бросать (возможно, вместо этого сохраните список отказов). Изменение архитектуры может вызвать другие проблемы, которые вам придется решать. Например, вам может потребоваться TestSuiteвнести изменения в поддержку изменений, внесенных в ваш TestCase.

диетбуддха
источник
1
Я подумал, что это, вероятно, будет окончательным ответом, но я хотел прикрыть свои базы и посмотреть, не упустил ли я что-нибудь. Благодаря!
Брюс Кристенсен
4
Я бы сказал, что переопределение TestCaseради реализации мягких утверждений - излишество - их особенно легко сделать на python: просто поймайте все ваши AssertionErrors (возможно, в простом цикле) и сохраните их в списке или наборе , а затем провалить их все сразу. Ознакомьтесь с ответом @Anthony Batchelor для подробностей.
dcsordas 08
2
@dscordas Зависит от того, предназначен ли это для одноразового теста или если вы хотите использовать эту возможность для большинства тестов.
dietbuddha
44

Другой способ иметь нефатальные утверждения - это захватить исключение утверждения и сохранить исключения в списке. Затем подтвердите, что этот список пуст, как часть 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()
Энтони Бэтчелор
источник
2
Совершенно уверен, что согласен с тобой. Вот как Selenium справляется с ошибками проверки в бэкэнде python.
Энтони Бэтчелор,
Да, проблема с этим решением заключается в том, что все утверждения считаются ошибками (а не ошибками), и способ отображения ошибок на самом деле неприменим. В любом случае, это способ, и функцию рендеринга можно легко улучшить
eMarine
Я использую это решение в сочетании с ответом dietbudda, переопределив все утверждения unittest.TestCaseс помощью блоков try / except.
thodic
Для сложных тестовых шаблонов это лучшее решение для устранения ошибки unittest, но оно делает тест довольно уродливым со всеми исключениями try / exceptions. это переход между множеством тестов и одним сложным тестом. Вместо этого я начал возвращать сообщение об ошибке. Таким образом, я могу протестировать весь тестовый паттерн за один тест и сохранить удобочитаемость для моих коллег-случайных разработчиков Python.
MortenB 05
Это очень умно, так что снимаю шляпу.
courtimas
31

Один из вариантов - это утверждение сразу для всех значений как кортежа.

Например:

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

Результатом этих тестов будет:

======================================================================
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)
?           ^  ++++         ^

Это показывает, что и модель, и количество колес неверны.

Hwiechers
источник
Это умно. Лучшее решение, которое я нашел до сих пор.
Чен Ни
8

Начиная с Python 3.4 вы также можете использовать подтесты :

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)

( msgпараметр используется для более простого определения того, какой тест не прошел.)

Вывод:

======================================================================
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)
Зуку
источник
1
Теперь это должен быть принятый ответ, поскольку его легче всего добавить в существующий код.
Майкл Скотт Катберт,
7

Наличие нескольких утверждений в одном модульном тесте считается анти-шаблоном. Ожидается, что единичный модульный тест будет проверять только одно. Возможно, вы слишком много тестируете. Рассмотрите возможность разделения этого теста на несколько тестов. Таким образом, вы можете правильно назвать каждый тест.

Однако иногда можно проверить несколько вещей одновременно. Например, когда вы утверждаете свойства одного и того же объекта. В этом случае вы фактически утверждаете, верен ли этот объект. Один из способов сделать это - написать собственный вспомогательный метод, который знает, как утверждать этот объект. Вы можете написать этот метод таким образом, чтобы он отображал все свойства с ошибкой или, например, показывал полное состояние ожидаемого объекта и полное состояние фактического объекта, когда утверждение не выполняется.

Стивен
источник
1
@ Брюс: утверждение должно быть неудачным или успешным. Никогда что-то среднее. Тест должен быть надежным, читаемым и поддерживаемым. Неудачное утверждение, не провалившее тест, - плохая идея. Это делает ваши тесты чрезмерно сложными (что снижает удобочитаемость и ремонтопригодность), а наличие тестов, которым «разрешено сбоить», позволяет легко их игнорировать, что означает, что они не заслуживают доверия.
Стивен
8
любая причина, по которой остальная часть теста не может быть запущена, и она все равно будет фатальной. Я бы подумал, что вы могли бы отложить возвращение отказа где-то в пользу агрегирования всех возможных отказов, которые могут произойти.
dietbuddha
5
Я думаю, мы оба говорим одно и то же. Я хочу, чтобы каждое неудачное утверждение приводило к провалу теста; просто я хочу, чтобы сбой произошел при возврате метода тестирования, а не сразу после проверки утверждения, как упоминалось в @dietbuddha. Это позволило бы проверить все утверждения в методе, чтобы я мог видеть (и исправлять) все сбои одним выстрелом. Тест по-прежнему заслуживает доверия, читается и обслуживается (даже более того).
Брюс Кристенсен,
10
Он не говорит, что тест не должен терпеть неудачу, когда вы нажимаете assert, он говорит, что неудача не должна препятствовать другим проверкам. Например, прямо сейчас я тестирую, что определенные каталоги доступны для записи для пользователей, групп и других. Каждое - отдельное утверждение. Было бы полезно узнать из результатов теста, что все три случая не работают, поэтому я могу исправить их с помощью одного вызова chmod, вместо того, чтобы получать сообщение «Путь не доступен для записи», когда нужно снова запускать тест, чтобы получить «Путь равен не доступен для групповой записи »и так далее. Хотя, наверное, я просто утверждал, что это должны быть отдельные тесты ...
Тим Китинг
8
Тот факт, что библиотека называется unittest, не означает, что тест является изолированным модульным тестом. Модуль unittest, а также pytest, нос и другие отлично подходят для системных тестов, интеграционных тестов и т. Д. С одной оговоркой, что вы можете потерпеть неудачу только один раз. Это действительно раздражает. Я бы очень хотел, чтобы все функции assert либо добавляли параметр, позволяющий продолжить работу с ошибкой, либо дублировали функции assert, называемые expectBlah, которые делают такие вещи. Тогда было бы проще писать большие функциональные тесты с помощью unittest.
Okken
5

Выполняйте каждое утверждение в отдельном методе.

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)
Леннарт Регебро
источник
5
Я понимаю, что это одно из возможных решений, но оно не всегда практично. Я ищу что-то, что работает, не разбивая один ранее связанный тест на несколько небольших методов.
Брюс Кристенсен
@ Брюс Кристенсен: Если они такие сплоченные, возможно, они составят историю? А затем их можно превратить в доктесты, которые действительно будут продолжаться даже после неудачи.
Lennart Regebro
1
У меня есть набор тестов, что-то вроде этого: 1. загрузить данные, 2. подтвердить данные, загруженные правильно, 3. изменить данные, 4. подтвердить, что модификация работала правильно, 5. сохранить измененные данные, 6. подтвердить данные, сохраненные правильно. Как я могу это сделать с помощью этого метода? Нет смысла загружать данные setup(), потому что это один из тестов. Но если я помещу каждое утверждение в отдельную функцию, тогда мне придется загружать данные 3 раза, а это огромная трата ресурсов. Как лучше всего справиться с такой ситуацией?
naught101
Что ж, тесты, которые проверяют определенную последовательность, должны быть в одном методе тестирования.
Леннарт Регебро
4

В PyPI есть пакет мягких утверждений, softestкоторый будет обрабатывать ваши требования. Он работает, собирая сбои, комбинируя данные об исключениях и трассировке стека и сообщая обо всем этом как часть обычного unittestвывода.

Например, этот код:

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

... производит этот вывод консоли:

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

ПРИМЕЧАНИЕ : я создал и поддерживаю softest.

skia.heliou
источник
3

expect очень полезен в gtest. Это питон путь в сущности , и код:

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
Кен
источник
2

Мне понравился подход @ Anthony-Batchelor для захвата исключения AssertionError. Но небольшая вариация этого подхода с использованием декораторов, а также способ сообщения тестовых случаев с прохождением / неудачей.

#!/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()

Вывод с консоли:

(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
источник
1

У меня возникла проблема с ответом от @Anthony Batchelor, потому что он заставил бы меня использовать try...catchвнутри моих модульных тестов. Вместо этого я инкапсулировал try...catchлогику в переопределении TestCase.assertEqualметода. Вот код:

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

Результат вывода:

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
 : 

Дополнительные альтернативные решения для правильного захвата трассировки стека могут быть опубликованы на странице Как правильно переопределить TestCase.assertEqual (), создав правильную трассировку стека?

пользователь
источник
0

Я не думаю, что есть способ сделать это с помощью PyUnit, и не хотел бы, чтобы PyUnit расширялся таким образом.

Я предпочитаю придерживаться одного утверждения для каждой тестовой функции ( или, более конкретно, утверждать одну концепцию для каждого теста ) и переписывать test_addition()как четыре отдельные тестовые функции. Это даст более полезную информацию о сбое, а именно :

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

Если вы решите, что этот подход не для вас, вы можете найти этот ответ полезным.

Обновить

Похоже, вы тестируете две концепции с помощью обновленного вопроса, и я бы разделил их на два модульных теста. Во-первых, параметры сохраняются при создании нового объекта. Это будет иметь два утверждения, одно для makeи одно дляmodel . Если первое не удается, то это явно необходимо исправить, независимо от того, проходит ли второе или нет, на данном этапе не имеет значения.

Вторая концепция более сомнительна ... Вы проверяете, инициализированы ли некоторые значения по умолчанию. Почему ? Было бы более полезно проверить эти значения в момент, когда они действительно используются (а если они не используются, то почему они там?).

Оба этих теста не пройдут, и оба должны. Когда я занимаюсь модульным тестированием, меня гораздо больше интересуют неудачи, чем успех, поскольку именно на этом мне нужно сконцентрироваться.

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
источник
Не могли бы вы разбить Car.test_init на четыре функции?
Брюс Кристенсен
@ Брюс Кристенсен: Я бы, наверное, разделил его на две части. Но даже тогда я не уверен, что ваши утверждения полезны. См. Обновление, чтобы ответить.
Johnsyweb
0

Я понимаю, что этот вопрос задавали буквально много лет назад, но сейчас есть (по крайней мере) два пакета Python, которые позволяют это делать.

Один из них самый мягкий: https://pypi.org/project/softest/

Другой - Python-Delayed-Assert: https://github.com/pr4bh4sh/python-delayed-assert

Я тоже не пользовался, но они очень похожи на меня.

Тодд Брэдли
источник