Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing101

 Testing101

Алексей Партилов (Rambler&Co) @ MoscowPython 38

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

Moscow Python Meetup

August 24, 2016
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. Mars Climate Orbiter Автор: NASA/JPL/Corby Waste - http://www.vitalstatistics.info/uploads/mars%20climate%20orbiter.jpg (see also

    http://www.jpl.nasa.gov/pictures/solar/mcoartist.html), Общественное достояние, https://commons.wikimedia.org/w/index.php?curid=390903
  2. • Методы и функции • Классы • Модели • Формы

    • View • Middleware • и еще много много всего • + что-нибудь общее
  3. Software engineer in test Developer James A. Whittaker, Jason Arbon,

    Jeff Carollo - How Google tests software Big Medium Small Testing engineer
  4. TDD

  5. Fizzbuzz • Возвращает fizz, если число делится только на 3.

    • Возвращает buzz, если число делится только на 5. • Возвращает fizzbuzz, если число делится и на 3 и на 5. • Возвращает число, если число не делится ни на 3 ни на 5.
  6. Red def test_fizzbuzz_mod_3(): """fizzbuzz возвращает fizz, если число делится только

    на 3.""" assert fizzbuzz(6) == 'fizz' • Возвращает fizz, если число делится только на 3.
  7. Green def fizzbuzz(n): """Отfizzbuzz'ить число.""" if n % 3 ==

    0: return 'fizz' def test_fizzbuzz_mod_3(): """fizzbuzz возвращает fizz, если число делится только на 3.""" assert fizzbuzz(6) == 'fizz' • Возвращает fizz, если число делится только на 3.
  8. Red def fizzbuzz(n): """Отfizzbuzz'ить число.""" if n % 3 ==

    0: return 'fizz' def test_fizzbuzz_mod_3(): """fizzbuzz возвращает fizz, если число делится только на 3.""" assert fizzbuzz(6) == 'fizz' def test_fizzbuzz_mod_5(): """fizzbuzz возвращает buzz, если число делится только на 5.""" assert fizzbuzz(10) == 'buzz' • Возвращает fizz, если число делится только на 3. • Возвращает buzz, если число делится только на 5.
  9. Green def fizzbuzz(n): """Отfizzbuzz'ить число.""" if n % 5 ==

    0: return 'buzz' if n % 3 == 0: return 'fizz' def test_fizzbuzz_mod_3(): """fizzbuzz возвращает fizz, если число делится только на 3.""" assert fizzbuzz(6) == 'fizz' def test_fizzbuzz_mod_5(): """fizzbuzz возвращает buzz, если число делится только на 5.""" assert fizzbuzz(10) == 'buzz' • Возвращает fizz, если число делится только на 3. • Возвращает buzz, если число делится только на 5.
  10. BDD

  11. Gherkin Функционал: Поиск отеля Как путешественник я хочу иметь возможность

    искать наиболее подходящие мне отели по местоположению и набору предлагаемых услуг. Сценарий: Поиск 4-х звездочного отеля в Барселоне. Допустим Я нахожусь на странице поиска отелей Если я ввожу в строку поиска "Барселона" И отмечаю галочкой 4-х звездный отель И нажимаю на кнопку "Поиск" То я попадаю на страницу с результатом поиска И вижу что первые 10 результатов - отели в городе Барселона и с рейтингом не ниже 4-х звезд
  12. Unit and integration • unittest • nose • pytest •

    tox Тестовые фреймворки E2E • lettuce • behave • pytest-bdd
  13. Unittest import unittest class TestFizzBuzz(unittest.TestCase): def test_fizzbuzz_mod_3_5(self): """fizzbuzz возвращает fizzbuzz,

    если число делится и на 3 и на 5.""" self.assertEqual(fizzbuzz(30), 'fizzbuzz') def test_fizzbuzz_mod_5(self): """fizzbuzz возвращает buzz, если число делится только на 5.""" self.assertEqual(fizzbuzz(10), 'buzz') def test_fizzbuzz_mod_3(self): """fizzbuzz возвращает fizz, если число делится только на 3.""" self.assertEqual(fizzbuzz(9), 'fizz') def test_fizzbuzz_no_mod(self): """fizzbuzz возвращает fizz, если число не делится ни на 3, ни на 5.""" self.assertEqual(fizzbuzz(4), 4) if __name__ == '__main__': unittest.main()
  14. Unittest class TestSomething(unittest.TestCase): def setUp(self): """Подготовка тестов.""" self.n = 20

    def tearDown(self): """Разгрузка.""" self.n = None def test_something(self): self.assertEqual(self.n, 20)
  15. Unittest assertEqual(a, b) assertNotEqual(a, b) assertTrue(x) assertFalse(x) assertIs(a, b) assertIsNot(a,

    b) assertIsNone(x) assertIsNotNone(x) assertIn(a, b) assertNotIn(a, b) assertIsInstance(a, b) assertNotIsInstance(a, b)
  16. Nose def test_fizzbuzz_mod_3_5(): """fizzbuzz возвращает fizzbuzz, если число делится и

    на 3 и на 5.""" assert fizzbuzz(30) == 'fizzbuzz' def test_fizzbuzz_mod_5(): """fizzbuzz возвращает buzz, если число делится только на 5.""" assert fizzbuzz(10) == 'buzz' def test_fizzbuzz_mod_3(): """fizzbuzz возвращает fizz, если число делится только на 3.""" assert fizzbuzz(6) == 'fizz' def test_fizzbuzz_no_mod(): """fizzbuzz возвращает число, если число не делится ни на 3 ни на 5.""" assert fizzbuzz(4) == 4
  17. Nose from nose import with_setup def my_setup_function(): pass def my_teardown_function():

    pass @with_setup(my_setup_function, my_teardown_function) def test_numbers_3_4(): assert multiply(3,4) == 12 class TestUM: def setup(self): print ("TestUM:setup() before each test method") def teardown(self): print ("TestUM:teardown() after each test method") def test_numbers_3_4(): assert multiply(3,4) == 12
  18. Pytest import pytest @pytest.fixture() def init_hello(): return 'hello' @pytest.fixture() def

    init_hello_world(init_hello): return init_hello + ' world!' def test_hello_world(init_hello_world): assert init_hello_world == 'hello world!'
  19. Pytest import pytest @pytest.mark.parametrize("n, result", [ (30, 'fizzbuzz'), (10, 'buzz'),

    (6, 'fizz'), (4, 4) ]) def test_fizzbuzz(n, result): """fizzbuzz возвращает соответствующее значение""" assert fizzbuzz(n) == result
  20. Pytest $ py.test fizzbuzz.py ============================= test session starts ============================== platform

    linux -- Python 3.5.1+, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 rootdir: /home/droppoint/codeWorks/interviews, inifile: collected 4 items fizzbuzz.py .... =========================== 4 passed in 0.01 seconds ===========================
  21. Tox [tox] envlist = py34,py35 [testenv:py34] passenv = TCAPI_* LC_ALL

    deps = pytest pytest-cov commands = py.test tests -rw --cov travel_content_api --cov-report html --junit-xml junit.xml [testenv:py35] passenv = TCAPI_* LC_ALL deps = pytest pytest-cov commands = py.test tests -rw --cov travel_content_api --cov-report html --junit-xml junit.xml [testenv:docs] passenv = LC_ALL deps = sphinx sphinxcontrib-httpdomain commands = sphinx-build -q -W -E -n -b html docs docs/_build/html
  22. behave Feature: showing off behave Scenario: run a simple test

    Given we have behave installed when we implement a test then behave will test it for us!
  23. behave from behave import * @given('we have behave installed') def

    step_impl(context): pass @when('we implement a test') def step_impl(context): assert True is not False @then('behave will test it for us!') def step_impl(context): assert context.failed is False
  24. lettuce Feature: Compute factorial In order to play with Lettuce

    As beginners We'll implement factorial Scenario: Factorial of 0 Given I have the number 0 When I compute its factorial Then I see the number 1
  25. lettuce from lettuce import * @step('I have the number (\d+)')

    def have_the_number(step, number): world.number = int(number) @step('I compute its factorial') def compute_its_factorial(step): world.number = factorial(world.number) @step('I see the number (\d+)') def check_number(step, expected): expected = int(expected) assert world.number == expected, \ "Got %d" % world.number def factorial(number): return -1
  26. lettuce from lettuce import * @step('I have the number (\d+)')

    def have_the_number(step, number): world.number = int(number) @step('I compute its factorial') def compute_its_factorial(step): world.number = factorial(world.number) @step('I see the number (\d+)') def check_number(step, expected): expected = int(expected) assert world.number == expected, \ "Got %d" % world.number def factorial(number): return -1
  27. pytest-bdd Feature: Blog A site where you can publish your

    articles. Scenario: Publishing the article Given I'm an author user And I have an article When I go to the article page And I press the publish button Then I should not see the error message And the article should be published
  28. pytest-bdd from pytest_bdd import scenario, given, when, then @scenario('publish_article.feature', 'Publishing

    the article') def test_publish(): pass @given("I'm an author user") def author_user(auth, author): auth['user'] = author.user @given('I have an article') def article(author): return create_test_article(author=author) @when('I go to the article page') def go_to_article(article, browser): browser.visit(urljoin(browser.url, '/manage/articles/{0}/'.format(article.id))) ...
  29. Mock >>> from unittest.mock import MagicMock >>> thing = ProductionClass()

    >>> thing.method = MagicMock(return_value=3) >>> thing.method(3, 4, 5, key='value') 3 >>> thing.method.called True >>> thing.method.call_args() call((3, 4, 5), {'key': value}) >>> thing.method.assert_called_with(3, 4, 5, key='value') >>> mock = Mock(side_effect=KeyError('foo')) >>> mock() Traceback (most recent call last): ... KeyError: 'foo'
  30. Mock @mock.patch('someproject.parser.open') def test_system_error(open_moc): """возвращает ошибку ParserError, если при записи

    csv файла возникла системная ошибка. """ open_mock.side_effect = IOError('Test') vars = [('hello', '42'), ('world', '42')] with pytest.raises(ParserError) as excinfo: render_vars_csv(path, vars) assert 'Test' in str(excinfo.value)
  31. Клиент API from urllib.parse import urljoin import requests from .exceptions

    import SomeAPIError class SomeAPIClient: """Клиент для какого-то API.""" ENDPOINT = 'http://some.com/api/values/' def get_value(self, id): """Получить целочисленное значение по ID.""" url = urljoin(self.ENDPOINT, id) response = requests.get(url) try: value = int(response.body) return value except ValueError: raise SomeAPIError('Response cannot be parsed as integer')
  32. Mock from someproject.client import SomeAPIClient import pytest import requests @pytest.fixture()

    def good_response(): """Фикстура нормального ответа от сервера some.com.""" response = requests.Response() response.headers = { "Content-Type": "text/plain", } response._content = b'42' response.status_code = 200 return response @mock.patch('someproject.client.requests.get') def test_someapiclient_get_value(requests_mock, good_response): """SomeAPIClient.get_value возвращает число, если запрос успешен.""" requests_mock.return_value = good_response client = SomeAPIClient() value = client.get_value(1) assert value == 42 assert requests_mock.call_count == 1
  33. responses import responses from someproject.client import SomeAPIClient @responses.activate def test_someapiclient_get_value():

    """SomeAPIClient.get_value возвращает число, если запрос успешен.""" responses.add( responses.GET, 'http://some.com/api/values/1', body='42', status=200 ) client = SomeAPIClient() value = client.get_value(1) assert value == 42 assert len(responses.calls) == 1 assert responses.calls[0].request.url == 'http://some.com/api/values/1' assert responses.calls[0].response.text == '42'
  34. • Test • Test • Test • Refaсtor • Refactor

    • Refactor • Test • Code • Refactor
  35. • Есть баг == есть фикс == есть тест •

    Изменил что-то == Написал тесты к этому • Тесты должны выполняться на похожей на эксплуатацию среде* (хотя бы один раз) *Даже если эксплуатация - это в браузере пользователя
  36. Coverage ----------- coverage: platform linux, python 3.4.3-final-0 ----------- Name Stmts

    Miss Branch BrPart Cover --------------------------------------------------------------------------------- travel_content_api/__init__.py 3 0 0 0 100% travel_content_api/app.py 61 4 0 0 93% travel_content_api/cli.py 117 12 12 3 88% travel_content_api/clients/__init__.py 0 0 0 0 100% travel_content_api/clients/booking.py 74 3 16 3 93% travel_content_api/clients/errors.py 20 0 0 0 100% travel_content_api/clients/redis_cache.py 45 12 2 0 74% travel_content_api/clients/schemas.py 17 0 0 0 100% travel_content_api/clients/tadvisor.py 59 4 10 2 91% travel_content_api/clients/webdav.py 32 0 8 0 100% ... --------------------------------------------------------------------------------- TOTAL 1667 84 376 24 94%
  37. Hypothesis from hypothesis import given from hypothesis.strategies import integers @given(integers())

    def test_fizzbuzz_integers(number): """fizzbuzz возвращает число либо строку.""" output = fizzbuzz(number) assert isinstance(output, int) or isinstance(output, str)