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

Testing101

 Testing101

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

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

Avatar for Moscow Python Meetup

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)