理解在单元测试类中使用语句捕获ValueError异常

7

如果你是Python和unittest的初学者,可以看一下单元测试介绍教程中的例子。其中使用with语句捕获ValueError。

被测试的脚本(invoice_calculator.py)是:

def divide_pay(amount, staff_hours):
    """
    Divide an invoice evenly amongst staff depending on how many hours they
    worked on a project
    """
    total_hours = 0
    for person in staff_hours:
        total_hours += staff_hours[person]

    if total_hours == 0:
        raise ValueError("No hours entered")

    per_hour = amount / total_hours

    staff_pay = {}
    for person in staff_hours:
        pay = staff_hours[person] * per_hour
        staff_pay[person] = pay

    return staff_pay

单元测试包括此函数,以便捕获一个边缘情况,在该情况下 staff_hours = None

import unittest
from invoice_calculator import divide_pay

class InvoiceCalculatorTests(unittest.TestCase):
    def test_equality(self):
        pay = divide_pay(300.0, {"Alice": 3.0, "Bob": 6.0, "Carol": 0.0})
        self.assertEqual(pay, {'Bob': 75.0, 'Alice': 75.0, 'Carol': 150.0})

    def test_zero_hours_total(self):
        with self.assertRaises(ValueError):
            pay = divide_pay(360.0, {"Alice": 0.0, "Bob": 0.0, "Carol": 0.0})

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

关于在test_zero_hours_total(self)中使用with语句,实际上这里是如何工作/执行的? test_zero_hours_total()函数基本上是这样工作的(通俗易懂的描述):当将360.0, {"Alice": 0.0, "Bob": 0.0, "Carol": 0.0}(这会在divide_pay()中引发ValueError)作为参数传递给divide_pay()函数时,预期错误应该是ValueError(我们通过将ValueError传递给assertRaises()函数来实现)。
3个回答

17

我不完全确定你在这里的问题是什么...

TestCase.assertRaises 创建一个可用作上下文管理器的对象(这就是它可以与with语句一起使用的原因)。当以这种方式使用时:

with self.assertRaises(SomeExceptionClass):
    # code

上下文管理器的__exit__方法将检查传入的异常信息。如果缺失,将抛出AssertionError,导致测试失败。如果异常类型错误(例如不是SomeExceptionClass的实例),也会抛出AssertionError


我的问题是:我对单元测试中的with语句的理解基本正确吗? - AdjunctProfessorFalcon
1
@Malvin9000 -- 我想是这样的吧?你对我刚才写的描述有同样的理解吗?;-) - mgilson

3

听起来你明白测试的作用。如果 assertRaises 不存在,你可能会发现了解如何编写测试非常有用。

def test_zero_hours_total(self):
    try:
        pay = divide_pay(360.0, {"Alice": 0.0, "Bob": 0.0, "Carol": 0.0})
    except ValueError:
        # The exception was raised as expected
        pass
    else:
        # If we get here, then the ValueError was not raised
        # raise an exception so that the test fails
        raise AssertionError("ValueError was not raised")

请注意,您不必将assertRaises用作上下文管理器。您还可以将其传递给异常、可调用对象和该可调用对象的参数:
def test_zero_hours_total(self):
    self.assertRaises(ValueError, divide_pay, 360.0, {"Alice": 0.0, "Bob": 0.0, "Carol": 0.0})

借助于 contextlib,这种形式的 assertRaises 变得非常容易编写。 - mgilson
@mgilson 我以前没有使用过contextlib,我得试一下 :) - Alasdair
这将是一个有趣的玩具问题,让你适应它。 - mgilson

0
应该是这样的: self.assertEqual(pay, {'Bob': 100.0, 'Alice': 200.0, 'Carol': 0.0})

这并没有回答问题。一旦你拥有足够的声望,你就可以评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审查 - rob
@rob:你能解释一下为什么这不是对问题的回答吗?在我看来,它看起来像一个回答。我有什么地方理解错了吗? - Jeremy Caney

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接