Глава 9. Отладка и тестирование Python
Содержание
Эта последняя глава является введением в две важные темы инженерии программного обеспечения - отладка и тестирование - которые являются важныи шагами в процессе разработки программного обеспечения.
Наша первая часть данной главы сосредоточена на отладке кода. Некий баг является ошибкой в программе и может вызывать различные проблемы, которые могут быть более или менее серьёзными в зависимости от ситуации. Для поддержки программистов при поиске ошибок (багов) применяются особые программные инструменты, имеющие название отладчиками (debuggers); применяя эти программные инструменты, у нас имеется возможность поиска ошибок или неверной работы в рамках программы за счёт получения преимуществ от особых функций отладчиков, неких действий, которые имеются в точности для выявления той части программного обеспечения, которая подвержена ошибке.
В нашей второй части основной темой выступает тестирование программного обеспечения: это процесс для выявления отсутствия несовершенств, незавершённости а также надёжности в разрабатываемом программном продукте.
В этом контексте мы будем изучать три наиболее важных инструмента Python для отладки исполняемого кода. А именно,
winpdb-reborn
, который предлагает отладку в неком инструменте визуализации;
pdb
, отладчика из стандартной библиотеки Python; а также
rpdb
, в котором r
это сокращение для
слова удалённый (remote), что означает что это отладчик кода с некой удалённой машины.
Что касается тестирования программного обеспечения, мы изучим следующие инструменты:
unittest
и nose
.
Это инфраструктуры для разработки тестирований элементов, при котором элемент это минимальный компонент некой программы внутри какой- то независимой операции.
В этой главе мы рассмотрим такие темы:
-
Что такое отладка?
-
Что такое тестирование программного обеспечения?
-
Отладка с применением Winpdb Reborn
-
Взаимодействие с
pdb
-
Реализация
rpdb
для отладки -
Работа с
unittest
-
Проверка приложения с помощью
nose
Термин отладка (debugging) указывает на деятельность по выявлению той части кода, в которой одна или более ошибок (багов) определены в программном обеспечении при его применении.
Такая ошибка может локализоваться на этапе проверки определённой программы; то есть когда она всё ещё находится в стадии своей разработки и ещё пока не готова к использованию конечным пользователем, либо же в более позднем применении этой программы. После обнаружения такой ошибки следует этап отладки и выявления той части программного обеспечения в котором и расположена эта ошибка, что порой очень сложно.
В наши дни такие действия поддерживаются особыми приложениями и отладчиками, которые показывают своему программисту исполнение инструкций программного обеспечения в пошаговом режиме, делая возможным одновременный просмотр и анализ входных и выходных данных самой этой программы.
Прежде чем такие инструменты стали возможными для деятельности по выявлению и исправлению ошибок (и даже сейчас, при их отсутствии), самый простой (но также и наименее эффективной) технологией инспектирования кода была печать файла или вывод инструкций на экран во время исполнения изучаемой программы.
Отладка является одной из наиболее важных операций при разработке программ. Она зачастую чрезвычайно сложны в силу сложности разрабатываемого программного обеспечения. К тому же это весьма деликатная работа из- за риска внесения новых ошибок или поведения, которое не находится в русле с тем, которое желательно для нас при попытке исправления тех, для коих и предпринимались данные действия.
Хотя задача совершенствования программного обеспечения с применением отладки всякий раз уникальна и представляет собой отдельную историю, всегда применимы некие общие принципы. В частности, в контексте программных приложений можно выделить следующие четыре этапа отладки, суммируемые следующей схемой:
Рисунок 9-1
Фазы отладки
Выявление ошибки
Выявление того компонента, в котором находится ошибка
Выявление условия возникновения ошибки
Проектирование и реализация устранения ошибки
Замечание | |
---|---|
Конечно, Python предлагает своим разработчикам многочисленные средства отладки (обратитесь к списку отладчиков Python). В этой главе мы
остановимся на |
Как уже упоминалось во введении к данной главе, тестирование программного обеспечения является процессом, применяемым для выявления недостатков корректности, полноты и надёжности в разрабатываемом программном обеспечении.
Подобной деятельностью мы, следовательно, желаем гарантировать высокое качество своего продукта выполняя поиск дефектов или некой последовательности инструкций и процедур, которые при выполнении с определёнными входными данными и в конкретных операционных средах приводят к сбоям в работе. Неисправность это такое поведение рассматриваемого программного обеспечения, которое не ожидается его пользователем; следовательно, оно отличается от заявленной спецификации, а также от заданных явно или подразумеваемых требований, определённых для данного приложения.
Основной целью проверки, таким образом, является выявление дефектов через неверную работу с тем, чтобы минимизировать наличия вероятности таких сбоев, происходящих при обычном применении данного программного продукта. Тестирование не устанавливает того, что некий продукт правильно работает при всех возможных условиях выполнения, но оно способно высветить дефекты для определённых условий.
Фактически, исходя из невозможности проверки всех комбинаций на входе и всех возможных программных и аппаратных сред, в которых способно работать данное приложение, вероятность неисправностей не может быть сведена к нулю, но она должна быть уменьшена до некого минимального значения, чтобы оказываться приемлемой для своего пользователя.
Неким особым видом тестирования является поэлементные проверки (которые мы изучим в данной главе), основная цель которых состоит в изолировании каждой части программы и демонстрации её правильности и завершённости в данной реализации. Это также быстро выявляет всякие дефекты для их быстрого исправления прежде чем приступать к интеграции.
Более того, тестирование элементов снижает общую стоимость - в плане времени и ресурсов - выявления и исправления недочётов, по сравнению с достижением того же самого результата выполняя проверки для всего приложения целиком.
Winpdb Reborn является одним из наиболее важных и широко известных отладчиков Python. Основной сильной стороной данного отладчика является управление процессом отладки кода на основе потока.
Замечание | |
---|---|
Winpdb Reborn основывается на отладчике RPDB2, в то время как Winpdb выступает интерфейсом GUI к RPDB2 (подробнее...). |
Наиболее широко применяемым способом установки Winpdb Reborn (release 2.0.0 dev5)
является установка через pip
, поэтому в своей консоли вам требуется набрать следующее:
C:\> pip install winpdb-reborn
К тому же, если вы ещё не установили wxPython для своего дистрибутива Python, тогда вам следует установить и его. wxPython является кросс- платформным инструментарием GUI для языка программирования Python.
Замечание | |
---|---|
Для Python версий 2.x обратитесь к странице проекта. Для Python версий 3.xx, wxPython автоматически устанавливается в виде
зависимости через |
В своём следующем разделе мы изучим основные свойства и графический интерфейс Winpdb Reborn посредством простого примера его использования.
Допустим, мы желаем проанализировать следующее приложение Pytthon, которое использует библиотеку потоков.
Это некий пример, крайне близкий к тому, который мы уже описывали ранее в разделе
Задание подкласса потока из
Главы 2, Параллельность на основе потоков. В своём следующем
примере мы применяем класс MyThreadClass
для создания последовательности
выполнения трех потоков и управления ими. Вот код для отладки целиком:
import time
import os
from random import randint
from threading import Thread
class MyThreadClass (Thread):
def __init__(self, name, duration):
Thread.__init__(self)
self.name = name
self.duration = duration
def run(self):
print ("---> " + self.name + \
" running, belonging to process ID "\
+ str(os.getpid()) + "\n")
time.sleep(self.duration)
print ("---> " + self.name + " over\n")
def main():
start_time = time.time()
# Thread Creation
thread1 = MyThreadClass("Thread#1 ", randint(1,10))
thread2 = MyThreadClass("Thread#2 ", randint(1,10))
thread3 = MyThreadClass("Thread#3 ", randint(1,10))
# Thread Running
thread1.start()
thread2.start()
thread3.start()
# Thread joining
thread1.join()
thread2.join()
thread3.join()
# End
print("End")
#Execution Time
print("--- %s seconds ---" % (time.time() - start_time))
if __name__ == "__main__":
main()
Давайте рассмотрим следующие этапы:
-
Откроем свою консоль и наберём необходимое название в той папке, которая содержит этот файл примера,
winpdb_reborn_code_example.py
:python -m winpdb .\winpdb_reborn_code_example.py
Замечание Это также работает и в macOS, однако вам потребуется воспользоваться неким построением инфраструктуры Python. Когда вы применяете Winpdb Reborn для Anaconda, просто пользуйтесь
pythonw
вместоpython
для запуска сеанса Winpdb Reborn. -
В случае успешной установки должен открыться GUI Winpdb Reborn:
-
Как мы можем видеть на следующем снимке экрана, мы вставили две точки прерывания (воспользовавшись меню Breakpoints), в обеих отображённых красным строках 12 и 23:
Замечание Для получения дополнительных сведений по точкам прерываний обратитесь к разделу Также ознакомьтесь... в этом рецепте.
-
Оставаясь в окне Source, мы помещаем свою мышку на строку 23, в которой мы поместили свою вторую точку прерывания и нажимаем клавишу
F8
, а затем на клавишуF5
. Наша точка прерывания позволяет исполнить весь код вплоть до выбранной строки. Как вы можете видеть, Namespace указывает что мы рассматриваем значение своего экземпляра классаMyThreadClass
сthread#1
в качестве аргумента:
-
Другим основополагающим свойством этого отладчика является возможность Step Into, которая позволяет инспектировать не только отлаживаемый код, но ещё и библиотечные функции и вызываемые на исполнение подпрограммы.
-
Прежде чем вы начнёте удалять свои предыдущие точки прерывания (Menu | Breakpoints | Clear All), вставьте новую точку прерывания в строке 28:
-
Наконец, нажмите клавишу
F5
и ваше приложение будет выполнено вплоть до точки прерывания в строке 28. -
Затем нажмите
F7
. Здесь ваше окно исходного кода больше не отображает код нашего примера, а вместо этого используемую нами библиотекуthreading
(смотрите снимок экрана внизу). -
Тем самым, наша функциональность Breakpoints совместно с Step Into не только позволяет отлаживать сам код в запросе, но также делает возможным инспектирование всех библиотечных функций и всех применяемых подпрограмм:
В данном примере мы познакомились с инструментом Winpdb Reborn. Это среда отладки (как и все среды в целом) позволяет вам останавливать выполнение программы в точных пунктах, инспектировать стек выполнения, содержимое переменных, состояние созданных объектов и многое иное.
Чтобы пользоваться Winpdb Reborn, всего лишь выполните такие шаги:
-
Установите точки прерывания в своём исходном коде (в окне Source).
-
Инспектируйте необходимые функции через его функциональность Step Into .
-
Просматривайте состояние переменных (в окне Namespace) и стека выполнения (в окне Stack).
Наши точки прерывания устанавливаются простым двойным нажатием на нужную строку при помощи левой кнопки мыши (вы увидите выбранные строки окрашенными красным). В качестве общего предостережения предлагается не применять множество команд в одной и той же строке; в противном случае будет невозможно ассоциировать точки прерывания с одной из них.
Когда мы пользуемся правой кнопкой мыши, мы можем выбирать disable breakpoints (отключение точек прерывания) без их удаления (красная подсветка исчезнет). Для удаления всех точек прерывания вместо этого, воспользуйтесь командой Clear All, которая присутствует в меню Breakpoints, как это уже упоминалось ранее.
По достижению самой первой точки прерывания, неплохо держать на глазу следующие отображения в этой точке программы для своего анализа:
-
Просмотр Stack отображает содержимое стека выполнения, в котором появились в данный момент все экземпляры разнообразных методов. Обычно тот, который расположен в самом низу данного стека это основной метод, а тот что находится в вершине стека это тот метод, который содержит достигнутую нами точку прерывания.
-
Просмотр Namespace отображает значение локальных переменных и позволяет вам инспектировать их значения. Когда такие переменные ссылаются на объекты, имеется возможность отыскать значение уникального идентификатора этого объекта и проинспектировать его состояние.
В целом выполнением программы можно управлять в различных режимах, связанных с соответствующей иконкой
(или через клавиши Fx
), присутствующей в полосе команд
Winpdb Reborn.
Наконец, мы укажем следующие важные методы исполнения:
-
Step Into (клавиша
F7
): Возобновляет исполнение вашей программы по одной строке за раз, а также вызывает библиотечные методов или подпрограммы. -
Return (клавиша
F12
): Позволяет вам продолжить выполнение в точности в той точке, в которой была активирована функция Step Into. -
Next (клавиша
F6
): Возобновляет необходимое выполнение данной программы по одной строке за раз без остановки во всех вызываемых методах. -
Run to Line (клавиша
F8
): Выполняет данную программу вплоть до её останова (в ожидании новых команд) до указанной строки.
Как мы уже видели в GUI Winpdb Reborn, наш подразделяется на пять основных окон:
-
Namespace: В этом окне отображаются имена логических элементов, которыми являются различные переменные и идентификаторы, определяемые в вашей программе и используемые в исходном файле.
-
Threads: Отображается текущий поток исполнения, а также описываются поля TID (сокращение от Thread ID), название этого потока и значение состояния данного потока.
-
Stack: Здесь отображается для анализа стек выполнения изучаемой программы. Стек также известен ккак структура данных LIFO ( Last In, First Out), поскольку самый последний вставленный в него элемент будет удаляться первым. Когда программа вызывает некую функцию, эта вызываемая функция должна знать как возвращать управление вызывавшей стороне, а потому значение адреса возврата вызываемой функции помещается в имеющемся стеке выполнения программы. Такой стек исполнения программы также содержит необходимую память для локальных переменных, применяемых при каждом вызове этой функции.
-
Console: Это интерфейс командной строки, который делает возможным текстовое взаимодействие между пользователем и Winpdb Reborn.
-
Source: Данное окно отображает исходный код отладки. Проматывая строки кода, также имеется возможность вставки точек прерывания нажатием
F9
когда вы заинтересовались кодом в данной строке.
Точки прерывания являются очень фундаментальным инструментом отладки. На практике они позволяют вам выполнять программу, но при этом с возможностью её прерывания в представляющем интерес месте или при возникновении некого условия для получения сведений об исполняемой программе.
Существует множество стратегий отладки. Вот некоторые из них:
-
Воспроизведение ошибки: Выявление тех входных данных, которые приводят к ней.
-
Упрощение ошибки: Определение самых простых данных, вызывающих её.
-
Разделяй и властвуй: Выполнение основной последовательности в пошаговом режиме вплоть до появления аномалий. Тот метод, который вызывает её, является самым последним выполненным, прежде чем стало возможным обнаружить данную проблему, поэтому мы можем повторить отладку выполняя пошагово такой конкретный вызов и продолжая снова в пошаговом режиме следуя инструкциям данного метода.
-
Действую осознанно: В процессе отладки вы постоянно сравниваете текущие значения своих переменных с тем что ожидали обнаружить.
-
Проверяйте все подробности: Не упускайте подробности в процессе отладки. Будет лучше сделать некую пометку когда вы заметите какие- то расхождения в своём исходном коде.
-
Исправляйте выявленные ошибки: Исправляйте свои ошибки когда вы уверены что разобрались также с самой проблемой.
Хорошее руководство по Winpdb Reborn можно найти по следующей ссылке.
pdb
является модулем Python для выполнения интерактивной отладки.
Основные функциональные возможности pdb
таковы:
-
Применение контрольных точек
-
Интерактивная обработка исходного кода в построковом режиме
-
Анализ кадра стека
Данный отладчик реализуется посредством класса pdb
. По этой причине его можно
просто расширять новыми функциональными возможностями.
Никакая установка pdb
не требуется, ибо он является частью самой стандартной
библиотеки Python. Его можно запустить следуя таким основным шаблонам использования:
-
Взаимодействуя через имеющуюся командную строку
-
Применяя интерпретатор Python
-
Вставляя некие директивы (то есть операторы
pdb
) в свой код для отладки
Взаимодействие через командную строку
Самый простой метод, это просто передать название своей программы в качестве входного параметра. Например, для
нашей программы pdb_test.py
это выглядит так:
class Pdb_test(object):
def __init__(self, parameter):
self.counter = parameter
def go(self):
for j in range(self.counter):
print ("--->",j)
return
if __name__ == '__main__':
Pdb_test(10).go()
При выполнении через командную строку, pdb
загружает необходимый
файл исходного кода для анализа и останавливает его выполнение в самом первом обнаруженном операторе. В этом
случае наша отладка остановлена на строке 1
(то есть на определении самого
класса Pdb_test
):
python -m pdb pdb_test.py
> .../pdb_test.py(1)<module>()
-> class Pdb_test(object):
(Pdb)
Применение интерпретатора Python
Модуль pdb
можно применять в интерактивном режиме при помощи команды
run()
:
>>> import pdb_test
>>> import pdb
>>> pdb.run('pdb_test.Pdb_test(10).go()')
> <string>(1)<module>()
(Pdb)
В данном случае наш оператор run()
взят из данного отладчика и остановит
выполнение перед оценкой первого выражения.
Вставка директив в сам код для отладки
Для продолжительно работающих процессов, в которых проблема может возникать намного позднее при исполнении, будет
гораздо более удобно начинать отладку в программе при помощи директивы pdb
set_trace()
:
import pdb
class Pdb_test(object):
def __init__(self, parameter):
self.counter = parameter
def go(self):
for j in range(self.counter):
pdb.set_trace()
print ("--->",j)
return
if __name__ == '__main__':
Pdb_test(10).go()
set_trace()
может вызываться из любой точки подлежащей отладке программы. Например,
он может вызываться при возникновении некоторого условия, обработке исключительной ситуации или в конкретной ветви
инструкций.
В данном случае получаемый вывод таков:
-> print ("--->",j)
(Pdb)
Выполнение кода останавливается, причём в точности вслед за выполнением оператора
pdb.set_trace()
.
Для взаимодействия с pdb
вам требуется применять его язык, который позволяет
вам перемещаться по вашему коду и изменять необходимые значения ваших переменных, вставлять точки прерывания или
перемещаться по вызовам в стеке:
-
Воспользуйтесь командой
where
(либо в качестве альтернативы её компактной формой,w
) для того чтобы увидеть в какой строке кода идёт выполнение и значение стека вызова. В данном случае это строка 17 в методеgo()
из модуляpdb_test.py
:> python -m pdb pdb_test.py -> class Pdb_test(object): (Pdb) where c:\python35\lib\bdb.py(431)run() -> exec(cmd, globals, locals) <string>(1)<module>() (Pdb)
-
Проинспектируйте те строки кода, которые расположены рядом с текущим местоположением (указанным стрелкой) при помощи
list
. В установленном по умолчанию режиме вокруг текущей строки перечисляются11
строк (пять до и пять после):(Pdb) list 1 -> class Pdb_test(object): 2 def __init__(self, parameter): 3 self.counter = parameter 4 5 def go(self): 6 for j in range(self.counter): 7 print ("--->",j) 8 return 9 10 if __name__ == '__main__': 11 Pdb_test(10).go()
-
Когда
list
получает два параметра, они интерпретируются как первая и последняя строки для отображения:(Pdb) list 3,9 3 self.counter = parameter 4 5 def go(self): 6 for j in range(self.counter): 7 print ("--->",j) 8 return 9
-
Для перемещения к более старым кадрам стека применяйте
up
(u
), а для движения к последним кадрам в стекеdown
(d
):(Pdb) up > <string>(1)<module>() (Pdb) up > c:\python35\lib\bdb.py(431)run() -> exec(cmd, globals, locals) (Pdb) down > <string>(1)<module>() (Pdb) down >....\pdb_test.py(1)<module>() -> class Pdb_test(object): (Pdb)
Отладка выполняется в соответствии с установленным потоком исполняемой программы (трассировка). В каждой строке кода, кодировщик отображает выполняемые инструкциями операции в режиме реального времени и те значения, которые записаны в установленных переменных. Таким образом разработчик имеет возможность проверять что всё работает верно и определять причину неисправности.
Всякий язык программирования обладает своим собственным отладчиком. Однако нет никакого правильного отладчика для всех языков программирования, так как каждый язык программирования обладает своими собственными синтаксисом и грамматикой. Отладчик пошагово выполняет представленный исходный код. Таким образом, этот отладчик обязан знать все правила этого языка, как и его компилятор.
Наиболее полезные команды pdb
, совместно с их краткими формами для того
чтобы держать их в памяти при отладке Python таковы:
Команда | Действие |
---|---|
|
Выводит на печать список аргументов текущей функции |
|
Создаёт точку прерывания (требуются параметры) |
|
Продолжает выполнение программы |
|
Приводит перечень команд (или подсказку) для некой команды (или параметра) |
|
Устанавливает следующую строку для выполнения |
|
Выводит на печать исходный код вокруг текущей строки |
|
Продолжает выполнение пока не возникнет следующая строка в текущей функции или не будет выполнен возврат |
|
Выполняет текущую строку, останавливаясь при первом возможном случае |
|
Улучшенный вывод на печать значения данного выражения |
|
Прерывает |
|
Продолжает выполнение врлоть до возврата из текущей функции |
Для получения дополнительных сведений относительно pdb
вы можете просмотреть
занимательное видео- руководство.
При некоторых обстоятельствах бывает более удобной отладка кода из удадённого местоположения, которое не расположено
в той же самой машине, в которой запущен ваш отладчик. Для этой цели был разработан
rpdb
. Он является некой обёрткой для pdb
,
которая применяет сокет TCP для взаимодействия с внешним миром.
Установка rpdb
вначале требует самого основного шага с применением
pip
. Для ОС Windows просто наберите следующее:
C:\> pip install rpdb
Далее вам следует убедиться, что у вас имеется работающим и включённым в вашей машине клиент
telnet. В Windows 10 когда вы откроете приглашение
Командной строки и наберёте telnet
, ваша ОС ответит сообщением об ошибке,
так как он не представлен при выполняемой по умолчанию установке.
Давайте рассмотрим как установить его за несколько протсых шагов:
-
Откройте приглашение Вомандной строки в режиме Администратора.
-
Кликните по кнопке поиска и наберите
cmd
. -
В появившемся списке кликните правой кнопкой по элементу приглашения Командной строки и выберите Run as Administrator.
-
Затем при работе в качестве Администратора в приглашении Командной строки наберите следующую команду:
dism /online /Enable-Feature /FeatureName:TelnetClient
-
Подождите несколько минут пока не завершится данная установка. В случае успешного процесса вы обнаружите следующее:
-
Теперь вы можете применять telnet непосредственно из этого приглашения. Набрав
telnet
вы получите появившимся такое окно:
В своём следующем примере мы рассмотрим как запускать удалённую отладку при помощи
rpdb
.
Давайте выполним такие шаги:
-
Рассмотрим такой образец кода:
import threading def my_func(thread_number): return print('my_func called by thread N° {}'.format(thread_number)) def main(): threads = [] for i in range(10): t = threading.Thread(target=my_func, args=(i,)) threads.append(t) t.start() t.join() if __name__ == "__main__": main()
-
Для использования
rpdb
вам требуется вставить следующие строки кода (сразу вслед за операторомimport threading
). Фактически, эти три строки кода включают использованиеrpdb
через порт4444
удалённого клиента с IP адресом127.0.0.1
:import rpdb debugger = rpdb.Rpdb(port=4444) rpdb.Rpdb().set_trace()
-
Если вы запустите наш образец кода после вставки этих трёх строк, который включили применение
rpdb
, тогда вы должны обнаружить в приглашении своей Командной строки следующее сообщение:pdb is running on 127.0.0.1:4444
-
Затем вы можете переключиться на отладку этого образца кода удалённо осуществив такое подключение через telnet:
telnet localhost 4444
-
Должно открыться следующее окно:
-
В своём образце кода обратите внимание на строку 7. Этот код не выполняется, он всего лишь ожидает выполнения некой инструкции:
-
Например, в нашем случае мы выполняем этот код и всякий раз повторно набираем оператор
next
:(Pdb) next > c:\users\giancarlo\desktop\python parallel programming cookbook 2nd edition\python parallel programming new book\chapter_x- code debugging\rpdb_code_example.py(10)<module>() -> def main(): (Pdb) next > c:\users\giancarlo\desktop\python parallel programming cookbook 2nd edition\python parallel programming new book\chapter_x- code debugging\rpdb_code_example.py(18)<module>() -> if __name__ == "__main__": (Pdb) next > c:\users\giancarlo\desktop\python parallel programming cookbook 2nd edition\python parallel programming new book\chapter_x- code debugging\rpdb_code_example.py(20)<module>() -> main() (Pdb) next my_func called by thread N 0 my_func called by thread N 1 my_func called by thread N 2 my_func called by thread N 3 my_func called by thread N 4 my_func called by thread N 5 my_func called by thread N 6 my_func called by thread N 7 my_func called by thread N 8 my_func called by thread N 9 --Return-- > c:\users\giancarlo\desktop\python parallel programming cookbook 2nd edition\python parallel programming new book\chapter_x- code debugging\rpdb_code_example.py(20)<module>()->None -> main() (Pdb)
После того как наша программа завершится, вы всё ещё можете продолжать работать в разделе отладки.
Теперь давайте в своём следующем разделе рассмотрим как работает rpdb
.
В нашем предыдущем разделе мы наблюдали как просто перемещаемся по своему коду при помощи оператора
next
, который продолжает исполнение вплоть до достижения следующей строки в
текущей функции или возврата из неё.
Для применения rpdb
применяйте следующие шаги:
-
Импортируйте библиотеку
rpdb
:import rpdb
-
Задайте параметр
debugger
, который определяет значение порта telnet для подключения к исполнению этого отладчика:debugger = rpdb.Rpdb(port=4444)
-
Вызовите директиву
set_trace()
, которая позволяет войти в необходимый режим отладчика:rpdb.Rpdb().set_trace()
В нашем случае мы поместили директиву set_trace()
сразу после своего
экземпляра debugger
. На практике мы можем помещать её в любом месте своего кода;
например, когда удовлетворяется некое условие, или внутри некого раздела, управляющего какой- то исключительной
ситуацией.
Наш второй шаг, вместо этого, состоит в открытии приглашения Командной строки и запуске
telnet
, с установкой того самого значения порта, которое определено при
задании параметра debugger
внутри образца нашего кода:
telnet localhost 4444
Имеется возможность взаимодействия с установленным отладчиком rpdb
при помощи небольшого командного языка, который позволяет перемещаться между вызовами в стеке, опрашивать и
изменять значения установленных переменных и управлять тем способом, которым этот отладчик выполняет свою
собственную программу.
Полный перечень команд, при помощи которых вы можете взаимодействовать в rpdb
может быть отображён через набор команды help
в соответствующем приглашении
Pdb
:
> c:\users\giancarlo\desktop\python parallel programming cookbook 2nd edition\python parallel programming new book\chapter_x- code debugging\rpdb_code_example.py(7)<module>()
-> def my_func(thread_number):
(Pdb) help
Documented commands (type help <topic>):
========================================
EOF c d h list q rv undisplay
a cl debug help ll quit s unt
alias clear disable ignore longlist r source until
args commands display interact n restart step up
b condition down j next return tbreak w
break cont enable jump p retval u whatis
bt continue exit l pp run unalias where
Miscellaneous help topics:
==========================
pdb exec
(Pdb)
Помимо наиболее полезных команд эта справка также содержит сведения о том как мы можем вставлять точки прерывания в свой код:
-
Наберите
b
и значение строки для установки некой точки прерывания. В нашем случае точки прерывания устанавливаются в строках5
и10
:(Pdb) b 5 Breakpoint 1 at c:\users\giancarlo\desktop\python parallel programming cookbook 2nd edition\python parallel programming new book\chapter_x- code debugging\rpdb_code_example.py:5 (Pdb) b 10 Breakpoint 2 at c:\users\giancarlo\desktop\python parallel programming cookbook 2nd edition\python parallel programming new book\chapter_x- code debugging\rpdb_code_example.py:10
-
Для отображения всех реализованных точек прерывания достаточно просто набрать команду
b
:(Pdb) b Num Type Disp Enb Where 1 breakpoint keep yes at c:\users\giancarlo\desktop\python parallel programming cookbook 2nd edition\python parallel programming new book\chapter_x- code debugging\rpdb_code_example.py:5 2 breakpoint keep yes at c:\users\giancarlo\desktop\python parallel programming cookbook 2nd edition\python parallel programming new book\chapter_x- code debugging\rpdb_code_example.py:10 (Pdb)
Всякой вновь добавляемой точке прерывания присваивается некий численный идентификатор. Он указывает нам применение
включения, запрета и интерактивного удаления точек прерывания. Чтобы запретить точку прерывания воспользуйтесь
командой disable
, которая сообщит установленному отладчику о том чтобы не
останавливаться по достижению данной строки. Эта точка прерывания не забывается, а просто игнорируется.
На этом сайте вы можете найти
сведения по pdb
и далее по rpdb
.
В своих двух последующих разделах мы рассмотрим некоторые инструментов Python, которые применяются для реализации тестирования элементов:
-
unittest
-
nose
Нужный нам модуль unittest
предоставляется стандартной библиотекой Python.
Она обладает обильным набором инструментов и процедур для выполнения тестирования элементов. В этом разделе мы кратко
рассмотрим то как работает unittest
.
Тестирование элемента состоит из двух частей:
-
Собственно код для управления так называемой тестовой системой
-
Тест сам по себе
Простейший модуль unittest
можно получить через подкласс
TestCase
, для которого должны переписываться или добавляться соответствующие
методы.
Простейший модуль unittest
может ьыть составлен следующим образом:
import unittest
class SimpleUnitTest(unittest.TestCase):
def test(self):
self.assertTrue(True)
if __name__ == '__main__':
unittest.main()
Для запуска модуля unittest
вам требуется включить
unittest.main ()
, в то время как у нас имеется некий единственный метод,
test()
, который отказывает если
True
примет когда- либо значение False
.
Запустив наш предыдущий пример, вы получите следующий результат:
-----------------------------------------------------------
Ran 1 test in 0.005s
OK
Наш тест был успешен, тем самым выдав нам надлежащий результат, OK
.
В своём следующем разделе мы более подробно обсудим как работает наш модуль
unittest
. В частности, мы желаем изучить возможные результаты
модульного тестирования.
Давайте рассмотрим как мы можем охарактеризовать результаты проверки на данном примере:
-
Импортируем относящийся к делу модуль:
import unittest
-
Определяем свой класс
outcomesTest
, который имеет подклассTestCase
в качестве своего аргумента:class OutcomesTest(unittest.TestCase):
-
Самым первым определяемым нами методом является
testPass
:def testPass(self): return
-
Вот наш метод
TestFail
:def testFail(self): self.failIf(True)
-
Ещё у нас имеется метод
TestError
:def testError(self): raise RuntimeError('test error!')
-
Наконец, мы обладаем функцией
main
, для которой мы повторно вызываем свою процедуру:if __name__ == '__main__': unittest.main()
В данном примере показаны возможные выдачи проверки модуля в результате
unittest
.
Вот все возможные выводы:
-
ERROR
: Выполненный тест возбудил исключительную ситуациюAssertionError
. Нет никакого способа в явном виде пройти проверку, поэтому состояние данного теста зависит от присутствия (или отсутствия) некой исключительной ситуации. -
FAILED
: Данная проверка не пройдена и возбуждена исключительная ситуацияAssertionError
. -
OK
: Проверка прошла успешно.
Наш вывод таков:
===========================================================
ERROR: testError (__main__.OutcomesTest)
-----------------------------------------------------------
Traceback (most recent call last):
File "unittest_outcomes.py", line 15, in testError
raise RuntimeError('Errore nel test!')
RuntimeError: Errore nel test!
===========================================================
FAIL: testFail (__main__.OutcomesTest)
-----------------------------------------------------------
Traceback (most recent call last):
File "unittest_outcomes.py", line 12, in testFail
self.failIf(True)
AssertionError
-----------------------------------------------------------
Ran 3 tests in 0.000s
FAILED (failures=1, errors=1)
Большинство проверок подтверждают истинность условия. Имеются различные способы написания проверок, которые
удостоверяются в истинности в зависимости от взгляда их автора на то, прошёл ли проверку желаемый результат данного
кода. Когда такой код выдаёт результат, которое можно рассматривать как истинное, следует применять методы
failUnless()
и assertTrue()
. Когда
этот код выдаёт нечто ложное, тогда имеет смысл применять методы failIf()
и assertFalse()
:
import unittest
class TruthTest(unittest.TestCase):
def testFailUnless(self):
self.failUnless(True)
def testAssertTrue(self):
self.assertTrue(True)
def testFailIf(self):
self.assertFalse(False)
def testAssertFalse(self):
self.assertFalse(False)
if __name__ == '__main__':
unittest.main()
Получаем такие результаты:
> python unittest_failwithmessage.py -v
testFail (__main__.FailureMessageTest) ... FAIL
===========================================================
FAIL: testFail (__main__.FailureMessageTest)
-----------------------------------------------------------
Traceback (most recent call last):
File "unittest_failwithmessage.py", line 9, in testFail
self.failIf(True, 'Il messaggio di fallimento va qui')
AssertionError: Il messaggio di fallimento va qui
-----------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
robby@robby-desktop:~/pydev/pymotw-it/dumpscripts$ python unittest_truth.py -v
testAssertFalse (__main__.TruthTest) ... ok
testAssertTrue (__main__.TruthTest) ... ok
testFailIf (__main__.TruthTest) ... ok
testFailUnless (__main__.TruthTest) ... ok
-----------------------------------------------------------
Ran 4 tests in 0.000s
OK
Как уже упоминалось ранее, когда наша проверка возбуждает некое отличное от AssertionError
исключительное состояние, тогда оно рассматривается как ошибка. Это чрезвычайно полезно для выявления ошибок, которые
происходят при изменении кода, для которого уже имеются соответствующие проверки.
Существуют обстоятельства, однако, при которых вы можете пожелать запустить некий тест для проверки того,
что определённый код на самом деле производит какую- то исключительную ситуаций. Например, в тех случаях, когда
в качестве некого атрибута для какого- то объекта передаётся неверное значение. В подобной ситуации
failUnlessRaises()
сделает ваш код более понятным чем перехват возникающей в
вашем коде исключительной ситуации:
import unittest
def raises_error(*args, **kwds):
print (args, kwds)
raise ValueError\
('Valore non valido:'+ str(args)+ str(kwds))
class ExceptionTest(unittest.TestCase):
def testTrapLocally(self):
try:
raises_error('a', b='c')
except ValueError:
pass
else:
self.fail('Non si vede ValueError')
def testFailUnlessRaises(self):
self.assertRaises\
(ValueError, raises_error, 'a', b='c')
if __name__ == '__main__':
unittest.main()
Результат для обеих функций один и тот же. Тем не менее, получаемый результат для второй проверки, которая
пользуется failUnlessRaises()
, короче:
> python unittest_exception.py -v
testFailUnlessRaises (__main__.ExceptionTest) ... ('a',) {'b': 'c'}
ok
testTrapLocally (__main__.ExceptionTest) ...('a',) {'b': 'c'}
ok
-----------------------------------------------------------
Ran 2 tests in 0.000s
OK
Дополнительные сведения относительно тестирования Python можно найти по следующей ссылке.
nose
является важным модулем Python для задания поэлементных проверок.
Он позволяет нам писать простые функции проверки при помощи подкласса unittest.TestCase
,
а также классы проверок, которые не являются подклассами
unittest.TestCase
.
Установите nose
с помощью
pip
:
C:\> pip install nose
Его исходный пакет можно выгрузить со ссылки и установить при помощи таких шагов:
-
Раскрыть полученный исходный архив.
-
Выполнить
cd
для перехода в новый каталог.
После этого введите такую команду:
C:\> python setup.py install
Одной из сильных сторон nose
является автоматический сбор проверок из
следующего:
-
Исходных файлов Python
-
Обнаруженных в рабочей папке каталогов и пакетов
Для определения того какие проверки запускать, передайте соответствующие названия тестов в его командную строку:
C:\> nosetests only_test_this.py
Такие названия проверок можно определять по именам файлов или модулей, а также в качестве возможного варианта можно указывать тот случай, когда требуется запускать отдельный модуль или файл с указанным названием для случая названия теста с двоеточием. Имена файлов могут быть относительными или абсолютными.
Вот некоторые образцы:
C:\> nosetests test.module
C:\> nosetests another.test:TestCase.test_method
C:\> nosetests a.test:TestCase
C:\> nosetests /path/to/test/file.py:test_function
Вы также можете изменять рабочий каталог для которого nose
ищет
тесты при помощи переключателя -w
:
C:\> nosetests -w /path/to/tests
Обратите внимание, тем не менее, что поддержка множества аргументов для -w
считается обсуждаемой и будет удалена в последующих версиях. Тем не менее, имеется возможность получения того же самого
поведения заданием целевых каталогов без переключателя -w
:
C:\> nosetests /path/to/tests /another/path/to/tests
Последующая индивидуализация выбора проверок и загрузки возможна через использование подключаемых модулей (plugins).
Вывод результатов проверок идентичен тому что делает unittest
, за исключением
дополнительных функциональных возможностей, таких как классы ошибок и свойства поддержки подключаемых модулей, таких как
перехват вывода и декларация самоанализа.
В своём следующем разделе мы рассмотрим некий класс проверок с использованием
nose
.
Давайте исполним следующие шаги:
-
Импортируем относящийся к делу
nose.tools
:from nose.tools import eq_
-
Затем настроим класс
TestSuite
. В нашем случае все методы этого класса проверяются соответствующей функциейeq_
:class TestSuite: def test_mult(self): eq_(2*2,4) def ignored(self): eq_(2*2,3)
Некая модульная проверка может разрабатываться независимо от его разработчика, однако рекомендуется иметь
некий стандартный продукт, такой как unittest
и придерживаться общей
практики проверок.
Как вы можете видеть на следующем примере, нашим методом тестирования задана функция
eq_
. Она аналогична assertEquals
из
unittest
, которая проверяет равенство двух параметров:
def test_mult(self):
eq_(2*2,4)
def ignored(self):
eq_(2*2,3)
Такая практика проверки, несмотря на наилучшие намерения, имеет очевидные ограничения, например, невозможность повторения по времени (скажем, при изменении программного модуля) для так называемых проверок регрессии.
Вот получаемый вывод:
C:\> nosetests -v testset.py
testset.TestSuite.test_mult ... ok
-----------------------------------------------------------
Ran 1 tests in 0.001s
OK
В целом, проверки не способны выявлять все имеющиеся в программе ошибки и это же верно и для модульного тестирования, которое благодаря анализу индивидуальных элементов, по- определению, не способно выявлять ошибки интеграции, проблемы с производительностью и прочие связанные с системой проблемы. Как правило, модульные проверки более действенны при их применении совместно с прочими технологиями тестирования.
Как и все прочие проверки, то же самое модульное тестирование не способно удостоверить отсутствие ошибок, а вместо этого всего лишь выделяет их присутствие.
Тестирование программного обеспечения является задачей комбинаторной математики. Для всякого Булева теста требуется как минимум две проверки, одна для условия истинности и одна для ложного условия. Можно показать, что для всякой строки функционального кода требуются три- пять строк кода проверки. Поэтому невозможно проверять все возможные сочетания любого нетривиального кода без особого инструментария выработки вариантов проверки.
Для достижения желательных результатов от поэлементной проверки необходимо строго придерживаться дисциплины на протяжении всего процесса разработки. Важно отслеживать не только уже разработанные и выполненные проверки, но также и все изменения, вносимые в рабочий код рассматриваемого элемента и всех прочих модулей. Важное значение имеет применение системы контроля версий. Когда более поздняя версия модуля не проходит проверку, которую она проходила ранее, тогда система контроля версий позволяет выделять те изменения кода, которые произведены за этот период.
Имеющее силу руководство для nose
доступно по ссылке
Главе 2,.