Единственный вопрос.. «Остановись и загорись»
Единственный вопрос. Один тестовый случай (test case) отвечает на один вопрос о тестируемом коде. Тестовый случай должен быть способен…
Каждый тестовый случай — это остров. Учитывая это, давайте составим тестовый случай (тест) для первого требования: 1. Функция to_roman() должна возвращать предстставление числа в римской системе счисления для всех чисел от 1 до 3999 Не сразу ясно, как этот скрипт делает... ну, хоть что-то. Он определяет класс, не содержащий метод __init__(). Класс содержит другой метод, который никогда не вызывается. Скрипт содержит блок __main__, но тот не ссылается на класс или его методы. Но кое-что он делает, поверьте мне. import roman1
① Для описания тестового случая первым делом определим класс TestCase подкласс модуля unittest. Этот класс содержит много полезных методов, которые вы можете использовать в ваших тестах для определенных условий. ② Это множество пар " число/значение", определенных мной вручную. Оно включает минимальные 10 чисел, наибольшее (3999), все числа, которые в преобразованном виде состоят из одного символа, а также набор случайных чисел. Не нужно тестировать все возможные варианты, но все уникальные варианты протестировать нужно. ③ Каждый тест определен отдельным методом, который вызывается без параметров и не возвращает значения. Если метод завершается нормально, без выброса исключения - тест считается пройденным, если выброшено исключение - тест завален. ④ Здесь и происходит вызов тестируемой функции to_roman(). (Ну, функция еще не написана, но когда будет, это будет строка, которая ее вызовет. ) Заметьте, что Вы только что определили интерфейс (API) функции to_roman(): она должна принимать число для конвертирования и возвращать строку (преставление в виде Римского числа). Если API отличается от вышеуказанного, тест вернет ошибку. Также отметьте, что Вы не отлавливаете какие-либо исключения, когда вызываете to_roman(). Это сделано специально. to_roman() не должна возвращать исключение при вызове с правильными входными параметрами и правильными значениями этих параметров. Если to_roman() выбрасывает исключение, Тест считается проваленным. ⑤ Предполагая, что функция to_roman() определена корректно, вызвана корректно, выполнилась успешно, и вернула значение, последним шагом будет проверка правильности возвращенного значения. Это общий вопрос, поэтому используем метод AssertEqual класса TestCase для проверки равенства (эквивалентности) двух значений. Если возвращенный функцией to_roman() результат (result)не равен известному значению, которое Вы ожидаете (numeral), assertEqual выбросит исключение и тест завершится с ошибкой. Если значения эквиваленты, assertEqual ничего не сделает. Если каждое значение, возвращенное to_roman() совпадет с ожидаемым известным, assertEqual никогда не выбросит исключение, а значит test_to_roman_known_values в итоге выполнится нормально, что будет означать, что функция to_roman() успешно прошла тест.
Раз у Вас есть тест, Вы можете написать саму функцию to_roman(). Во-первых, Вам необходимо написать заглушку, пустую функцию и убедиться, что тест провалится. Если тест удачен, когда функция еще ничего не делает, значит тест не работает вообще! Unit testing это как танец: тест ведет, код следует. Пишете тест, который проваливается, потом - код, пока тест не пройдет. # roman1. py ① На этом этапе Вы определяете API для функции to_roman(), но пока не хотите писать ее код. (Для первой проверки теста. ) Чтобы заглушить функцию, используется зарезервированное слово Python - pass, которое... ничего не делает. Выполняем romantest1. py on в интерпретаторе для проверки теста. Если Вы вызвали скрипт с параметром -v, будет выведен подробности о работе скрипта (verbose), и Вы сможете подробно увидеть, что происходит в каждом тесте. Если повезло, увидите нечто подобное: you@localhost: ~/diveintopython3/examples$ python3 romantest1. py -v ① Запущенный скрипт выполняет метод unittest. main(), который запускает каждый тестовый случай. Каждый тестовый случай - метод класса в romantest. py. Нет особых требований к организации этих классов; они могут быть как класс с методом для отдельного тестового случая, mfr и один класс + несколько методов для всех тестовых случаев. Необходимо лишь, чтобы каждый класс был наследником unittest. TestCase. ② Для каждого тестового случая модуль unittest выведет строку документации метода и результат - успех или провал. Как видно, тест провален.
③ Для каждого проваленного теста система выводит детальную информацию о том, что конкретно произошло. В данном случае вызов assertEqual() вызвал ошибку объявления (AssertionError), поскольку ожидалось возвращения 'I' от to_roman(1), но этого не произошло. (Если у функции нет нет явного возврата, то она вернет None, значение null в Python. ) ④ После детализации каждого тестового случая, unittest отображает суммарно, сколько тестов было выполнено и сколько это заняло времени. ⑤ В целом тест считается проваленным, если хоть один случай не пройден. Unittest различает ошибки и провалы. Провал вызывает метод assertXYZ, например assertEqual или assertRaises, который провалится, если объявленное условие неверно или ожидаемое исключение не выброшено. Ошибка - это другой тип исключения, который выбрасывается тестируемым кодом или тестовым юнитом и не является ожидаемым. Наконец, мы можем написать функцию to_roman(). roman_numeral_map = (('M', 1000), ① roman_numeral_map - это кортеж кортежей, определяющий три вещи: представление базовых символов римских цифр и популярных их сочетаний; порядок римских символов (в обратном направлении, от M и до I); значения римских цифр. Каждый внутренний кортеж - это пара значений (представление, число). И это не только односимвольные римские цифры; это также пары символов типа CM (“тысяча без сотни”). Это сильно упрощает код функции to_roman(). ② Вот здесь видно, в чем выигрыш такой структуры roman_numeral_map, поскольку не требуется какой-то хитрой логики при обработке вычитанием. Для конвертирования в римское число необходимо просто пройти в цикле roman_numeral_map, находя наименьшее число, в которое влезает остаток ввода. При нахожднии такового, к возвращаемому значению функции добавляется соответствующее римское представление, ввод уменьшается на это число и далее операция повторяется для следующего кортежа.
Если все же не понятно, как работает функция to_roman(), добавим print() в конец цикла: while n > = integer: Этот отладочный вывод показывает следующее: > > > import roman1 Ну, функция to_roman() вроде бы работает, как и предполагалось в начале главы. Но пройдет ли она написанный ранее тест? you@localhost: ~/diveintopython3/examples$ python3 romantest1. py -v 1. Ура! Функция to_roman() прошла тест “known values”. Возможно не всесторонняя проверка, но в ее ходе проверены различные входные данные, включая числа, записываемые одним римским символом, наибольшее исходное значение (3999), и значение, дающее наибольшее римское число (3888). На этом этапе можно сказать, что функция корректно обрабатывает любые правильные исходные значения. “Правильные” исходные значения? Хм. А как насчет неправильных? «Остановись и загорись» Недостаточно проверить работу функции только с правильными входными данными; также необходимо убедиться, что функция выдаст ошибку при неправильном вводе. И не просто ошибку - а такую как ожидается. > > > import roman1 1. Это определенно не то, что ожидалось — это не правильные римские числа! По сути, все эти числа выходят за возможные пределы, но функция все равно возвращает результат, только фиктивный. Тихое возвращение неверного значения - ооооооооооочень неверно; если возникает ошибка, лучше чтобы программа завершалась быстро и шумно. " Остановись и загорись", как говорится. " Питоновский" способ остановиться и загореться - это выбросить исключение. Спрашивается, как же учесть это в требованиях к тестированию? Для начинающих - вот так: функция to_roman() должна выбрасывать исключение типа OutOfRangeError, если ей передать число более 3999. Как будет выглядеть тест? class ToRomanBadInput(unittest. TestCase): ① 1. Как и в предыдущем случае, создаем класс-наследник от unittest. TestCase. У Вас может быть более одного теста на класс (как Вы увидите дальше в этой главе), но я решил создать отдельный класс для этого, потому что этот случай отличается от предыдущих. Мы поместили все тесты на " положительный выход" в одном классе, а на ошибки - в другом. 2. Как и в предыдущем случае, тест - это метод, имя которого - название теста.
3. Класс unittest. TestCase предоставляет метод assertRaises, который принимает следующие аргументы: тип ожидаемого исключения, имя тестируемой функции и аргументы этой функции. (Если тестируемая функция принимает более одного аргумента, все они передаются методу assertRaises по порядку, как будто передаете их тестируемой функции. ) Обратите особое внимание на последнюю строку кода. Вместо вызова функции to_roman() и проверки вручную того, что она выбрасывает исключение (путем обертывания ее в блок try-catch), метод assertRaises делает все это за нас. Все что Вы делаете - говорите, какой тип исключения ожидаете (roman2. OutOfRangeError), имя функции (to_roman()), и ее аргументы (4000). Метод assertRaises позаботится о вызове функции to_roman() и проверит, что она возвращает исключение roman2. OutOfRangeError. Также заметьте, что Вы передаете функцию to_roman() как аргумент; Вы не вызываете ее и не передаете ее имя как строку. Кажись, я уже упоминал, что все в Python является объектом? Что же происходит, когда Вы запускаете скрипт с новым тестом? you@localhost: ~/diveintopython3/examples$ python3 romantest2. py -v 1. Следовало ожидать этот провал (если конечно Вы не написали дополнительного кода), но… это не совсем " провал", скорее это ошибка. Это тонкое но очень важное различие. Тест может вернуть три состояния: успех, провал и ошибку. Успех, естественно, означает, что тест пройден — код делает что положено. «Провал» - то что вернул тест выше — код выполняется, но возвращает не ожидаемое значение. «Ошибка» означает, что Ваш код работает неправильно. 2. Почему код не выполняется правильно? Раскрутка стека все объясняет. Тестируемый модуль не выбрасывает исключение типа OutOfRangeError. То самое, которое мы скормили методу assertRaises(), потому что ожидаем его при вводе большого числа. Но исключение не выбрасывается, потому вызов метода assertRaises() провален. Без шансов - функция to_roman() никогда не выбросит OutOfRangeError. Решим эту проблему - определим класс исключения OutOfRangeError в roman2. py. class OutOfRangeError(ValueError): ① 1. Исключения - это классы. Ошибка «out of range» это разновидность ошибки — аргумент выходит за допустимые пределы. Поэтому это исключение наследуется от исключения ValueError. Это не строго необходимо (по идее достаточно наследования от класса Exception), однако так правильно. 2. Исключение вобщем-то ничего и не делает, но Вам нужна хотя бы одна строка в классе. Встроенная функция pass ничего не делает, однако необходима для минимального определения кода в Python. Теперь запустим тест еще раз. you@localhost: ~/diveintopython3/examples$ python3 romantest2. py -v 1. Тест по-прежнему не проходит, хотя уже и не выдает ошибку. Это прогресс! Значит, метод assertRaises() был выполнен и тест функции to_roman() был произведен. 2. Конечно, функция to_roman() не выбрасывает только что определенное исключение OutOfRangeError, так как Вы ее еще " не заставили". И это хорошие новости! Значит, тест работает, а проваливаться он будет, пока Вы не напишете условие его успешного прохождения. Этим и займемся. def to_roman(n): 1. Все просто: если переданный параметр больше 3999, выбрасываем исключение OutOfRangeError. Тест не ищет текстовую строку, объясняющую причину исключения, хотя Вы можете написать тест для проверки этого (но учтите трудности, связанные с различными языками - длина строк или окружение могут отличаться). Позволит ли это пройти тест? Узнаем: you@localhost: ~/diveintopython3/examples$ python3 romantest2. py -v 1. Ура! Оба теста пройдены. Так как Вы работали, переключаясь между кодированием и тестированием, то Вы с уверенностью можете сказать, что именно последние 2 строки кода позволили тесту вернуть " успех", а не " провал". Такая уверенность далась не дешево, но окупит себя с лихвой в дальнейшем.
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|