Понимание с оператором, чтобы поймать ValueError в классе unittest

Новичок в модульном тестировании и Python в целом, наткнулся на пример во вводном руководстве по модульному тестированию, в котором оператор 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()

Что касается использования оператора with в test_zero_hours_total(self), что на самом деле здесь происходит с точки зрения того, как этот оператор работает/выполняется?

Работает ли функция test_zero_hours_total() в основном следующим образом (описание неспециалиста): ожидаемая ошибка должна быть ValueError (что мы делаем, передавая ValueError функции assertRaises()), когда 360.0, {"Alice": 0.0, "Bob": 0.0, "Carol": 0.0} (что вызовет ValueError в divide_pay()) передается в качестве аргументов функции divide_pay() функция?


person AdjunctProfessorFalcon    schedule 16.12.2015    source источник


Ответы (2)


Я не уверен на 100%, что ваш вопрос здесь...

TestCase.assertRaises создает объект, который можно использовать в качестве диспетчера контекста (поэтому его можно использовать с оператором with). При использовании таким образом:

with self.assertRaises(SomeExceptionClass):
    # code

Метод __exit__ контекстного менеджера проверит переданную информацию об исключении. Если она отсутствует, будет выдано AssertionError, что приведет к сбою теста. Если исключение неправильного типа (например, не экземпляр SomeExceptionClass), также будет выдано AssertionError.

person mgilson    schedule 16.12.2015
comment
Мой вопрос: правильно ли я понимаю, как работает оператор with в модульном тесте? - person AdjunctProfessorFalcon; 17.12.2015
comment
@Malvin9000 -- Я так думаю? Соответствует ли ваше понимание описанию, которое я только что написал? ;-) - person mgilson; 17.12.2015

Похоже, вы понимаете, что делает тест. Возможно, вам будет полезно посмотреть, как вы могли бы написать тест, если бы 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})
person Alasdair    schedule 16.12.2015
comment
А с помощью contextlib эта форма assertRaises становится довольно простой в написании. - person mgilson; 17.12.2015
comment
@mgilson Я раньше не использовал contextlib, придется попробовать :) - person Alasdair; 17.12.2015
comment
Это будет забавная игрушечная задача, чтобы привыкнуть к ней. - person mgilson; 17.12.2015