Предварительное тестирование Python для исключений, когда покрытие не проходит - PullRequest
4 голосов
/ 15 апреля 2010

Недавно я столкнулся с простой, но неприятной ошибкой. У меня был список, и я хотел найти в нем самого маленького члена. Я использовал встроенный в Python min (). Все работало отлично, пока в каком-то странном сценарии список не стал пустым (из-за странного пользовательского ввода я не мог этого предвидеть). Мое приложение упало с ошибкой ValueError (кстати, не задокументировано в официальных документах).

У меня очень обширные юнит-тесты, и я регулярно проверяю покрытие, чтобы избежать подобных сюрпризов. Я также использую Pylint (все интегрировано в PyDev) и никогда не игнорирую предупреждения, но мне не удалось обнаружить эту ошибку до того, как это сделали мои пользователи.

Могу ли я что-нибудь изменить в своей методологии , чтобы избежать ошибок во время выполнения? (что было бы обнаружено во время компиляции в Java / C #?).

Я ищу что-то большее, чем просто обернуть мой код, за исключением. Что еще я могу сделать? Сколько других сборок в функциях Python скрывают такие неприятные сюрпризы, как этот ???

Ответы [ 4 ]

7 голосов
/ 15 апреля 2010

Проблема здесь в том, что искаженный внешний ввод привел к сбою вашей программы. Решение состоит в том, чтобы полностью протестировать возможные входные сценарии на границах вашего кода. Вы говорите, что ваши юнит-тесты «обширные», но вы явно не проверяли эту возможность. Покрытие кода является полезным инструментом, но важно помнить, что код покрытия не - это то же самое, что тщательное его тестирование. Тщательное тестирование представляет собой комбинацию покрывающих сценариев использования и строк кода.

Методология, которую я использую, заключается в том, чтобы доверять внутренним абонентам, но никогда не доверять внешним абонентам или вводу. Поэтому я явно не проверяю блок для пустого списка в любом коде, кроме первой функции, которая получает внешний вход. Но эта входная функция должна быть исчерпывающе покрыта.

В этом случае я думаю, что исключение библиотеки - разумное поведение - нет смысла просить min пустого списка. Библиотека не может законно установить для вас значение, например 0, так как вы можете иметь дело, например, с отрицательными числами.

Я думаю, что пустой список никогда не должен был достигать кода, который запрашивает min - он должен был быть идентифицирован при вводе и либо вызывать там исключение, либо устанавливать его в 0, если это работает для вас, или как иначе это работает для вас.

4 голосов
/ 15 апреля 2010

Даже в Java / C # класс исключений RuntimeError не проверяется и не будет обнаруживаться компилятором (поэтому они называются RuntimeError, а не CompileError).

В python есть некоторые исключения, такие какKeyboardInterrupt особенно опасны, так как его можно поднять практически в любой произвольной точке программы.

Я ищу нечто большее, чем просто обернуть мой код в большую попытку, кроме.

Все, кроме этого, пожалуйста.Гораздо лучше, чтобы исключения попадали к пользователю и останавливали программу, а не позволяли ошибкам обойтись молча (Zen of Python).

В отличие от Java, Python не требует перехвата всех исключений, потому что требование перехвата всех исключений позволяет программистам слишком легко игнорировать исключение (путем написания пустого обработчика исключений).

Justрасслабься, пусть ошибка остановится;пусть пользователь сообщит вам об этом, чтобы вы могли это исправить.Другой альтернативой является то, что вы заходите в отладчик на сорок два часа, потому что данные клиента повсеместно повреждаются из-за пустого обязательного обработчика исключений.

Итак, что вы должны изменить в своей методологии, так это думать, что исключение - это плохо;они не красивые, но они лучше, чем альтернативы.

1 голос
/ 16 апреля 2010

Вы могли бы использовать рандомизированное тестирование:

#!/usr/bin/env python
import random
from peckcheck import TestCase, an_int, main

def a_seq(generator):
    return lambda size: [generator(size) 
                         for _ in xrange(random.randrange(size))] 

class TestMin(TestCase):
    def testInputNoThrow(self, x=a_seq(an_int)):
        min(x)

if __name__=="__main__":
    main()

Чтобы установить peckcheck, введите:

$ pip install http://github.com/downloads/zed/peckcheck/peckcheck-0.1.v2.6.tar.gz

Или просто личинка peckcheck.py

0 голосов
/ 15 апреля 2010

Я не знаю прямого ответа на ваш вопрос; Мне бы тоже понравилось, если бы Пилинт предупреждал о таких возможностях. Моя общая практика, учитывая, что пустые списки вызывают проблемы во всевозможных ситуациях, это проверять списки на правду перед их использованием; например:

val = min(vals) if vals else 0

Во многих случаях это «бесплатно», так как вам все равно часто нужно проверять None. Он также может окупить производительность в пустых списках особых случаев, чтобы избежать, т. Е. Запуска нового потока, процесса или транзакции базы данных для обработки нулевых элементов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...