Про PyTest

Oct 14, 2016 17:04

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

1) Чтобы протестировать результат работы метода, нужно вызвать этот метод с параметрами, на которых мы хотим его тестировать.
Пример (из документации):
def func(x):
      return x + 1
def test_answer():
      assert func(3) == 5

2) Чтобы протестировать результат работы скрипта scr.py, нужно вызвать функцию main() скрипта scr:
import scr
def test_scr():
      scr.main()

3) Чтобы задать скрипту параметры, нужно использовать фикстуру monkeypatch
monkeypatch.setattr(sys, 'argv', ["arg1", "arg2", "argn"])

Фикстура в pytest -- это некий метод, в котором задают параметры тестового окружения.
Есть уже встроенные в pytest фикстуры, но так же можно определять свои самостоятельно.
monkeypatch -- это встроенная фикстура.

Конкретно с помощью monkeypatch, в частности, можно изменять атрибуты модуля sys (в нашем случае таким атрибутом является список argv аргументов командной строки). Вообще, эта фикстура ещё много чего может (например, устанавливать переменные окружения, редактировать словари etc.)

Имя используемой фикстуры передаётся тесту в параметрах тестовой функции вот таким образом (на примере monkeypatch):
def test_scr(monkeypatch):
     #далее происходит обращение к фикстуре уже непосредственно при выполнении теста:
    monkeypatch.setattr(sys, 'argv', ["arg1", "arg2", "argn"])
    #теперь скрипт scr запустится с заданными аргументами
    scr.main()

Теперь, собственно, самое главное -- про результаты работы скрипта:
4) Чтобы проверить, что тестируемая функция возбуждает исключение, нужно использовать метод raises:
pytest.raises(SystemExit) #SystemExit -- пример исключения, которое происходит после завершения работы по sys.exit()

5) Можно также проверить информацию об исключении (код ошибки, сообщение etc.):
assert 'At least two arguments demanded! No filename or string!' in str(exc_info.value)

В exc_info.value (объект ExceptionInfo) содержится куча всякий информации, поэтому проверяем вхождение (in), а не точное соответствие.

Всё вместе будет выглядеть примерно так:
#в этом тесте проверяется, что скрипт возбуждает исключение, когда его запускают менее, чем с двумя аргументами командной строки
def test_zero_parameters(monkeypatch):
      monkeypatch.setattr(sys, 'argv', ["parser.py"])
      with pytest.raises(SystemExit) as exc_info:
            scr.main()
      assert 'At least two arguments demanded! No filename or string!' in str(exc_info.value)

6) Если нам требуется проверить, что скрипт напечатает в консоль, используем встроенную фикстуру capsys.
Результат вывода в консоль можно получить таким образом:
out = capsys.readouterr()

В тесте это будет выглядеть так:
def test_string_in_file(monkeypatch, capsys):
      monkeypatch.setattr(sys, 'argv', ['parser_.py', 'text.txt', '12'])
      scr.main() #скрипт должен найти вхождения строки '12' и вывести их в консоль
      out = capsys.readouterr()
      assert '12' in str(out)

7) О том, как создавать фикстуры:
Чтобы python мог отличить фикстуру от обычного метода, она помечается:
@pytest.fixture()
'''
Далее идёт метод, который описывает фикстуру. Представим, что нам нужно протестировать скрипт с 1000 любыми параметрами командной строки на предмет "а не захлебнётся ли он таким количеством".
Фикстура генерит 1000 параметров командной строки, с которыми потом можно будет запустить скрипт.
'''
def generate_thousand_params():
      thousand_params = ""
      i = 0
      while i < 1000:
            thousand_params = thousand_params + "--some-param "
            i  = i+1
      return thousand_params

В тесте обращение к фикстуре generate_thousand_params будет вглядеть таким образом:
def test_thousand_parameters(monkeypatch, capsys, generate_thousand_params):
      monkeypatch.setattr(sys, 'argv', ['parser_.py', 'text.txt', '111', generate_thousand_params])
      scr.main()
      out = capsys.readouterr()
      assert '111' in str(out) #признак того, что скрипт корректно отработал -- результат в консоли

Фикстуре можно задавать свои параметры:
@pytest.fixture(scope='module', params=['1', '2', '3'])
scope -- время жизни фикстуры (модуль, функция)
params -- список параметров, которые нужно использовать в фикстуре

Например, нам нужно для тестовых целей нагенерить несколько текстовых файлов с разными именами. Мы можем перечислить эти имена в списке params:
@pytest.fixture(params=['1.txt', 'abc.txt', 'абц.txt'])
И потом использовать в фикстуре следующим образом:
def other_filenames(request):
      file_name = request.param #фикстура отработает для каждого из параметров
      helper.create_test_file(file_name, '111')
      yield file_name
      os.remove(file_name)

Вместо return в фикстурах чаще используют ключевое слово yield. Как она работает, проще всего пояснить на приведённом выше примере:
Код фикстуры отрабатывает для первого параметра 1.txt (создает текстовый файл), далее yield возвращает этот созданный файл в тест, тест отрабатывает для созданного файла, и далее отрабатывает кусок кода, который следует за yield -- в данном случае это удаление созданного файла (приведение системы в исходное состояние или teardown).
После этого работа фикстуры продолжится со второго параметра, потом с третьего и так далее. То есть yield -- это по сути возврат результата не функции (как return), а итерации.

6) Параметризацию можно использовать и непосредственно в тестах.
Перед объявлением тестовой функции задаем список параметров:
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
И далее используем их в тесте:
def test_eval(test_input, expected):
      assert eval(test_input) == expected

PS Если кто-то заметил, что в каком-то месте я написала глупость, то с удовольствием выслушаю комментарий и поправлю :)

qa, #признак, #далее, #скрипт, #теперь, #systemexit, , #фикстура, python

Previous post Next post
Up