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

Что мы должны тестировать?

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

Unit testing

Метод, с помощью которого отдельные единицы исходного кода тестируются. Wikipedia говорит В компьютерном программировании, unit testing — это метод, с помощью которого отдельные единицы исходного кода, наборы из одного или нескольких модулей компьютерной программы вместе с соответствующими управляющими данными, процедурами использования и процедурами эксплуатации, тестируются для определения, подходят ли они для использования.

unittest module

В Python у нас есть модуль unittest для помощи нам.

Код для вычисления факториала

В этом примере мы создадим файл factorial.py.

import sys

def fact(n):
    """
    Функция вычисления факториала

    :arg n: Число
    :returns: факториал числа n

    """
    if n == 0:
        return 1
    return n * fact(n -1)

def div(n):
    """
    Просто деление
    """
    res = 10 / n
    return res


def main(n):
    res = fact(n)
    print(res)

if __name__ == '__main__':
    if len(sys.argv) > 1:
        main(int(sys.argv[1]))

Результат

$ python factorial.py 5

Какую функцию тестировать?

Как вы можете видеть, fact(n) — это функция, которая выполняет все вычисления, поэтому мы должны протестировать её, по крайней мере.

Наш первый тестовый случай

Теперь мы напишем наш первый тестовый случай.

import unittest
from factorial import fact

class TestFactorial(unittest.TestCase):
    """
    Наш основной тестовый класс
    """

    def test_fact(self):
        """
        Фактический тест.
        Любой метод, начинающийся с ``test_``, будет считаться тестовым случаем.
        """
        res = fact(5)
        self.assertEqual(res, 120)


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

Запуск теста:

$ python factorial_test.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Описание

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

Тест-кейс создается путем создания подкласса unittest.TestCase.

Теперь откройте тестовый файл и измените 120 на 121 и посмотрите, что произойдет :)

Разные assert-утверждения

МетодПроверяет, чтоНовое в
assertEqual(a, b)a == b
assertNotEqual(a, b)a != b
assertTrue(x)bool(x) is True
assertFalse(x)bool(x) is False
assertIs(a, b)a is b2.7
assertIsNot(a, b)a is not b2.7
assertIsNone(x)x is None2.7
assertIsNotNone(x)x is not None2.7
assertIn(a, b)a in b2.7
assertNotIn(a, b)a not in b2.7
assertIsInstance(a, b)isinstance(a, b)2.7
assertNotIsInstance(a, b)not isinstance(a, b)2.7

Тестирование исключений

Если мы вызовем div(0) в factorial.py, мы можем увидеть, вызывает ли он исключение.

Мы также можем тестировать эти исключения, например:

self.assertRaises(ZeroDivisionError, div, 0)

Полный код

import unittest
from factorial import fact, div

class TestFactorial(unittest.TestCase):
    """
    Наш основной тестовый класс
    """

    def test_fact(self):
        """
        Фактический тест.
        Любой метод, который начинается с ``test_``, будет считаться тестовым случаем.
        """
        res = fact(5)
        self.assertEqual(res, 120)

    def test_error(self):
        """
        Тест на вызов исключения из-за ошибки времени выполнения
        """
        self.assertRaises(ZeroDivisionError, div, 0)

## mounttab.py

Здесь у нас только одна функция mount_details(), которая выполняет разбор и вывод деталей монтирования.

import os


def mount_details():
    """
    Выводит детали монтирования
    """
    if os.path.exists('/proc/mounts'):
        fd = open('/proc/mounts')
        for line in fd:
            line = line.strip()
            words = line.split()
            print()'%s on %s type %s' % (words[0],words[1],words[2]), end=' ')
            if len(words) > 5:
                print('(%s)' % ' '.join(words[3:-2]))
            else:
                print('')


if __name__ == '__main__':
    mount_details()

После рефакторинга

Теперь мы рефакторили код и имеем одну новую функцию parse_mounts, которую мы можем легко протестировать.

import os

def parse_mounts():
    """
    Она парсит /proc/mounts и возвращает список кортежей
    """
    result = []
    if os.path.exists('/proc/mounts'):
        fd = open('/proc/mounts')
        for line in fd:
            line = line.strip()
            words = line.split()
            if len(words) > 5:
                res = (words[0],words[1],words[2], '(%s)' % ' '.join(words[3:-2]))
            else:
               res = (words[0],words[1],words[2])
            result.append(res)
    return result

def mount_details():
    """
    Печатает детали монтирования
    """
    result = parse_mounts()
    for line in result:
        if len(line) == 4:
            print('%s on %s type %s %s' % line)
        else:
            print('%s on %s type %s' % line)


if __name__ == '__main__':
    mount_details()

и тестовый код для этого.

#!/usr/bin/env python
import unittest
from mounttab2 import parse_mounts

class TestMount(unittest.TestCase):
    """
    Наша основная тестовая класс
    """

    def test_parsemount(self):
        """
        Фактический тест.
        Любой метод, который начинается с ``test_``, будет считаться тестовым случаем.
        """
        result = parse_mounts()
        self.assertIsInstance(result, list)
        self.assertIsInstance(result[0], tuple)

    def test_rootext4(self):
        """
        Тест для поиска корневой файловой системы
        """
        result = parse_mounts()
        for line in result:
            if line[1] == '/' and line[2] != 'rootfs':
                self.assertEqual(line[2], 'ext4')


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

$ python mounttest.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

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

Покрытие тестами — это простой способ найти непротестированные части кодовой базы. Он не говорит вам, насколько хороши ваши тесты.

В Python у нас уже есть хороший инструмент для покрытия тестами, который может нам помочь. Вы можете установить его в Fedora

# yum install python-coverage

Или используя pip.

$ pip install coverage

Пример покрытия

$ coverage -x mounttest.py
<ВЫВОД убран>

$ coverage -rm
Name        Stmts   Miss  Cover   Missing
-----------------------------------------
mounttab2      21      7    67%   16, 24-29, 33
mounttest      14      0   100%
-----------------------------------------
TOTAL          35      7    80%

Перейти к следующему уроку →