Разработка через тестирование

Разработка через тестирование (TDD)

В этой статье рассмотрим методологию разработки через тестирования и почему это полезно для Python программиста.

Введение

Если вы занимаетесь разработкой не первый год возможно вы слышали о методологии разработки через тестирование (Test Driven Development) или TDD, а так же задавались вопросом, что это такое. 

Многие книги о программирования, которые существуют, не говорят о TDD и о том, как вы можете использовать его для улучшения качества своих проектов. 

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

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

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

Что это такое

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

Согласно википедии разработка через тестирование (англ. test-driven development, TDD) — техника разработки программного обеспечения, которая основывается на повторении очень коротких циклов разработки. Сначала пишется тест, покрывающий желаемое изменение, затем пишется код, который позволит пройти тест, и под конец проводится рефакторинг нового кода к соответствующим стандартам. Кент Бек, считающийся изобретателем этой техники, утверждал в 2003 году, что разработка через тестирование поощряет простой дизайн и внушает уверенность.

Кто использует

TDD — это методология, заимствованная из фреймворков Agile и Extreme Programming. Поэтому можно с уверенностью сказать, что любые компании, внедряющие эти стратегии разработки, будут использовать TDD в той или иной форме. Однако часто говорят, что большинство компаний не используют практику TDD так, как она задумана. Это связано со сроками, большим количеством кода, который нужно написать, и возможностью постоянно добавлять и обновлять тесты. Несмотря на это, многие утверждают, что TDD все еще жив и здоров и имеет свое применение в конкретных проектах, в частности, в алгоритмических функциях и стабильных приложениях.

Польза от TDD

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

Внедрение в проект

Для внедрения TDD мы воспользуемся языком программирования Python и в качестве проекта напишем калькулятор.

В TDD используется в общей сложности четыре этапа

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

Для python мы будем использовать встроенную библиотеки unittest, документацию по unittest можно найти здесь.

Итак, поскольку этот код уже написан, я просто покажу реализацию unittest. Мы будем реализовывать это для всех четырех функций сложения, умножения, вычитания и деления. Поэтому сначала мы должны понять, что мы хотим, чтобы делала каждая из этих функций, что в нашем случае довольно просто. Затем мы можем создать модульные тесты, чтобы убедиться, что эти функции работают так, как задумано, и проходят наши модульные тесты.

Вот функции, которые мы будем тестировать

# Сложение
def addition(a: int, b: int):
    """Складывает два заданных числа"""
    return a + b


# Вычитание
def subtracting(a: int, b: int):
    """Вычитание двух заданных чисел"""
    return a - b


# Умножение
def multiplication(a: int, b: int):
    """Умножает два заданных числа"""
    return a * b


# Деление
def division(a: int, b: int):
    """Делит два заданных числа"""
    return a / b

Эти базовые функции позволят нам протестировать и понять, как использовать unittest, не заботясь о сложности отдельных функций.

Для тестирования этих функций мы импортируем библиотеку unittest. 

import unittest

import calc

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

Если вы заметили, в нижней части файла мы запускаем функцию unittest.main(), которая поможет нам инициализировать тестовые кейсы при прямом запуске файла.

Для запуска тестов будет достаточно выполнить команду 

python3 calc_test.py

Добавим тестовые кейсы для функции сложения и проверим как работает функция.

Начнем с создания класса, который будет наследоваться от класса unittest.Testcase. Затем мы протестируем нашу функцию, используя assertEqual и передавая ей функцию addition из нашего исходного файла calc.py. 

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

import unittest

import calc


class TestCalculator(unittest.TestCase):
    def test_addition(self):
        self.assertEqual(calc.addition(11, 22), 33)
        self.assertEqual(calc.addition(-10, 1), -9)
        self.assertEqual(calc.addition(74.1, 22), 96.1)


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

Запустим тесты и посмотрим на результаты

python3 calc_test.py

Вывод команды

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

OK

Тест прошел успешно. В качестве демонстрации, поменяйте возвращаемое значение функции addition на код ниже и перезапустите тесты.

return a + b + 1

Должна появится ошибка, примерно такого содержания

F
======================================================================
FAIL: test_addition (__main__.TestCalculator)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/krnlnx/Projects/Test/calc_test.py", line 8, in test_addition
    self.assertEqual(calc.addition(11, 22), 33)
AssertionError: 34 != 33

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

FAILED (failures=1)

Покрытие кода тестами

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

import unittest

import calc


class TestCalculator(unittest.TestCase):
    def test_addition(self):
        self.assertEqual(calc.addition(11, 22), 33)
        self.assertEqual(calc.addition(-10, 1), -9)
        self.assertEqual(calc.addition(74.1, 22), 96.1)

    def test_subtracting(self):
        self.assertEqual(calc.subtracting(94, 24), 70.0)
        self.assertEqual(calc.subtracting(-34, 55), -89.0)
        self.assertEqual(calc.subtracting(23.5, 15), 8.5)

    def test_multiplication(self):
        self.assertEqual(calc.multiplication(13, 22), 286)
        self.assertEqual(calc.multiplication(-32, 10), -320)
        self.assertEqual(calc.multiplication(2.5, 4), 10)

    def test_division(self):
        self.assertEqual(calc.division(400, 40), 10)
        self.assertEqual(calc.division(-120, 20), -6)
        self.assertEqual(calc.division(90.5, 40), 2.2625)


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

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

....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

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

Заключение

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

Дополнительные материалы

В первую очередь я бы посоветовал прочитать книгу Гарри Персиваля: Python. Разработка на основе тестирования. Это очень хорошо структурированная книга, в которой большое количество примеров использования TDD.

Читайте код в открытых проектах на github, более менее крупные проекты содержат в себе тесты и изучение реальных кейсов в приложениях самый лучший способ понять, зачем нужен TDD.

Ознакомьтесь с фреймворками для тестирования

PyTest — https://docs.pytest.org/

Unittest — https://docs.python.org/3/library/unittest.html

Robot — https://robotframework.org

Nose — https://github.com/nose-devs/nose2

Егор Егоров

Программирую на Python с 2017 года. Люблю создавать контент, который помогает людям понять сложные вещи. Не представляю жизнь без непрерывного цикла обучения, спорта и чувства юмора.

Ссылка на мой github есть в шапке. Залетай.

Оцените автора
Егоров Егор
Добавить комментарий

  1. Максим

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

    Ответить
    1. Егор Егоров автор

      Да, Максим, вы правы, идиологически в tdd сперва пишут тест, потом код.

      Ответить
  2. Андрей

    Егор, большое вам спасибо за вашу работу!

    Ответить
    1. Егор Егоров автор

      Андрей, пожалуйста.
      Только это все-таки хобби, а не работа 🙂

      Ответить