Книга Изучаем Python
Скачать 4.68 Mb.
|
217 от unittest.TestCase ; рассмотрим пример использования такого метода в контексте тестирования реального класса. Таблица 11.1. Методы assert, предоставляемые модулем unittest Метод Использование assertEqual(a, b) Проверяет, что a == b assertNotEqual(a, b) Проверяет, что a != b assertTrue(x) Проверяет, что значение x истинно assertFalse(x) Проверяет, что значение x ложно assertIn(элемент, список) Проверяет, что элемент входит в список assertNotIn(элемент, список) Проверяет, что элемент не входит в список Класс для тестирования Тестирование класса имеет много общего с тестированием функции — значитель- ная часть работы направлена на тестирование поведения методов класса. Впрочем, существуют и различия, поэтому мы напишем отдельный класс для тестирования. Возьмем класс для управления проведением анонимных опросов: survey.py class AnonymousSurvey(): """Сбор анонимных ответов на опросы.""" def __init__(self, question): """Сохраняет вопрос и готовится к сохранению ответов.""" self.question = question self.responses = [] def show_question(self): """Выводит вопрос.""" print(question) def store_response(self, new_response): """Сохраняет один ответ на опрос.""" self.responses.append(new_response) def show_results(self): """Выводит все полученные ответы.""" print("Survey results:") for response in responses: print('- ' + response) Класс начинается с вопроса, предоставленного администратором , и включает пустой список для хранения ответов. Класс содержит методы для вывода во- проса , добавления нового ответа в список ответов и вывода всех ответов, хранящихся в списке . Чтобы создать экземпляр на основе этого класса, не- обходимо предоставить вопрос. После того как будет создан экземпляр, пред- ставляющий конкретный опрос, программа выводит вопрос методом show_ 218 Глава 11 • Тестирование question() , сохраняет ответ методом store_response() и выводит результаты вызовом show_results() Чтобы продемонстрировать, что класс AnonymousSurvey работает, напишем про- грамму, которая использует этот класс: language_survey.py from survey import AnonymousSurvey # Определение вопроса с созданием экземпляра AnonymousSurvey. question = "What language did you first learn to speak?" my_survey = AnonymousSurvey(question) # Вывод вопроса и сохранение ответов. my_survey.show_question() print("Enter 'q' at any time to quit.\n") while True: response = input("Language: ") if response == 'q': break my_survey.store_response(response) # Вывод результатов опроса. print("\nThank you to everyone who participated in the survey!") my_survey.show_results() Программа определяет вопрос и создает объект AnonymousSurvey на базе этого во- проса. Программа вызывает метод show_question() для вывода вопроса, после чего переходит к получению ответов. Каждый ответ сохраняется сразу же при получе- нии. Когда ввод ответов был завершен (пользователь ввел q ), метод show_results() выводит результаты опроса: What language did you first learn to speak? Enter 'q' at any time to quit. Language: English Language: Spanish Language: English Language: Mandarin Language: q Thank you to everyone who participated in the survey! Survey results: - English - Spanish - English - Mandarin Этот класс работает для простого анонимного опроса. Но допустим, вы решили усовершенствовать класс AnonymousSurvey и модуль survey , в котором он находит- ся. Например, каждому пользователю будет разрешено ввести несколько ответов. Или вы напишете метод, который будет выводить только уникальные ответы и сообщать, сколько раз был дан тот или иной ответ. Или вы напишете другой класс для проведения неанонимных опросов. Тестирование класса 219 Реализация таких изменений грозит повлиять на текущее поведение класса AnonymousSurvey . Например, может оказаться, что поддержка ввода нескольких ответов случайно повлияет на процесс обработки одиночных ответов. Чтобы га- рантировать, что доработка модуля не нарушит существующего поведения, для класса нужно написать тесты. Тестирование класса AnonymousSurvey Напишем тест, проверяющий всего один аспект поведения AnonymousSurvey . Этот тест будет проверять, что один ответ на опрос сохраняется правильно. После того как метод будет сохранен, метод assertIn() проверяет, что он действительно на- ходится в списке ответов: test_survey.py import unittest from survey import AnonymousSurvey class TestAnonmyousSurvey(unittest.TestCase): """Тесты для класса AnonymousSurvey""" def test_store_single_response(self): """Проверяет, что один ответ сохранен правильно.""" question = "What language did you first learn to speak?" my_survey = AnonymousSurvey(question) my_survey.store_response('English') self.assertIn('English', my_survey.responses) unittest.main() Программа начинается с импортирования модуля unittest и тестируемого класса AnonymousSurvey . Тестовый сценарий TestAnonymousSurvey , как и в предыдущих случаях, наследует от unittest.TestCase . Первый тестовый метод проверяет, что сохраненный ответ действительно попадает в список ответов опроса. Этому методу присваивается хорошее содержательное имя test_store_single_response() . Если тест не проходит, имя метода в выходных данных сбойного теста ясно пока- зывает, что проблема связана с сохранением отдельного ответа на опрос. Чтобы протестировать поведение класса, необходимо создать экземпляр класса. В точке создается экземпляр с именем my_survey для вопроса "What language did you first learn to speak?" , Один ответ ( English ) сохраняется с использова- нием метода store_response() . Затем программа убеждается в том, что ответ был сохранен правильно; для этого она проверяет, что значение English присутствует в списке my_survey.responses . При запуске программы test_survey .py тест проходит успешно: ---------------------------------------------------------------------- Ran 1 test in 0.001s OK 220 Глава 11 • Тестирование Неплохо, но опрос с одним ответом вряд ли можно назвать полезным. Убедимся в том, что три ответа сохраняются правильно. Для этого в TestAnonymousSurvey добавляется еще один метод: import unittest from survey import AnonymousSurvey class TestAnonymousSurvey(unittest.TestCase): """Тесты для класса AnonymousSurvey""" def test_store_single_response(self): """Проверяет, что один ответ сохранен правильно.""" def test_store_three_responses(self): """Проверяет, что три ответа были сохранены правильно.""" question = "What language did you first learn to speak?" my_survey = AnonymousSurvey(question) responses = ['English', 'Spanish', 'Mandarin'] for response in responses: my_survey.store_response(response) for response in responses: self.assertIn(response, my_survey.responses) unittest.main() Новому методу присваивается имя test_store_three_responses() . Мы создаем объект опроса по аналогии с тем, как это делалось в test_store_single_response() Затем определяется список, содержащий три разных ответа , и для каждого из этих ответов вызывается метод store_response() . После того как ответы будут сохранены, следующий цикл проверяет, что каждый ответ теперь присутствует в my_survey.responses . Если снова запустить test_survey .py , оба теста (для одного ответа и для трех ответов) проходят успешно: ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK Все прекрасно работает. Тем не менее тесты выглядят немного однообразно, по- этому мы воспользуемся еще одной возможностью unittest для повышения их эффективности. Метод setUp() В программе test_survey .py в каждом тестовом методе создавался новый экземпляр AnonymousSurvey , а также новые ответы. Класс unittest.TestCase содержит метод Тестирование класса 221 setUp() , который позволяет создать эти объекты один раз, а затем использовать их в каждом из тестовых методов. Если в класс TestCase включается метод setUp() , Python выполняет метод setUp() перед запуском каждого метода, имя которого на- чинается с test_ . Все объекты, созданные методом setUp() , становятся доступными во всех написанных вами тестовых методах. Используем setUp() для создания экземпляра AnonymousSurvey и набора ответов, которые могут использоваться в test_store_single_response() и test_store_ three_responses() : import unittest from survey import AnonymousSurvey class TestAnonymousSurvey(unittest.TestCase): """Тесты для класса AnonymousSurvey.""" def setUp(self): """ Создание опроса и набора ответов для всех тестовых методов. """ question = "What language did you first learn to speak?" self.my_survey = AnonymousSurvey(question) self.responses = ['English', 'Spanish', 'Mandarin'] def test_store_single_response(self): """Проверяет, что один ответ сохранен правильно.""" self.my_survey.store_response(self.responses[0]) self.assertIn(self.responses[0], self.my_survey.responses) def test_store_three_responses(self): """Проверяет, что три ответа были сохранены правильно.""" for response in self.responses: self.my_survey.store_response(response) for response in self.responses: self.assertIn(response, self.my_survey.responses) unittest.main() Метод setUp() решает две задачи: он создает экземпляр опроса и список отве- тов . Каждый из этих атрибутов снабжается префиксом self , поэтому он может использоваться где угодно в классе. Это обстоятельство упрощает два тестовых метода, потому что им уже не нужно создавать экземпляр опроса или ответы. Метод test_store_single_response() убеждается в том, что первый ответ в self. responses — self.responses[0] — сохранен правильно, а метод test_store_single_ response() убеждается в том, что правильно были сохранены все три ответа в self. responses При повторном запуске test_survey .py оба теста по-прежнему проходят. Эти тесты будут особенно полезными при расширении AnonymousSurvey с поддержкой не- скольких ответов для каждого участника. После внесения изменений вы можете повторить тесты и убедиться в том, что изменения не повлияли на возможность сохранения отдельного ответа или серии ответов. 222 Глава 11 • Тестирование При тестировании классов, написанных вами, метод setUp() упрощает написание тестовых методов. Вы создаете один набор экземпляров и атрибутов в setUp() , а затем используете эти экземпляры во всех тестовых методах. Это намного проще и удобнее, чем создавать новый набор экземпляров и атрибутов в каждом тестовом методе. ПРИМЕЧАНИЕ Во время работы тестового сценария Python выводит один символ для каждого модульного теста после его завершения . Для прошедшего теста выводится точка; если при выполнении произошла ошибка, выводится символ E, а если не прошла проверка условия assert, выводится символ F . Вот почему вы увидите другое количество точек и символов в первой строке вывода при выполнении ваших тестовых сценариев . Если выполнение тестового сценария занимает слишком много време- ни, потому что сценарий содержит слишком много тестов, эти символы дадут некоторое представ- ление о количестве прошедших тестов . УПРАЖНЕНИЯ 11-3 . Работник: напишите класс Employee, представляющий работника . Метод __init__() должен получать имя, фамилию и ежегодный оклад; все эти значения должны сохраняться в атрибутах . Напишите метод give_raise(), который по умолчанию увеличивает ежегодный оклад на $5000 — но при этом может получать другую величину прибавки . Напишите тестовый сценарий для Employee . Напишите два тестовых метода, test_give_ default_raise() и test_give_custom_raise() . Используйте метод setUp(), чтобы вам не при- ходилось заново создавать экземпляр Employee в каждом тестовом методе . Запустите свой тестовый сценарий и убедитесь в том, что оба теста прошли успешно . Итоги В этой главе вы научились писать тесты для функций и классов с использованием средств модуля unittest . Вы узнали, как написать класс, наследующий от unittest. TestCase , и как писать тестовые методы для проверки конкретных аспектов пове- дения ваших функций и классов. Вы научились использовать метод setUp() для эффективного создания экземпляров и атрибутов, которые могут использоваться во всех методах для тестирования класса. Тестирование — важная тема, на которую многие новички не обращают вни- мания. Пока вы делаете свои первые шаги в программировании, писать тесты для простых проектов не нужно. Но как только вы начинаете работать над про- ектами, требующими значительных затрат ресурсов на разработку, непременно обеспечьте тестирование критических аспектов поведения ваших функций и классов. С эффективными тестами вы можете быть уверены в том, что измене- ния в проекте не повредят тому, что уже работает, а это развяжет вам руки для усовершенствования кода. Случайно нарушив существующую функциональность, вы немедленно узнаете об этом, что позволит вам быстро исправить проблему. Отреагировать на сбой теста всегда намного проще, чем на отчет об ошибке от не- довольного пользователя. Другие программисты будут более уважительно относиться к вашим проектам, если вы включите в них исходные тесты. Они будут чувствовать себя более комфортно, Итоги 223 экспериментируя с вашим кодом, и с большей готовностью присоединятся к уча- стию в ваших проектах. Если вы будете участвовать в проекте, над которым работа- ют другие программисты, вам придется продемонстрировать, что ваш код проходит существующие тесты; кроме того, от вас будут ждать, что вы напишете тесты для нового поведения, добавленного вами в проект. Поэкспериментируйте с тестами и освойтесь с процессом тестирования кода. Пишите тесты для критических аспектов поведения ваших функций и классов, но не стремитесь к полному тестовому покрытию своих ранних проектов (если у вас для этого нет особых причин). Ч а с т ь II Проекты Поздравляем! Вы знаете о Python достаточно для того, чтобы взяться за построение интерактивных, осмысленных проектов . Создание собственных проектов закрепит новые навыки и упрочит ваше понимание концепций, представленных в части I . В части II представлены три типа проектов; вы можете взяться за любые из них в том порядке, который вам больше нравится . Ниже приведено краткое описание каждого проекта, чтобы вам было проще решить, с чего начать . Программирование игры на языке Python В проекте Alien Invasion (главы 12, 13 и 14) мы воспользуемся пакетом Pygame для написания 2D-игры, в которой игрок должен сбивать корабли пришельцев, падающие по экрану с нарастающей скоростью и сложностью. К концу этого про- екта вы будете знать достаточно для того, чтобы создавать собственные 2D-игры с использованием Pygame. Визуализация данных Проект Data Visualization начинается с главы 15. В этом проекте вы научитесь генерировать данные и создавать практичные, элегантные визуализации этих данных с использованием пакетов matplotlib и Pygal. Глава 16 научит вас работать с данными из сетевых источников и передавать их пакету визуализации для по- строения графиков погодных данных и карты с населением мира. Наконец, глава 17 показывает, как написать программу для автоматической загрузки и визуализации данных. Навыки визуализации пригодятся вам в изучении области анализа дан- ных — в современном мире это умение ценится очень высоко. Веб-приложения В проекте Web Applications (главы 18, 19 и 20) мы при помощи пакета Django соз- дадим простое веб-приложение для ведения веб-дневника по произвольным темам. Пользователь создает учетную запись с именем и паролем, вводит тему и делает заметки. Вы также научитесь развертывать свои приложения так, чтобы сделать их доступными для потенциальных пользователей со всего мира. После завершения проекта вы сможете заняться построением собственных про- стых веб-приложений. Кроме того, вы будете готовы к изучению более серьезных ресурсов, посвященных построению приложений с использованием Django. Проект I Инопланетное вторжение 12 Стреляющий корабль Давайте создадим собственную игру! Мы воспользуемся Pygame — подборкой ин- тересных, мощных модулей Python для управления графикой, анимацией и даже звуком, упрощающей построение сложных игр. Pygame берет на себя такие задачи, как перерисовка изображений на экране, что позволяет вам пропустить бульшую часть рутинного, сложного программирования и сосредоточиться на высокоуров- невой логике игровой динамики. В этой главе мы настроим Pygame и создадим корабль, который движется вле- во и вправо и стреляет по приказу пользователя. В следующих двух главах вы создадите флот инопланетного вторжения, а затем займетесь внесением усовер- шенствований — например, ограничением количества попыток и добавлением таблицы рекордов. Эта глава также научит вас управлять большими проектами, состоящими из многих файлов. Мы часто будем проводить рефакторинг и изменять структуру содержи- мого файлов, чтобы проект был четко организован, а код оставался эффективным. Программирование игр — идеальный способ совместить изучение языка с раз- влечением. Написание простой игры поможет вам понять, как пишутся профес- сиональные игры. В процессе работы над этой главой вводите и запускайте код, чтобы понять, как каждый блок кода участвует в общем игровом процессе. Экс- периментируйте с разными значениями и настройками, чтобы лучше понять, как следует организовать взаимодействие с пользователем в ваших собственных играх. ПРИМЕЧАНИЕ Игра Alien Invasion состоит из множества файлов; создайте в своей системе новый каталог с име- нем alien_invasion . Чтобы команды import работали правильно, все файлы проекта должны нахо- диться в этой папке . Планирование проекта Построение крупного проекта должно начинаться не с написания кода, а с плани- рования. План поможет вам направить усилия в нужном направлении и повысит вероятность успешного завершения проекта. Итак, напишем общее описание игрового процесса. Хотя это описание не затра- гивает все аспекты игры, оно дает достаточно четкое представление о том, с чего начинать работу: |