Мэтиз. Изучаем Python. Crash course2 n d e d i t i o na h a n d s o n, p r o j e c t b a s e d i n t r o d u c t i o n t o p r o g r a m m i n g
Скачать 6.2 Mb.
|
233 Неплохо, но опрос с одним ответом вряд ли можно назвать полезным. Убедимся в том, что три ответа сохраняются правильно. Для этого в 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) if __name__ == '__main__': 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 содержит метод 234 Глава 11 • Тестирование 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) if __name__ == '__main__': unittest.main() Метод setUp() решает две задачи: он создает экземпляр опроса и список отве- тов . Каждый из этих атрибутов снабжается префиксом self , поэтому он может использоваться где угодно в классе. Это обстоятельство упрощает два тестовых метода, потому что им уже не нужно создавать экземпляр опроса или ответы. Метод test_store_single_response() убеждается в том, что первый ответ в self. responses — self.responses[0] — сохранен правильно, а метод test_store_single_ response() убеждается в том, что правильно были сохранены все три ответа в self. responses При повторном запуске test_survey .py оба теста по-прежнему проходят. Эти тесты будут особенно полезными при расширении AnonymousSurvey с поддержкой не- скольких ответов для каждого участника. После внесения изменений вы можете Итоги 235 повторить тесты и убедиться в том, что изменения не повлияли на возможность сохранения отдельного ответа или серии ответов. При тестировании классов, написанных вами, метод 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() для эффективного создания экземпляров и атрибутов, которые могут использоваться во всех методах для тестирования класса. Тестирование — важная тема, на которую многие новички не обращают внимания. Пока вы делаете свои первые шаги в программировании, писать тесты для про- стых проектов не нужно. Но как только вы начинаете работать над проектами, требующими значительных затрат ресурсов на разработку, непременно обеспечьте тестирование критических аспектов поведения ваших функций и классов. С эф- фективными тестами вы можете быть уверены в том, что изменения в проекте не повредят тому, что уже работает, а это развяжет вам руки для усовершенствова- ния кода. Случайно нарушив существующую функциональность, вы немедленно 236 Глава 11 • Тестирование узнаете об этом, что позволит вам быстро исправить проблему. Отреагировать на сбой теста всегда намного проще, чем на отчет об ошибке от недовольного пользователя. Другие программисты будут более уважительно относиться к вашим проектам, если вы включите исходные тесты. Они будут чувствовать себя более комфортно, экс- периментируя с вашим кодом, и с большей готовностью присоединятся к участию в ваших проектах. Если вы будете участвовать в проекте, над которым работают другие программисты, вам придется продемонстрировать, что ваш код проходит существующие тесты; кроме того, от вас будут ждать, что вы напишете тесты для нового поведения, добавленного вами в проект. Поэкспериментируйте с тестами и освойтесь с процессом тестирования кода. Пи- шите тесты для критических аспектов поведения ваших функций и классов, но не стремитесь к полному тестовому покрытию своих ранних проектов (если только у вас для этого нет особых причин). Часть II ПРОЕКТЫ Поздравляем! Вы уже знаете о Python достаточно, чтобы взяться за построение интерактивных осмысленных проектов. Создание собственных проектов закрепит новые навыки и упрочит ваше понимание концепций, представленных в части I. В части II представлены три типа проектов; вы можете взяться за любые из них в том порядке, который вам больше нравится. Ниже приведено краткое описание каждого проекта, чтобы вам было проще решить, с чего начать. 238 Глава 11 • Тестирование Программирование игры на языке Python В проекте Alien Invasion (главы 12, 13 и 14) мы воспользуемся пакетом Pygame для написания 2D-игры, в которой игрок должен сбивать корабли пришельцев, падающие по экрану с нарастающей скоростью и сложностью. К концу этого про- екта вы будете знать достаточно для того, чтобы создавать собственные 2D-игры с использованием Pygame. Визуализация данных Проект Data Visualization начинается с главы 15. В этом проекте вы научитесь генерировать данные и создавать практичные, элегантные визуализации этих данных с использованием пакетов matplotlib и Pygal. Глава 16 научит вас работать с данными из сетевых источников и передавать их пакету визуализации для по- строения графиков погодных данных и карты глобальной сейсмической активно- сти. Наконец, глава 17 показывает, как написать программу для автоматической загрузки и визуализации данных. Навыки визуализации пригодятся вам в изучении области анализа данных — в современном мире это умение ценится очень высоко. Веб-приложения В проекте Web Applications (главы 18, 19 и 20) мы при помощи пакета Django соз- дадим простое веб-приложение для ведения веб-дневника по произвольным темам. Пользователь создает учетную запись с именем и паролем, вводит тему и делает заметки. Вы также научитесь развертывать свои приложения так, чтобы сделать их доступными для потенциальных пользователей со всего мира. После завершения проекта вы сможете заняться построением собственных про- стых веб-приложений. Кроме того, вы будете готовы к изучению более серьезных ресурсов, посвященных построению приложений с использованием Django. Проект 1 Игра «Инопланетное вторжение» 12 Инопланетное вторжение Давайте создадим собственную игру! Мы воспользуемся Pygame — подборкой интересных мощных модулей Python для управления графикой, анимацией и даже звуком, упрощающей построение сложных игр. Pygame берет на себя такие задачи, как перерисовка изображений на экране, что позволяет вам пропустить большую часть рутинного, сложного программирования и сосредоточиться на высокоуров- невой логике игровой динамики. В этой главе мы настроим Pygame и создадим корабль, который движется влево и вправо и стреляет по приказу пользователя. В следующих двух главах вы соз- дадите флот инопланетного вторжения, а затем займетесь внесением усовершен- ствований, например ограничением количества попыток и добавлением таблицы рекордов. Эта глава также научит вас управлять большими проектами, состоящими из многих файлов. Мы часто будем проводить рефакторинг и изменять структуру содержимого файлов, чтобы проект был четко организован, а код оставался эф- фективным. Программирование игр — идеальный способ совместить изучение языка с раз- влечением. Написание простой игры поможет вам понять, как пишутся про- фессиональные игры. В процессе работы над этой главой вводите и запускайте код, чтобы понять, как каждый блок кода участвует в общем игровом процессе. Экспериментируйте с разными значениями и настройками, чтобы лучше понять, как следует организовать взаимодействие с пользователем в ваших собственных играх. ПРИМЕЧАНИЕ Игра Alien Invasion состоит из множества файлов; создайте в своей системе новый каталог с именем alien_invasion . Чтобы команды import работали правильно, все файлы проекта должны находиться в этой папке . Кроме того, если вы уверенно работаете с системами контроля версий, возможно, вам стоит использовать такую систему в этом проекте . Если ранее вы никогда не использо- вали системы контроля версий, обратитесь к краткому обзору в приложении Г . Создание проекта игры 241 Планирование проекта Построение крупного проекта должно начинаться не с написания кода, а с планиро- вания. План поможет вам сосредоточить усилия в нужном направлении и повысит вероятность успешного завершения проекта. Итак, напишем общее описание игрового процесса. Хотя это описание не затра- гивает все аспекты игры, оно дает достаточно четкое представление о том, с чего начинать работу: Каждый игрок управляет кораблем, который находится в середине нижнего края экрана. Игрок перемещает корабль вправо и влево клавишами управле- ния курсором; клавиша «пробел» используется для стрельбы. В начале игры флот пришельцев находится в верхней части экрана и постепенно опускается вниз, также смещаясь в сторону. Игрок выстрелами уничтожает пришельцев. Если ему удается сбить всех пришельцев, появляется новый флот, который движется быстрее предыдущего. Если пришелец сталкивается с кораблем игрока или доходит до нижнего края экрана, игрок теряет корабль. Если игрок теряет все три корабля, игра заканчивается. В первой фазе разработки мы создадим корабль, который может двигаться вправо и влево. Корабль должен стрелять из пушки, когда игрок нажимает клавишу «про- бел». Когда это поведение будет реализовано, мы можем заняться пришельцами и доработкой игрового процесса. Установка Pygame Прежде чем браться за программирование, установите пакет Pygame. Модуль pip помогает управлять загрузкой и установкой пакетов Python. Чтобы установить Pygame, введите следующую команду в приглашении терминала: $ python -m pip install --user pygame Эта команда приказывает Python запустить модуль pip и включить пакет pygame в установленный экземпляр Python текущего пользователя. Если для запуска программ или сеанса терминала вместо python используется другая команда (на- пример, python3 ), команда будет выглядеть так: $ python3 -m pip install --user pygame ПРИМЕЧАНИЕ Если команда не работает в macOS, попробуйте снова выполнить коман ду без флага --user . Создание проекта игры Построение игры начнется с создания пустого окна Pygame, в котором позднее будут отображаться игровые элементы, — прежде всего корабль и пришельцы. 242 Глава 12 • Инопланетное вторжение Также игра должна реагировать на действия пользователя, назначать цвет фона и загружать изображение корабля. Создание окна Pygame и обработка ввода Начнем с создания пустого окна Pygame, для чего будет создан класс, представля- ющий окно. Создайте в текстовом редакторе новый файл и сохраните его с именем alien_invasion .py , после чего введите следующий код: alien_invasion.py import sys import pygame class AlienInvasion: """Класс для управления ресурсами и поведением игры.""" def __init__(self): """Инициализирует игру и создает игровые ресурсы.""" ❶ pygame.init() ❷ self.screen = pygame.display.set_mode((1200, 800)) pygame.display.set_caption("Alien Invasion") def run_game(self): """Запуск основного цикла игры.""" ❸ while True: # Отслеживание событий клавиатуры и мыши. ❹ for event in pygame.event.get(): ❺ if event.type == pygame.QUIT: sys.exit() # Отображение последнего прорисованного экрана. ❻ pygame.display.flip() if __name__ == '__main__': # Создание экземпляра и запуск игры. ai = AlienInvasion() ai.run_game() Программа начинается с импортирования модуля sys и pygame . Модуль pygame содержит функциональность, необходимую для создания игры, а модуль sys за- вершает игру по команде игрока. Игра Alien Invasion начинается с класса с именем ALienInvasion . В методе __init__() функция pygame.init() инициализирует настройки, необходимые Pygame для нормальной работы . В точке вызов pygame.display.set_mode() создает окно, в котором прорисовываются все графические элементы игры. Ар- гумент (1200, 800) представляет собой кортеж, определяющий размеры игрового окна. (Вы можете изменить эти значения в соответствии с размерами своего мони- Создание проекта игры 243 тора.) Объект окна присваивается атрибуту self.screen , что позволяет работать с ним во всех методах класса. Объект, присвоенный self.screen , называется поверхностью (surface). Поверхность в Pygame представляет часть экрана, на которой отображается игровой элемент. Каждый элемент в игре (например, пришелец или корабль игрока) представлен поверхностью. Поверхность, возвращаемая display.set_mode() , представляет все игровое окно. При активизации игрового цикла анимации эта поверхность авто- матически перерисовывается при каждом проходе цикла, чтобы она обновлялась всеми изменениями, обусловленными вводом от пользователя. Процессом игры управляет метод run_game() . Метод содержит непрерывно выпол- няемый цикл while , который содержит цикл событий и код, управляющий об- новлениями экрана. Событием называется действие, выполняемое пользователем во время игры (например, нажатие клавиши или перемещение мыши). Чтобы наша программа реагировала на события, мы напишем цикл событий для прослушивания событий и выполнения соответствующей операции в зависимости от типа произо- шедшего события. Этим циклом событий является цикл for в точке . Для получения доступа к событиям, обнаруженным Pygame, используется метод pygame.event.get() . Он возвращает список событий, произошедших с момента последнего вызова этой функции. При любом событии клавиатуры или мыши от- рабатывает цикл for . В этом цикле записывается серия команд if для обнаружения и обработки конкретных событий. Например, когда игрок щелкает на кнопке за- крытия игрового окна, программа обнаруживает событие pygame.QUIT и вызывает метод sys.exit() для выхода из игры . Вызов pygame.display.flip() приказывает Pygame отобразить последний отрисо- ванный экран. В данном случае при каждом выполнении цикла while будет отобра- жаться пустой экран со стиранием старого экрана, так что виден будет только новый экран. При перемещении игровых элементов вызов pygame.display.flip() будет по- стоянно обновлять экран, отображая игровые элементы в новых позициях и скрывая старые изображения; таким образом создается иллюзия плавного движения. В последней строке файла создается экземпляр игры, после чего вызывается метод run_game() . Вызов run_game() заключается в блок if , чтобы он выполнялся только при прямом вызове функции. Запустив файл alien_invasion .py , вы увидите пустое окно Pygame. Назначение цвета фона Pygame по умолчанию создает черный экран, но это банально — выберем другой цвет фона. Это делается в методе __init__() : |