Модульное тестирование нескольких уровней исключений - где остановиться? - PullRequest
4 голосов
/ 19 января 2011

Давайте представим, что у меня есть 3 класса, каждый из которых имеет по одному ответственно (и метод). Давайте также представим, что эти классы имеют интерфейсы для облегчения внедрения зависимостей. Класс A вызывает интерфейс для класса B, а класс B вызывает интерфейс для класса C. Если класс C выдает исключение NotAPrimeNumberException (если, скажем, параметр int не является простым числом), я ожидаю, что будет выполнен модульный тест, который убедится, что что C выдает NotAPrimeNumberException, если переданный параметр не является простым числом. Пока все хорошо.

В настоящее время я убежден, что модульные тесты предоставляют всю документацию, необходимую для понимания поведения тестируемого метода. Таким образом, вышеупомянутый модульный тест будет выглядеть примерно так: MakeSureNotAPrimeNumberExceptionIsThrownIfNumberisNotPrimeTest ().

Класс B знает, что класс C может вызвать исключение NotAPrimeNumberException. Если я хочу позволить пузырю NotAPrimeNumberException выйти из класса B, должен ли я написать модульный тест для класса B, чтобы каким-то образом проверить, что NotAPrimeNumberException вызывается в некоторых обстоятельствах? А как насчет класса А? Если A также позволяет всплыть NotAPrimeNumberException, должен ли он иметь для этого модульный тест?

Меня беспокоит то, что если я не напишу модульный тест в классе B, то потребители для класса B не будут знать, что класс B может выдать этот тип исключения. Однако, если я действительно пишу модульный тест, то немного глупо заставлять вызов класса B генерировать исключение NotAPrimeNumberException только для того, чтобы НЕ обрабатывать исключение в классе B. Если я не пишу модульный тест, что является ли подходящим способом, если таковые имеются, документировать в API, что это исключение может произойти?

Бонусный вопрос: как вы облегчаете это в NUnit с Rhino-mocks? Это, конечно, зависит от первых двух вопросов.

Ответы [ 4 ]

2 голосов
/ 19 января 2011

Вот несколько соображений:

  1. Класс C предназначен для создания исключения NotAPrimeNumberException.Следовательно, у вас должен быть тест, который проверяет, что исключение выдается, когда ожидается.
  2. От того, тестируете ли вы класс B или A в этом случае, зависит от того, делают ли они что-нибудь для обработки исключения.Если класс B должен выполнить какое-то действие, когда класс C выдает исключение, вам следует проверить это поведение.Если класс B просто пропускает это исключение, вам не следует проверять его.В противном случае вам также потребуется проверить поведение NullReferenceException, ArithmeticOverflowException, HttpException и любого другого типа исключения.Это модульное тестирование ad nausaeam, и оно не помогает.
  3. В связи с изменением поведения класса C. у вас есть несколько моментов, на которые следует обратить внимание.Любой тест, который зависит от измененного поведения, также должен быть изменен.(Так как вы выполняете TDD, вы изменили тесты до того, как изменили поведение, верно?) Кроме того, любые изменения, имитирующие поведение исключения C в классе C, также должны быть изменены.Нет смысла в тестировании того, что происходит, когда класс C генерирует исключение, если он никогда не генерирует это исключение.Это последнее соображение несколько проблематично, и я не знаю, как им можно управлять формально.Меня интересуют дальнейшие мысли.
2 голосов
/ 19 января 2011

Если классы A и B ожидают определенного поведения от классов, с которыми сотрудничают, то вам следует проверить, чтобы такое поведение происходило.Если вы хотите избежать огромного шарика грязи, к которому вы стремитесь, проводя звонки из А в В в С, то вам следует применить Голливудский принцип / DIP, чтобы разорвать эту зависимость.A должен зависеть от IB (интерфейс B), а не от экземпляра B. Тогда тесты A могут просто утверждать, что A делает то, что должен, включая правильные действия перед лицом исключений, выдаваемых IB (или игнорируя их),Аналогично, B может зависеть от интерфейса C, IC, и тесты B могут аналогичным образом игнорировать детали того, что C может или не может делать, вместо этого сосредоточив внимание только на том, что делает B.

Интеграционный тест, проводящийвверх от A до B к C сможет проверить, что классы взаимодействуют правильно, а также будет местом, где исключение, которое возникает в C, всплывает от A, если это именно то, что вы хотите, чтобы произошло.И если позже окажется, что C просто возвращает null, а не вызывает исключение, тогда ваш интеграционный тест не пройден, и вы увидите, что произошло новое поведение.Однако я бы не стал загромождать свои отдельные модульные тесты тестами, показывающими, что если что-то сломается, исключение будет пузыриться, поскольку это поведение по умолчанию.Вместо этого я мог бы проверить, что любые новые исключения, которые я поднимаю, генерируются, когда ожидается, и что любая обработка исключений, которые я выполняю, работает так, как я ожидаю.Это потому, что в модульном тестировании вы должны тестировать только тестируемый код, а не поведение соавторов.

1 голос
/ 19 января 2011

Вы должны использовать насмешки для B и C и вызовы их методов.В этом суть теста UNIT - вам нужно только протестировать единицу кода, а не его зависимости.

Откладывая это в сторону, если вы не хотите использовать mocking в этом случае, думаю,не должно быть никаких особых условий, позволяющих ему пройти, если в одну из зависимостей было сгенерировано исключение.Это должно потерпеть неудачу.Первая причина: конкретный код, который вы тестируете, на самом деле не работает (вы тестируете его целиком, с зависимостями).Вторая причина: ваши тесты запутываются таким образом.

Не забудьте сделать это простым.

0 голосов
/ 19 января 2011

Если классам A и B на самом деле не нужно заботиться о том, есть ли исключение, то вам не следует юнит-тестировать эту функциональность.Если у них есть определенное поведение в отношении этого исключения (возможно, они заключают его в другое исключение или ловят его и громко жалуются с помощью звуковых сигналов), и вам необходимо проверить это поведение, то вам следует это сделать.

В первом случае я бы принял решение не проводить модульные тесты A и B, чтобы избежать хрупкости моих тестов.Если не важно, что вы проверяете поведение A и B перед определенным исключением, тогда вы не хотите связывать эти тесты с поведением другого модуля.

...