简体   繁体   English

如何在单个Python单元测试中处理多个断言?

[英]How do I handle multiple asserts within a single Python unittest?

This is a problem that came up when performing a single test that had multiple independent failure modes, due to having multiple output streams. 由于具有多个输出流,因此在执行具有多个独立故障模式的单个测试时会出现此问题。 I also wanted to show the results of asserting the data on all those modes, regardless of which failed first. 我还想展示在所有那些模式下断言数据的结果,而不管哪个先失败。 Python's unittest has no such feature outside of using a Suite to represent the single test, which was unacceptable since my single test always needed to be run as a single unit; Python的单元测试除了使用套件表示单个测试外没有其他功能,这是不可接受的,因为我的单个测试始终需要作为一个单元运行。 it just doesn't capture the nature of the thing. 它只是没有抓住事物的本质。

A practical example is testing an object that also generates a log. 一个实际的示例是测试也会生成日志的对象。 You want to assert the output of it's methods, but you also want to assert the log output. 您想声明其方法的输出,但也想声明日志的输出。 The two outputs require different tests, which can be neatly expressed as two of the stock asserts expressions, but you also don't want the failure of one to hide the possible failure of the other within the test. 这两个输出需要不同的测试,可以将它们很好地表示为两个断言表达式,但是您也不希望一个失败会在测试中隐藏另一个可能的失败。 So you really need to test both at the same time. 因此,您确实需要同时测试两者。

I cobbled together this useful little widget to solve my problem. 我拼凑了这个有用的小部件来解决我的问题。

def logFailures(fnList):
    failurelog = []
    for fn in fnList:
        try:
            fn()
        except AssertionError as e:
            failurelog.append("\nFailure %d: %s" % (len(failurelog)+1,str(e)))

    if len(failurelog) != 0:
        raise AssertionError(
            "%d failures within test.\n %s" % (len(failurelog),"\n".join(failurelog))
        )

Which is used like so: 用法如下:

def test__myTest():
    # do some work here
    logFailures([
        lambda: assert_(False,"This test failed."),
        lambda: assert_(False,"This test also failed."),
    ])

The result is that logFailures() will raise an exception that contains a log of all the assertions that were raised in methods within the list. 结果是logFailures()将引发一个异常,该异常包含列表中方法中引发的所有断言的日志。

The question: While this does the job, I'm left wondering if there's a better way to handle this, other than having to go to the length of creating nested suites of tests and so forth? 问题:尽管这样做可以完成工作,但我想知道是否还有更好的方法来处理此问题,而不是花很多时间来创建嵌套的测试套件等等?

I disagree with the dominant opinion that one should write a test method for each assertion. 我不同意一种普遍观点,即应该为每个断言编写一种测试方法。 There are situations where you want to check multiple things in one test method. 在某些情况下,您想用一种测试方法检查多件事情。 Here is my answer for how to do it: 这是我的解决方法:

# Works with unittest in Python 2.7
class ExpectingTestCase(unittest.TestCase):
    def run(self, result=None):
        self._result = result
        self._num_expectations = 0
        super(ExpectingTestCase, self).run(result)

    def _fail(self, failure):
        try:
            raise failure
        except failure.__class__:
            self._result.addFailure(self, sys.exc_info())

    def expect_true(self, a, msg):
        if not a:
            self._fail(self.failureException(msg))
        self._num_expectations += 1

    def expect_equal(self, a, b, msg=''):
        if a != b:
            msg = '({}) Expected {} to equal {}. '.format(self._num_expectations, a, b) + msg
            self._fail(self.failureException(msg))
        self._num_expectations += 1

And here are some situations where I think it's useful and not risky: 在某些情况下,我认为这很有用且没有风险:

1) When you want to test code for different sets of data. 1)当您要测试不同数据集的代码时。 Here we have an add() function and I want to test it with a few example inputs. 这里我们有一个add()函数,我想用一些示例输入来测试它。 To write 3 test methods for the 3 data sets means repeating yourself which is bad. 为3个数据集编写3个测试方法意味着重复自己,这很不好。 Especially if the call was more elaborate.: 尤其是在通话更为详尽的情况下:

class MyTest(ExpectingTestCase):
    def test_multiple_inputs(self):
        for a, b, expect in ([1,1,2], [0,0,0], [2,2,4]):
            self.expect_equal(expect, add(a,b), 'inputs: {} {}'.format(a,b))

2) When you want to check multiple outputs of a function. 2)当您要检查一个功能的多个输出时。 I want to check each output but I don't want a first failure to mask out the other two. 我想检查每个输出,但是我不想让第一个失败掩盖其他两个输出。

class MyTest(ExpectingTestCase):
    def test_things_with_no_side_effects(self):
        a, b, c = myfunc()
        self.expect_equal('first value', a)
        self.expect_equal('second value', b)
        self.expect_equal('third value', c)

3) Testing things with heavy setup costs. 3)以高昂的安装成本进行测试。 Tests must run quickly or people stop using them. 测试必须快速运行,否则人们将停止使用它们。 Some tests require a db or network connection that takes a second which would really slow down your test. 某些测试需要花费一秒钟的数据库或网络连接,这实际上会降低测试速度。 If you are testing the db connection itself, then you probably need to take the speed hit. 如果要测试数据库连接本身,则可能需要提高速度。 But if you are testing something unrelated, we want to do the slow setup once for a whole set of checks. 但是,如果您要测试不相关的内容,则我们希望对整套检查进行一次缓慢的设置。

With using a subtest, execution would not stop after the first failure https://docs.python.org/3/library/unittest.html#subtests 使用子测试时,在第一次失败后,执行不会停止https://docs.python.org/3/library/unittest.html#subtests

Here is example with two fail asserts: 这是两个失败断言的示例:

class TestMultipleAsserts(unittest.TestCase):

    def test_multipleasserts(self):
        with self.subTest():
            self.assertEqual(1, 0)
        with self.subTest():
            self.assertEqual(2, 0)

Output will be: 输出将是:

======================================================================
FAIL: test_multipleasserts (__main__.TestMultipleAsserts) (<subtest>)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./test.py", line 9, in test_multipleasserts
    self.assertEqual(1, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_multipleasserts (__main__.TestMultipleAsserts) (<subtest>)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./test.py", line 11, in test_multipleasserts
    self.assertEqual(2, 0)
AssertionError: 2 != 0

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

FAILED (failures=2)

You can easy wrap subtest as following 您可以如下轻松包装子测试

class MyTestCase(unittest.TestCase):
    def expectEqual(self, first, second, msg=None):
        with self.subTest():
            self.assertEqual(first, second, msg)

class TestMA(MyTestCase):
    def test_ma(self):
        self.expectEqual(3, 0)
        self.expectEqual(4, 0)

This feels like over-engineering to me. 对我来说,这感觉像是过度设计。 Either: 要么:

  • Use two asserts in one test case. 在一个测试用例中使用两个断言。 If the first assert fails, it's true, you won't know whether the second assert passed or not. 如果第一个断言失败,那是事实,您将不知道第二个断言是否通过。 But you're going to fix the code anyway, so fix it, and then you'll find out if the second assert passed. 但是无论如何,您都将要修复该代码,因此对其进行修复,然后您将确定第二个断言是否通过。

  • Write two tests, one to check each condition. 编写两个测试,一个检查每个条件。 If you fear duplicated code in the tests, put the bulk of the code in a helper method that you call from the tests. 如果您担心测试中的代码重复,请将大部分代码放在从测试中调用的帮助器方法中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM