Продовжуючи модульний тест Python, коли твердження не вдається


84

EDIT: перейшов на кращий приклад і пояснив, чому це справжня проблема.

Я хотів би написати модульні тести на 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, який підтримує таку поведінку?


До речі, мені було цікаво, скільки реальних тестів може отримати користь від нефатальних тверджень, тому я переглянув деякі приклади коду (відредаговано 19.08.2014, щоб використовувати код пошуку замість Google Code Search, RIP). З 10 випадково вибраних результатів із першої сторінки всі містили тести, які зробили кілька незалежних тверджень в одному і тому ж методі тестування. Всім було б корисно від тверджень, що не є фатальними.


2
Що ти врешті зробив? Мене цікавить ця тема (із зовсім інших причин, які я б радив обговорити в просторішому місці, ніж коментар), і хотів би дізнатися ваш досвід. До речі, посилання "приклади коду" закінчується на "На жаль, цю службу було вимкнено", тому, якщо у вас є кешована версія цієї версії, мені також було б цікаво її переглянути.
Давіде,

Для подальшого використання я вважаю, що це еквівалентний пошук у поточній системі, але результати вже не такі, як описано вище.
ZAD-Man

2
@Davide, я в підсумку нічого не робив. Підхід "зробити лише одне твердження для кожного методу" здається мені занадто жорстко догматичним, але єдиним дієвим (і ремонтопридатним) рішенням є пропозиція Ентоні "схопи і додай". Однак це занадто потворно для мене, тому я просто затримався з кількома твердженнями для кожного методу, і мені доведеться жити з запущеними тестами частіше, ніж потрібно, щоб знайти всі помилки.
Брюс Крістенсен,

Структура тестування python під назвою PyTest є досить інтуїтивно зрозумілою і за замовчуванням відображає всі помилки затвердження. Це може обійти проблему, з якою ви стикаєтесь.
Суря Шехар Чакраборті,

Відповіді:


9

Що ви, мабуть, захочете зробити, це отримати, unittest.TestCaseоскільки це клас, який видає, коли твердження не вдається. Вам доведеться переробити архітектуру, TestCaseщоб її не кидати (можливо, натомість збережіть список невдач). Повторна архітектура може спричинити інші проблеми, які вам доведеться вирішити. Наприклад, вам може знадобитися провести пошук, TestSuiteщоб внести зміни на підтримку змін, внесених до вашого TestCase.


1
Я припустив, що це, мабуть, буде остаточною відповіддю, але я хотів висвітлити свої підстави і подивитися, чи не пропускаю чогось. Дякую!
Брюс Крістенсен,

4
Я б сказав, що це надмірне перевизначення TestCaseзаради реалізації м'яких тверджень - їх особливо легко зробити в python: просто вловіть усі ваші AssertionErrors (можливо, у простому циклі) і збережіть їх у списку або наборі , а потім провалити їх усі відразу. Перевірте відповідь @Anthony Batchelor, щоб дізнатись конкретні відомості.
dcsordas

2
@dscordas Залежить від того, чи це для одноразового тесту, чи ви хочете мати цю можливість для більшості тестів.
dietbuddha

43

Інший спосіб отримати твердження, що не є фатальними, - це зафіксувати виняток із твердження та зберегти винятки у списку. Потім стверджуйте, що цей список порожній як частина 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

Я використовую це рішення у поєднанні з відповіддю дієбудди, замінюючи всі твердження за unittest.TestCaseдопомогою блоків try / виключення.
thodic

Для складних тестових зразків це найкраще рішення для подолання помилки unittest, але це робить тест досить потворним з усіма спробами / винятками. це перехід між безліччю тестів і складним єдиним тестом. Натомість я почав повертати повідомлення про помилку. Тож я можу протестувати весь тестовий шаблон за один тест і зберегти читабельність для своїх побратимів-розробників випадкових python.
MortenB

Це надзвичайно розумно, тому капелюх вам.
Корсимас

30

Одним із варіантів є ствердження всіх значень одночасно у вигляді кортежу.

Наприклад:

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

Це показує, що і модель, і кількість коліс неправильні.


Це розумно. Найкраще рішення, яке я знайшов на даний момент.
Чень Ні

7

Вважається анти-шаблоном мати кілька тверджень в одному одиничному тесті. Очікується, що один одиничний тест перевірить лише одне. Можливо, ви занадто багато тестуєте. Подумайте про те, щоб розділити цей тест на кілька тестів. Таким чином ви можете правильно назвати кожен тест.

Однак іноді це нормально, перевіряючи кілька речей одночасно. Наприклад, коли ви стверджуєте властивості того самого об'єкта. У такому випадку ви фактично стверджуєте, чи правильний цей об’єкт. Це можна зробити, написавши власний допоміжний метод, який знає, як заявити про цей об’єкт. Ви можете написати цей метод таким чином, що він відображає всі властивості, що не працюють, або, наприклад, показує повний стан очікуваного об'єкта та повний стан фактичного об'єкта, коли затвердження не вдається.


1
@Bruce: Твердження має бути невдалим або успішним. Ніколи щось середнє. Тест повинен бути надійним, читабельним та ремонтопридатним. Невдале твердження, яке не проходить тест, є поганою ідеєю. Це робить ваші тести надмірно складними (що знижує читабельність і ремонтопридатність), а наявність тестів, яким “дозволено невдало”, дозволяє легко ігнорувати їх, а значить, вони не заслуговують на довіру.
Стівен

8
будь-яка причина, через яку решта тесту не може працювати, і це все ще призведе до летального результату. Я думаю, ви могли б десь відкласти повернення помилки на користь агрегування всіх можливих помилок, які можуть статися.
dietbuddha

5
Я думаю, ми обидва говоримо одне і те ж. Я хочу, щоб кожне невдале твердження призвело до невдалого тесту; просто я хочу, щоб помилка сталася, коли метод тесту повертається, а не негайно, коли тестування затверджується, як згадував @dietbuddha. Це дозволило б перевірити всі твердження методу, щоб я міг бачити (і виправляти) всі помилки одним пострілом. Тест все ще надійний, читабельний та ремонтопридатний (тим більше, насправді).
Брюс Крістенсен

10
Він не каже, що тест не повинен провалитися, коли ви натиснете на твердження, він каже, що невдача не повинна перешкоджати іншим перевіркам. Наприклад, зараз я тестую, що певні каталоги можуть бути записані користувачем, групою та іншими. Кожне - це окреме твердження. З виводу тесту було б корисно знати, що всі три випадки зазнали збою, тому я можу виправити їх одним викликом chmod, замість того, щоб отримувати "Шлях не можна записати користувачеві", щоб знову запустити тест, щоб отримати "Шлях є не для групового запису "тощо. Хоча, мабуть, я просто стверджував, що вони повинні бути окремими тестами ...
Тім Кітінг,

8
Те, що бібліотеку називають unittest, це не означає, що тест є ізольованим unit test. Модуль unittest, а також pytest, nose та інші, чудово працюють для системних тестів, інтеграційних тестів тощо. З одним застереженням - ви можете зазнати невдачі лише один раз. Це насправді дратує. Я дуже хотів би бачити, щоб усі функції assert додавали або параметр, що дозволяє продовжувати роботу з помилкою, або дублювання функцій assert, які називаються awacBlah, які роблять таке. Тоді було б набагато простіше писати більші функціональні тести за допомогою unittest.
Okken

7

З 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
Тепер це має бути прийнятою відповіддю, як найпростішою для потрапляння до існуючого коду.
Майкл Скотт Катберт,

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
Я усвідомлюю, що це одне з можливих рішень, але це не завжди практично. Я шукаю щось, що працює, не розбиваючи один раніше згуртований тест на кілька маленьких методів.
Брюс Крістенсен

@Bruce Christensen: Якщо вони такі згуртовані, то, можливо, вони складають історію? І тоді вони можуть бути зроблені в doctests, який дійсно буде тривати навіть після збою.
Леннарт Регебро

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.


3

очікувати дуже корисно в gtest. Це шлях python в gist і код:

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

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)

Тож би ви розбили Car.test_init на чотири функції?
Брюс Крістенсен

@Bruce Christensen: Я б, можливо, розбив це на дві частини. Але навіть тоді я не впевнений, що ваші твердження корисні. Дивіться оновлення, щоб відповісти.
Johnsyweb

0

Я усвідомлюю, що це питання було задано буквально роки тому, але зараз (принаймні) є два пакети Python, які дозволяють вам це зробити.

Один з них найм'якший: https://pypi.org/project/softest/

Інший - Python-Delayed-Assert: https://github.com/pr4bh4sh/python-delayed-assert

Я теж не використовував, але вони схожі на мене.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.