Глава 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, rpdb и pdb.

Что такое тестирование программного обеспечения?

Как уже упоминалось во введении к данной главе, тестирование программного обеспечения является процессом, применяемым для выявления недостатков корректности, полноты и надёжности в разрабатываемом программном обеспечении.

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

Основной целью проверки, таким образом, является выявление дефектов через неверную работу с тем, чтобы минимизировать наличия вероятности таких сбоев, происходящих при обычном применении данного программного продукта. Тестирование не устанавливает того, что некий продукт правильно работает при всех возможных условиях выполнения, но оно способно высветить дефекты для определённых условий.

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

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

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

Отладка при помощи Winpdb Reborn

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 автоматически устанавливается в виде зависимости через pip.

В своём следующем разделе мы изучим основные свойства и графический интерфейс 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()
	   

Давайте рассмотрим следующие этапы:

  1. Откроем свою консоль и наберём необходимое название в той папке, которая содержит этот файл примера, winpdb_reborn_code_example.py:

    
    python -m winpdb .\winpdb_reborn_code_example.py
    	   
    [Замечание]Замечание

    Это также работает и в macOS, однако вам потребуется воспользоваться неким построением инфраструктуры Python. Когда вы применяете Winpdb Reborn для Anaconda, просто пользуйтесь pythonw вместо python для запуска сеанса Winpdb Reborn.

  2. В случае успешной установки должен открыться GUI Winpdb Reborn:

     

    Рисунок 9-2


    GUI Windpdb Reborn

  3. Как мы можем видеть на следующем снимке экрана, мы вставили две точки прерывания (воспользовавшись меню Breakpoints), в обеих отображённых красным строках 12 и 23:

     

    Рисунок 9-3


    Точки прерывания кода

    [Замечание]Замечание

    Для получения дополнительных сведений по точкам прерываний обратитесь к разделу Также ознакомьтесь... в этом рецепте.

  4. Оставаясь в окне Source, мы помещаем свою мышку на строку 23, в которой мы поместили свою вторую точку прерывания и нажимаем клавишу F8, а затем на клавишу F5. Наша точка прерывания позволяет исполнить весь код вплоть до выбранной строки. Как вы можете видеть, Namespace указывает что мы рассматриваем значение своего экземпляра класса MyThreadClass с thread#1 в качестве аргумента:

     

    Рисунок 9-4


    Namespace

  5. Другим основополагающим свойством этого отладчика является возможность Step Into, которая позволяет инспектировать не только отлаживаемый код, но ещё и библиотечные функции и вызываемые на исполнение подпрограммы.

  6. Прежде чем вы начнёте удалять свои предыдущие точки прерывания (Menu | Breakpoints | Clear All), вставьте новую точку прерывания в строке 28:

     

    Рисунок 9-5


    Точка прерывания в строке 28

  7. Наконец, нажмите клавишу F5 и ваше приложение будет выполнено вплоть до точки прерывания в строке 28.

  8. Затем нажмите F7. Здесь ваше окно исходного кода больше не отображает код нашего примера, а вместо этого используемую нами библиотеку threading (смотрите снимок экрана внизу).

  9. Тем самым, наша функциональность Breakpoints совместно с Step Into не только позволяет отлаживать сам код в запросе, но также делает возможным инспектирование всех библиотечных функций и всех применяемых подпрограмм:

     

    Рисунок 9-6


    Строка 28 окна исходного кода после выполнения Step Into

Как это работает...

В данном примере мы познакомились с инструментом Winpdb Reborn. Это среда отладки (как и все среды в целом) позволяет вам останавливать выполнение программы в точных пунктах, инспектировать стек выполнения, содержимое переменных, состояние созданных объектов и многое иное.

Чтобы пользоваться Winpdb Reborn, всего лишь выполните такие шаги:

  1. Установите точки прерывания в своём исходном коде (в окне Source).

  2. Инспектируйте необходимые функции через его функциональность Step Into .

  3. Просматривайте состояние переменных (в окне 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

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 вам требуется применять его язык, который позволяет вам перемещаться по вашему коду и изменять необходимые значения ваших переменных, вставлять точки прерывания или перемещаться по вызовам в стеке:

  1. Воспользуйтесь командой 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)
    		
  2. Проинспектируйте те строки кода, которые расположены рядом с текущим местоположением (указанным стрелкой) при помощи 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()
    		
  3. Когда 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
    		
  4. Для перемещения к более старым кадрам стека применяйте 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 таковы:

Таблица 9-1. Основные команды pdb
Команда Действие

args

Выводит на печать список аргументов текущей функции

break

Создаёт точку прерывания (требуются параметры)

continue

Продолжает выполнение программы

help

Приводит перечень команд (или подсказку) для некой команды (или параметра)

jump

Устанавливает следующую строку для выполнения

list

Выводит на печать исходный код вокруг текущей строки

next

Продолжает выполнение пока не возникнет следующая строка в текущей функции или не будет выполнен возврат

step

Выполняет текущую строку, останавливаясь при первом возможном случае

pp

Улучшенный вывод на печать значения данного выражения

quit или exit

Прерывает pdb

return

Продолжает выполнение врлоть до возврата из текущей функции

Дополнительно

Для получения дополнительных сведений относительно pdb вы можете просмотреть занимательное видео- руководство.

Реализация rpdb для отладки

При некоторых обстоятельствах бывает более удобной отладка кода из удадённого местоположения, которое не расположено в той же самой машине, в которой запущен ваш отладчик. Для этой цели был разработан rpdb. Он является некой обёрткой для pdb, которая применяет сокет TCP для взаимодействия с внешним миром.

Приготовление

Установка rpdb вначале требует самого основного шага с применением pip. Для ОС Windows просто наберите следующее:


C:\> pip install rpdb
		

Далее вам следует убедиться, что у вас имеется работающим и включённым в вашей машине клиент telnet. В Windows 10 когда вы откроете приглашение Командной строки и наберёте telnet, ваша ОС ответит сообщением об ошибке, так как он не представлен при выполняемой по умолчанию установке.

Давайте рассмотрим как установить его за несколько протсых шагов:

  1. Откройте приглашение Вомандной строки в режиме Администратора.

  2. Кликните по кнопке поиска и наберите cmd.

  3. В появившемся списке кликните правой кнопкой по элементу приглашения Командной строки и выберите Run as Administrator.

  4. Затем при работе в качестве Администратора в приглашении Командной строки наберите следующую команду:

    
    dism /online /Enable-Feature /FeatureName:TelnetClient
    	   
  5. Подождите несколько минут пока не завершится данная установка. В случае успешного процесса вы обнаружите следующее:

     

    Рисунок 9-7



  6. Теперь вы можете применять telnet непосредственно из этого приглашения. Набрав telnet вы получите появившимся такое окно:

     

    Рисунок 9-8



В своём следующем примере мы рассмотрим как запускать удалённую отладку при помощи rpdb.

Как это сделать...

Давайте выполним такие шаги:

  1. Рассмотрим такой образец кода:

    
    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()
    	   
  2. Для использования rpdb вам требуется вставить следующие строки кода (сразу вслед за оператором import threading). Фактически, эти три строки кода включают использование rpdb через порт 4444 удалённого клиента с IP адресом 127.0.0.1:

    
    import rpdb
    debugger = rpdb.Rpdb(port=4444)
    rpdb.Rpdb().set_trace()
    	   
  3. Если вы запустите наш образец кода после вставки этих трёх строк, который включили применение rpdb, тогда вы должны обнаружить в приглашении своей Командной строки следующее сообщение:

    
    pdb is running on 127.0.0.1:4444
    	   
  4. Затем вы можете переключиться на отладку этого образца кода удалённо осуществив такое подключение через telnet:

    
    telnet localhost 4444
    	   
  5. Должно открыться следующее окно:

     

    Рисунок 9-9



  6. В своём образце кода обратите внимание на строку 7. Этот код не выполняется, он всего лишь ожидает выполнения некой инструкции:

     

    Рисунок 9-10



  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 применяйте следующие шаги:

  1. Импортируйте библиотеку rpdb:

    
    import rpdb
    	   
  2. Задайте параметр debugger, который определяет значение порта telnet для подключения к исполнению этого отладчика:

    
    debugger = rpdb.Rpdb(port=4444)
    	   
  3. Вызовите директиву 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)
		

Помимо наиболее полезных команд эта справка также содержит сведения о том как мы можем вставлять точки прерывания в свой код:

  1. Наберите 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
    		
  2. Для отображения всех реализованных точек прерывания достаточно просто набрать команду 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

Нужный нам модуль 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. В частности, мы желаем изучить возможные результаты модульного тестирования.

Как это сделать...

Давайте рассмотрим как мы можем охарактеризовать результаты проверки на данном примере:

  1. Импортируем относящийся к делу модуль:

    
    import unittest
    	   
  2. Определяем свой класс outcomesTest, который имеет подкласс TestCase в качестве своего аргумента:

    
    class OutcomesTest(unittest.TestCase):
    	   
  3. Самым первым определяемым нами методом является testPass:

    
    def testPass(self):
            return
    	   
  4. Вот наш метод TestFail:

    
    def testFail(self):
        self.failIf(True)
    	   
  5. Ещё у нас имеется метод TestError:

    
    def testError(self):
            raise RuntimeError('test error!')
    	   
  6. Наконец, мы обладаем функцией 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

nose является важным модулем Python для задания поэлементных проверок. Он позволяет нам писать простые функции проверки при помощи подкласса unittest.TestCase, а также классы проверок, которые не являются подклассами unittest.TestCase.

Приготовление

Установите nose с помощью pip:


C:\> pip install nose
		

Его исходный пакет можно выгрузить со ссылки и установить при помощи таких шагов:

  1. Раскрыть полученный исходный архив.

  2. Выполнить 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.

Как это сделать...

Давайте исполним следующие шаги:

  1. Импортируем относящийся к делу nose.tools:

    
    from nose.tools import eq_
    	   
  2. Затем настроим класс 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,.