Рубрики
Без рубрики

Python 102: введение в TDD и Unittest

Получите практические, реальные навыки Python на наших ресурсах и пути

Автор оригинала: Mike Driscoll.

Тестирование кода Python – это что-то новое для меня. Это не требуется, где я работаю, поэтому я не провел много времени, посмотрев в него, помимо чтения книги по теме и читал несколько блогов. Тем не менее, я решил, что было высокое время, когда я проверю это и посмотрим, каково все волнение. В этой статье вы узнаете о тестировании, управляемой на основе разработки (TDD) с Python, используя встроенный Unittest Module Python. Это на самом деле основано на моем одном опыте программирования TDD и пары (спасибо Мэтт и Аарон!). В этой статье мы узнаем, как забить боулинг с Python!

Начиная

Я искал, как забить боулинг в Интернете. Я нашел полезное руководство по About.com Что вы можете прочитать тоже. Как только вы узнаете правила, пришло время написать некоторые тесты. Если вы не знали, идея разработки тестирования, управляемая тестом, заключается в том, что вы пишете тесты, прежде чем писать фактический код. В этой статье мы напишем тест, а затем какой-то код, чтобы пройти тест. Мы будем воплотить и вперед между тестами и кодом написания, пока не сделаем. Для этой статьи мы напишем всего три теста. Давайте начнем!

Первый тест

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

import unittest

########################################################################
class TestBowling(unittest.TestCase):
    """"""

    #----------------------------------------------------------------------
    def test_all_ones(self):
        """Constructor"""
        game = Game()
        game.roll(11, 1)
        self.assertEqual(game.score, 11)

Это довольно простой тест. Мы создаем игровой объект, а затем вызовите его рулон Способ одиннадцать раз со счетом одного раза каждый раз. Тогда мы используем Assertequal Метод от Неизвестный Модуль для проверки, если оценка объекта игры является правильным (то есть одиннадцать). Следующим шагом является написать самый простой код, который вы можете придумать, чтобы сделать тестовый пропуск. Вот один пример:

########################################################################
class Game:
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self.score = 0
        
    #----------------------------------------------------------------------
    def roll(self, numOfRolls, pins):
        """"""
        for roll in numOfRolls:
            self.score += pins

Для простоты, вы можете просто скопировать и вставить это в тот же файл с вашим тестом. Мы нарушим их в два файла для нашего следующего теста. Во всяком случае, как вы можете видеть, наш Игра Класс Super Simple. Все, что нужно было пройти теста, было свойство оценки и рулон Метод, который может обновить его. Если вы не знаете, как для Петли работают, тогда вам нужно пойти прочитать Учебник Python Отказ

Давайте запустим тест и посмотрим, если он пройдет! Самый простой способ запуска тестов – добавить следующие две строки кода в нижнюю часть файла:

if __name__ == '__main__':
    unittest.main()

Затем запустите файл Python через командную строку, если вы сделаете, вы должны получить что-то вроде следующего:

E
======================================================================
ERROR: test_all_ones (__main__.TestBowling)
Constructor
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\Mike\Documents\My Dropbox\Scripts\Testing\bowling\test_one.py",
 line 27, in test_all_ones
    game.roll(11, 1)
  File "C:\Users\Mike\Documents\My Dropbox\Scripts\Testing\bowling\test_one.py",
 line 15, in roll
    for roll in numOfRolls:
TypeError: 'int' object is not iterable

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

Упс! У нас есть ошибка там где-то. Похоже, мы проходим целое число, а затем пытаемся повторить это. Это не работает! Нам нужно изменить метод рулона нашего объекта игры на следующее, чтобы сделать его работать:

#----------------------------------------------------------------------
def roll(self, numOfRolls, pins):
    """"""
    for roll in range(numOfRolls):
        self.score += pins

Если вы запустите тест сейчас, вы должны получить следующее:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Обратите внимание “.” Потому что это важно. Эта маленькая точка означает, что один тест пробежал и что он прошел. «ОК» в конце концов также высказывает вас в этот факт. Если вы изучаете оригинальный выход, вы заметите, что он отводится с «E» для ошибки, и нет DOT! Давайте перейдем к тестированию # 2.

Второй тест

Для второго теста мы проверим, что произойдет, когда мы получаем удар. Нам нужно будет изменить первый тест для использования списка для количества штифтов, но сбитых вниз в каждом кадре, поэтому мы посмотрим на обоих тестах здесь. Вы, вероятно, найдете это довольно общий процесс, в котором вам может потребоваться отредактировать пару тестов из-за фундаментальных изменений в том, что вы тестируете. Обычно это произойдет только в начале вашего кодирования, и вам станет лучше позже, так что вам не нужно это делать. Так как это мой первый раз, когда я не думал достаточно далеко голову. Во всяком случае, давайте посмотрим на код:

from game import Game
import unittest

########################################################################
class TestBowling(unittest.TestCase):
    """"""

    #----------------------------------------------------------------------
    def test_all_ones(self):
        """Constructor"""
        game = Game()
        pins = [1 for i in range(11)]
        game.roll(11, pins)
        self.assertEqual(game.score, 11)
        
    #----------------------------------------------------------------------
    def test_strike(self):
        """
        A strike is 10 + the value of the next two rolls. So in this case
        the first frame will be 10+5+4 or 19 and the second will be
        5+4. The total score would be 19+9 or 28.
        """
        game = Game()
        game.roll(11, [10, 5, 4])
        self.assertEqual(game.score, 28)
        
if __name__ == '__main__':
    unittest.main()

Давайте посмотрим на наш первый тест и как он изменился. Да, мы нарушаем правила здесь немного, когда дело доходит до TDD. Не стесняйтесь не изменять первый тест и посмотреть, какие перерывы. В test_all_ones Метод, мы устанавливаем Pins Переменная к равным пониманию списка, который создал список одиннадцати. Тогда мы прошли это к нашему Игра Объект рулон Способ наряду с количеством валков.

Во втором тесте мы бросаем забастовку в нашем первом рулоне, пять наших второго и четыре в нашей третьей. Обратите внимание, что мы пошли на голову и сказали, что мы проходили в одиннадцать рулонов, и все же мы проходим только в три. Это означает, что нам нужно установить другие восемь рулонов в нули. Наконец, мы используем наш надежный Assertequal Метод для проверки, если мы получим правильное всего. Наконец, обратите внимание, что теперь мы импортируем класс игры, а не удерживая его тестами. Теперь нам нужно реализовать код, необходимый для прохождения этих двух тестов. Давайте посмотрим на одно возможное решение:

########################################################################
class Game:
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self.score = 0
        self.pins = [0 for i in range(11)]
        
    #----------------------------------------------------------------------
    def roll(self, numOfRolls, pins):
        """"""
        x = 0
        for pin in pins:
            self.pins[x] = pin
            x += 1
        x = 0
        for roll in range(numOfRolls):
            if self.pins[x] == 10:
                self.score = self.pins[x] + self.pins[x+1] + self.pins[x+2]
            else:
                self.score += self.pins[x]
            x += 1
        print self.score

Сразу у летучей мыши вы заметите, что у нас есть новый атрибут класса под названием Self.pins Это содержит PIN-код по умолчанию, который является одиннадцатью нулями. Тогда в нашем рулон Способ, мы добавляем правильные оценки в правильное положение в списке Self.pins в первом цикле. Затем во второй цикле мы проверяем, постучал ли штифты, равна десяти. Если это так, мы добавляем его и на следующие два балла, чтобы забить. В противном случае мы делаем то, что мы ДД. В конце метода мы распечатаем счет, чтобы проверить, ли это то, что мы ожидаем. На данный момент мы готовы комировать наш последний тест.

Третий (и окончательный) тест

Наш последний тест проверит на правильный балл, который приведет к тому, что кто-то будет бросить запасную. Тест прост, решение немного сложнее. Пока мы на этом, мы собираемся ревертировать тестовый код немного. Как обычно, мы сначала посмотрим на тест.

from game_v2 import Game
import unittest

########################################################################
class TestBowling(unittest.TestCase):
    """"""
    
    #----------------------------------------------------------------------
    def setUp(self):
        """"""
        self.game = Game()
        
    #----------------------------------------------------------------------
    def test_all_ones(self):
        """
        If you don't get a strike or a spare, then you just add up the 
        face value of the frame. In this case, each frame is worth
        one point, so the total is eleven.
        """
        pins = [1 for i in range(11)]
        self.game.roll(11, pins)
        self.assertEqual(self.game.score, 11)
        
    #----------------------------------------------------------------------
    def test_spare(self):
        """
        A spare is worth 10, plus the value of your next roll. So in this
        case, the first frame will be 5+5+5 or 15 and the second will be
        5+4 or 9. The total is 15+9, which equals 24,
        """
        self.game.roll(11, [5, 5, 5, 4])
        self.assertEqual(self.game.score, 24)
        
    #----------------------------------------------------------------------
    def test_strike(self):
        """
        A strike is 10 + the value of the next two rolls. So in this case
        the first frame will be 10+5+4 or 19 and the second will be
        5+4. The total score would be 19+9 or 28.
        """
        self.game.roll(11, [10, 5, 4])
        self.assertEqual(self.game.score, 28)
        
if __name__ == '__main__':
    unittest.main()

Во-первых, мы добавили Настройка Способ, который создаст объект Self.game для нас для каждого теста. Если бы мы получили доступ к базе данных или что-то в этом роде, мы, вероятно, имеем бы метод отрывателя для закрытия соединений или файлов или такой вещи. Они работают в начале и в конце каждого теста соответственно должны существовать. test_all_ones и test_strike Испытания в основном то же самое, за исключением того, что они используют «Self.game» сейчас. Единственный новый тест – test_spare Отказ DocString объясняет, как запчасти работают и код – это только две строки. Да, вы можете понять это. Давайте посмотрим на код, который нам нужно будет пройти эти тесты:

# game_v2.py
########################################################################
class Game:
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        self.score = 0
        self.pins = [0 for i in range(11)]
        
    #----------------------------------------------------------------------
    def roll(self, numOfRolls, pins):
        """"""
        x = 0
        for pin in pins:
            self.pins[x] = pin
            x += 1
        x = 0
        spare_begin = 0
        spare_end = 2
        for roll in range(numOfRolls):
            spare = sum(self.pins[spare_begin:spare_end])
            if self.pins[x] == 10:
                self.score = self.pins[x] + self.pins[x+1] + self.pins[x+2]
            elif spare == 10:
                self.score = spare + self.pins[x+2]
                x += 1
            else:
                self.score += self.pins[x]
            x += 1
            if x == 11: 
                break
            spare_begin += 2
            spare_end += 2
        print self.score

Для этой части головоломки мы добавляем в наше условное утверждение в нашем петле. Для расчета ценности запасного времени мы используем SART_BEGIN и FART_END Список позиций, чтобы получить правильные значения из нашего списка, а затем мы суммируем их. Вот что запасной переменная для. Это может быть лучше помещено в Элиф, но я оставлю это для читателя, чтобы поэкспериментировать. Технически, это только первая половина запасной балл. Вторая половина находятся следующие два рулона, что вы найдете в расчете в части ELIF текущего кода. Остальная часть кода одинакова.

Другие ноты

Как вы, возможно, догадались, в нетестовом модуле есть много намного больше, чем то, что здесь покрыто. Есть много других утверждений, которые вы можете использовать для тестирования результатов. Вы можете пропустить тесты, запустите тесты из командной строки, используйте TestLoader, чтобы создать тестовый набор и многое, гораздо больше. Обязательно прочитайте Полная документация Когда у вас есть шанс, так как этот учебник едва царапает поверхность этой библиотеки.

Обертывание

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