【问题标题】:Continuing in Python's unittest when an assertion fails断言失败时继续 Python 的单元测试
【发布时间】:2011-06-11 14:17:06
【问题描述】:

编辑:切换到一个更好的例子,并阐明了为什么这是一个真正的问题。

我想用 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 错误。当我第一次运行测试时,这样可以节省我查看这两个错误的时间。

为了比较,Google 的 C++ 单元测试框架 distinguishes between 在非致命的EXPECT_* 断言和致命的ASSERT_* 断言之间:

断言成对出现,测试相同的东西,但对当前函数有不同的影响。 ASSERT_* 版本在失败时会产生致命失败,并中止当前功能。 EXPECT_* 版本生成非致命故障,不会中止当前功能。通常 EXPECT_* 是首选,因为它们允许在测试中报告多个失败。但是,如果在相关断言失败时继续没有意义,您应该使用 ASSERT_*。

有没有办法在 Python 的 unittest 中获得类似 EXPECT_* 的行为?如果不在unittest 中,那么是否有另一个 Python 单元测试框架支持这种行为?


顺便说一句,我很好奇有多少实际测试可以从非致命断言中受益,所以我查看了一些code examples(2014-08-19 编辑,使用搜索代码而不是 Google 代码搜索,RIP)。从第一页随机选择的 10 个结果中,所有测试都包含在同一测试方法中做出多个独立断言的测试。所有人都将从非致命断言中受益。

【问题讨论】:

  • 你最后做了什么?我对这个话题很感兴趣(出于完全不同的原因,我很乐意在比评论更宽敞的地方讨论)并且想知道你的经历。顺便说一句,“代码示例”链接以“可悲的是,此服务已被关闭”结尾,所以如果您有缓存版本,我也有兴趣查看它。
  • 供以后参考,我相信this是当前系统上的等价搜索,但结果不再如上述。
  • @Davide,我最终什么都没做。 “每个方法只做一个断言”的方法对我来说似乎过于教条,但唯一可行(和可维护)的解决方案似乎是 Anthony 的“catch and append”建议。不过,这对我来说太难看了,所以我只是坚持每个方法都有多个断言,而且我必须忍受运行测试的次数超过发现所有故障所需的次数。
  • 名为 PyTest 的 python 测试框架非常直观,默认显示所有断言失败。这可能是解决您面临的问题的方法。

标签: python unit-testing


【解决方案1】:

另一种获得非致命断言的方法是捕获断言异常并将异常存储在列表中。然后断言该列表是空的,作为拆解的一部分。

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

【讨论】:

  • 很确定我同意你的看法。这就是 Selenium 在 python 后端处理验证错误的方式。
  • 是的,这个解决方案的问题是所有断言都被视为错误(而不是失败者),并且呈现错误的方式并不是真正可用的。无论如何是一种方式,渲染功能可以轻松改进
  • 我将此解决方案与 dietbudda's answer 结合使用,方法是使用 try / except 块覆盖 unittest.TestCase 中的所有断言。
  • 对于复杂的测试模式,这是克服 unittest 错误的最佳解决方案,但它使所有的 try/excepts 测试看起来相当难看。这是大量测试和复杂的单一测试之间的权衡。我已经开始返回错误字典。所以我可以在一次测试中测试整个测试模式,并为我的休闲 Python 开发人员保持可读性。
  • 这非常聪明,向你致敬。
【解决方案2】:

一个选项以元组的形式同时对所有值进行断言。

例如:

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

这说明型号和轮数都不正确。

【讨论】:

  • 这很聪明。迄今为止我找到的最佳解决方案。
【解决方案3】:

从 Python 3.4 开始你也可以使用subtests:

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)

【讨论】:

  • 这现在应该是公认的最容易放入现有代码的答案。
  • 这是最优雅的解决方案
【解决方案4】:

您可能想做的是派生unittest.TestCase,因为这是在断言失败时抛出的类。您将不得不重新构建您的 TestCase 以不抛出(也许保留一个失败列表)。重新架构东西可能会导致您必须解决的其他问题。例如,您最终可能需要派生 TestSuite 以进行更改以支持对您的 TestCase 所做的更改。

【讨论】:

  • 我认为这可能是最终的答案,但我想覆盖我的基础,看看我是否遗漏了什么。谢谢!
  • 我想说,为了实现软断言而覆盖 TestCase 是一种矫枉过正的做法——它们在 python 中特别容易实现:只需捕获所有 AssertionErrors(也许在一个简单的循环),并将它们存储在一个列表或一组中,然后一次将它们全部失败。查看@Anthony Batchelor 的具体答案。
  • @dscordas 取决于这是针对一次性测试还是您希望在大多数测试中拥有此功能。
【解决方案5】:

在单个单元测试中有多个断言被认为是一种反模式。单个单元测试预计只测试一件事。也许你测试的太多了。考虑将此测试拆分为多个测试。这样您就可以正确命名每个测试。

但有时,可以同时检查多项内容。例如,当您断言同一对象的属性时。在这种情况下,您实际上是在断言该对象是否正确。一种方法是编写一个自定义帮助方法,该方法知道如何在该对象上进行断言。您可以编写该方法,使其显示所有失败的属性,或者例如在断言失败时显示预期对象的完整状态和实际对象的完整状态。

【讨论】:

  • @Bruce:断言应该失败或成功。从来没有介于两者之间的东西。测试应该是可信赖的、可读的和可维护的。未通过测试的失败断言是一个坏主意。它使您的测试过于复杂(降低了可读性和可维护性),并且“允许失败”的测试很容易忽略它们,这意味着它们不值得信赖。
  • 其他测试无法运行的任何原因,它仍然是致命的。我认为您可以将故障的返回延迟到某个地方,以便汇总所有可能发生的故障。
  • 我认为我们都在说同样的话。我希望每个失败的断言都会导致测试失败;只是我希望在测试方法返回时发生故障,而不是在测试断言时立即发生,正如@dietbuddha 提到的那样。这将允许测试方法中的 所有 断言,以便我可以一次性查看(并修复)所有故障。该测试仍然是值得信赖的、可读的和可维护的(实际上更是如此)。
  • 他并不是说当你点击断言时测试不应该失败,他说失败不应该阻止其他检查。例如,现在我正在测试特定目录是用户、组和其他可写的。每个都是一个单独的断言。从测试输出中知道所有三种情况都失败会很有用,因此我可以通过一次 chmod 调用来修复它们,而不是得到“路径不是用户可写的”,而必须再次运行测试才能得到“路径是不可组写”等等。虽然我想我只是争辩说它们应该是单独的测试......
  • 仅仅因为该库被称为unittest,并不意味着该测试是一个孤立的单元测试。 unittest 模块,以及 pytest 和 nose 等,非常适合系统测试、集成测试等。需要注意的是,你只能失败一次。真的很烦人。我真的很想看到所有的断言函数要么添加一个允许您继续失败的参数,要么添加一个名为expectBlah的断言函数,它们会做这样的事情。然后使用 unittest 编写更大的功能测试会更容易。
【解决方案6】:

在单独的方法中执行每个断言。

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)

【讨论】:

  • 我意识到这是一种可能的解决方案,但并不总是可行的。我正在寻找一种无需将以前的内聚测试分解为几种小方法的方法。
  • @Bruce Christensen:如果他们如此有凝聚力,那么也许他们会形成一个故事?然后可以将它们制成 doctests,确实 在失败后继续。
  • 我有一组测试,如下所示:1. 加载数据,2. 断言数据加载正确,3. 修改数据,4. 断言修改正常工作,5. 保存修改后的数据,6 . 断言数据保存正确。我怎么能用这种方法做到这一点?在setup() 中加载数据是没有意义的,因为这是测试之一。但是如果我把每个断言都放到自己的函数中,那么我必须加载数据 3 次,这是对资源的巨大浪费。处理这种情况的最佳方法是什么?
  • 嗯,测试特定序列的测试应该使用相同的测试方法。
【解决方案7】:

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

【讨论】:

    【解决方案8】:

    expect 在 gtest 中非常有用。 这是gist中的python方式,代码:

    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
    

    【讨论】:

      【解决方案9】:

      我喜欢@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
      

      【讨论】:

        【解决方案10】:

        我对@9​​87654321@ 的答案有疑问,因为它会迫使我在单元测试中使用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()
        
                # https://stackoverflow.com/questions/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
         : 
        

        更多关于正确堆栈跟踪捕获的替代解决方案可以发布在How to correctly override TestCase.assertEqual(), producing the right stacktrace?

        【讨论】:

          【解决方案11】:

          我认为 PyUnit 没有办法做到这一点,并且不希望看到 PyUnit 以这种方式扩展。

          我更喜欢每个测试函数坚持一个断言 (or more specifically asserting one concept per test),并将 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)
          

          如果您认为这种方法不适合您,您可能会发现 this answer 很有帮助。

          更新

          看起来您正在用更新后的问题测试两个概念,我会将它们分成两个单元测试。第一个是在创建新对象时存储参数。这将有两个断言,一个用于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:我可能会把它分成两部分。但即便如此,我也不确定你的断言是否有用。查看更新以回答。
          【解决方案12】:

          我知道这个问题是在几年前就被问到的,但现在(至少)有两个 Python 包可以让你这样做。

          一个最软:https://pypi.org/project/softest/

          另一个是Python-Delayed-Assert:https://github.com/pr4bh4sh/python-delayed-assert

          我也没有用过,但它们看起来和我很相似。

          【讨论】:

            猜你喜欢
            • 2011-10-13
            • 2015-12-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多