Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

QA Fest 2017. Иван Цыганов. Не смешите мой coverage

244 views

Published on

Я расскажу о тестировании с точки зрения разработчика. Каждый разработчик рано или поздно приходит к выводу, что тесты необходимы. Следующий вопрос который он себе задает - а насколько хороши мои тесты. Я расскажу об инструментах и библиотеках, которые помогают оценить качество тестов в мире Python. Мы посмотрим как работает библиотека coverage.py, в каких ситуациях она бессильна и самое главное - почему. Узнаем что такое мутационное тестирование, как его можно применять в реальных проектах и как оно помогает оценить качество тестов. Увидим как работает библиотека Hypothesis и поймем в каких ситуациях она может оказаться нам полезной. В докладе будут внутренности Python-библиотек и объектов, много примеров и конечно же котики!

Published in: Education
  • Be the first to comment

  • Be the first to like this

QA Fest 2017. Иван Цыганов. Не смешите мой coverage

  1. 1. Не смешите мой coverage! Цыганов Иван Positive Technologies
  2. 2. Обо мне ✤ Люблю OpenSource ✤ Не умею frontend ✤ Добровольно пишу тесты
  3. 3. ✤ Более 15 лет практического опыта на рынке ИБ ✤ Более 700 сотрудников в 9 странах ✤ Каждый год находим более 200 уязвимостей нулевого дня ✤ Проводим более 200 аудитов безопасности в крупнейших компаниях мира ежегодно
  4. 4. MaxPatrol ✤ Pentest. Тестирование на проникновение. ✤ Audit. Системные проверки. ✤ Compliance. Соответствие стандартам. ✤ Одна из крупнейших баз знаний в мире Система контроля защищенности и соответствия стандартам.
  5. 5. MaxPatrol ✤ Pentest. Тестирование на проникновение. ✤ Compliance. Соответствие стандартам. ✤ Одна из крупнейших баз знаний в мире Система контроля защищенности и соответствия стандартам. ✤ Audit. Системные проверки.
  6. 6. > 50 000 строк кода
  7. 7. Зачем мы тестируем? ✤ Уверенность, что написанный код работает ✤ Ревью кода становится проще ✤ Гарантия, что ничего не сломалось при изменениях
  8. 8. Давайте писать тесты! def normalize_digits(digits_list): for digit in digits_list: if isinstance(digit, str): if digit.startswith('0x'): result = int(digit, base=16) else: result = int(digit) elif isinstance(digit, (int, float)): result = digit yield result
  9. 9. Плохой тест assert list(normalize_digits(['1', '0x1'])) == [1, 1] def normalize_digits(digits_list): for digit in digits_list: if isinstance(digit, str): if digit.startswith('0x'): result = int(digit, base=16) else: result = int(digit) elif isinstance(digit, (int, float)): result = digit yield result
  10. 10. Неожиданные данные >>> list(normalize_digits(['1', '2', '¯_(ツ)_/¯']))
  11. 11. Неожиданные данные >>> list(normalize_digits(['1', '2', '¯_(ツ)_/¯'])) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 7, in normalize_digits ValueError: invalid literal for int() with base 10: '¯_(ツ)_/¯'
  12. 12. Неожиданные данные >>> list(normalize_digits(['1', '2', '¯_(ツ)_/¯'])) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 7, in normalize_digits ValueError: invalid literal for int() with base 10: '¯_(ツ)_/¯' def normalize_digits(digits_list): for digit in digits_list: if isinstance(digit, str): try: if digit.startswith('0x'): result = int(digit, base=16) else: result = int(digit) except ValueError: continue elif isinstance(digit, (int, float)): result = digit yield result
  13. 13. Наличие тестов - не показатель качества
  14. 14. ✤ Проверить покрытие кода тестами ✤ ... ✤ ... Как сделать тесты лучше?
  15. 15. coverage.py ✤ Позволяет проверить покрытие кода тестами ✤ Есть плагин для pytest
  16. 16. coverage.py ✤ Позволяет проверить покрытие кода тестами ✤ Есть плагин для pytest ✤ В основном работает
  17. 17. coverage.ini [report]
 show_missing = True
 precision = 2 py.test --cov-config=coverage.ini --cov=target test.py
  18. 18. def test(): assert list(normalize_digits(['1', '0x1'])) == [1, 1] Name Stmts Miss Cover Missing -------------------------------------------- src.py 12 4 66.67% 9-12 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 yield result
  19. 19. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 yield result def test(): assert list(normalize_digits(['1', '0x1'])) == [1, 1] Name Stmts Miss Cover Missing -------------------------------------------- src.py 12 4 66.67% 9-12
  20. 20. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 yield result Name Stmts Miss Cover Missing -------------------------------------------- src.py 12 4 66.67% 9-12 def test(): assert list(normalize_digits(['1', '0x1'])) == [1, 1] assert list(normalize_digits([1, 'o_0'])) == [1]
  21. 21. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 yield result def test(): assert list(normalize_digits(['1', '0x1'])) == [1, 1] assert list(normalize_digits([1, 'o_0'])) == [1] Name Stmts Miss Cover Missing -------------------------------------------- src.py 12 0 100.00%
  22. 22. 1 def get_total_price(cart_prices): 2 if len(cart_prices) == 0: 3 return 0 4   5 result = {'TotalPrice': sum(cart_prices)} 6 if len(cart_prices) >= 2: 7 result['Discount'] = result['TotalPrice'] * 0.25 8   9 return result['TotalPrice'] - result.get('Discount') def test_get_total_price(): assert get_total_price([90, 10]) == 75 assert get_total_price( []) == 0 Name Stmts Miss Cover Missing -------------------------------------------- src.py 12 0 100.00%
  23. 23. >>> sum(normalize_digits([1, 2, None])) == 3 False 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 yield result
  24. 24. >>> sum(normalize_digits([1, 2, None])) == 3 False >>> sum(normalize_digits([1, 2, None])) 5 >>> list(normalize_digits([1, 2, None])) [1, 2, 2] 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 yield result
  25. 25. coverage.ini [report]
 show_missing = True
 precision = 2
 [run]
 branch = True py.test --cov-config=coverage.ini --cov=target test.py
  26. 26. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 yield result def test(): assert list(normalize_digits(['1', '0x1'])) == [1, 1] assert list(normalize_digits([1, 'o_0'])) == [1] Name Stmts Miss Branch BrPart Cover Missing -------------------------------------------------------- src.py 12 0 8 1 95.00% 11 ->13
  27. 27. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 yield result def test(): assert list(normalize_digits(['1', '0x1'])) == [1, 1] assert list(normalize_digits([1, 'o_0'])) == [1] Name Stmts Miss Branch BrPart Cover Missing -------------------------------------------------------- src.py 12 0 8 1 95.00% 11 ->13
  28. 28. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result def test(): assert list(normalize_digits(['1', '0x1'])) == [1, 1] assert list(normalize_digits([1, 'o_0'])) == [1] assert list(normalize_digits([1, None])) == [1] Name Stmts Miss Branch BrPart Cover Missing -------------------------------------------------------- src.py 12 0 8 1 95.00% 11 ->13
  29. 29. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result def test(): assert list(normalize_digits(['1', '0x1'])) == [1, 1] assert list(normalize_digits([1, 'o_0'])) == [1] assert list(normalize_digits([1, None])) == [1] Name Stmts Miss Branch BrPart Cover Missing -------------------------------------------------------- src.py 12 0 8 1 100.00%
  30. 30. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 base = 16 if digit.startswith('0x') else 10 5 try: 6 result = int(digit, base=base) 7 except ValueError: 8 continue 9 elif isinstance(digit, (int, float)): 10 result = digit 11 else: continue 12 yield result 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result
  31. 31. def test(): assert list(normalize_digits(['1', 1, None, 'o_0'])) == [1, 1] Name Stmts Miss Branch BrPart Cover Missing -------------------------------------------------------- src.py 12 0 8 1 100.00% 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 base = 16 if digit.startswith('0x') else 10 5 try: 6 result = int(digit, base=base) 7 except ValueError: 8 continue 9 elif isinstance(digit, (int, float)): 10 result = digit 11 else: continue 12 yield result
  32. 32. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 base = 16 if digit.startswith('0x') else 10 5 try: 6 result = int(digit, base=base) 7 except ValueError: 8 continue 9 elif isinstance(digit, (int, float)): 10 result = digit 11 else: continue 12 yield result def test(): assert list(normalize_digits(['1', 1, None, 'o_0'])) == [1, 1] Name Stmts Miss Branch BrPart Cover Missing -------------------------------------------------------- src.py 12 0 8 1 100.00%
  33. 33. Как считать coverage? Все строки Реально выполненные строки- Непокрытые строки=
  34. 34. Все строки Source coverage.parser.PythonParser Statements
  35. 35. coverage.parser.PythonParser ✤ Обходит все токены и отмечает «интересные» факты ✤ Компилирует код. Обходит code-object и сохраняет номера строк
  36. 36. Обход токенов ✤ Запоминает определения классов ✤ «Сворачивает» многострочные выражения ✤ Исключает комментарии
  37. 37. Обход байткода ✤ Рекурсивно обходит все code object в code.co_const ✤ Полностью повторяет метод dis.findlinestarts ✤ Анализирует code_obj.co_lnotab ✤ Генерирует пару (номер байткода, номер строки)
  38. 38. Как считать coverage --branch? Все переходы Реально выполненные переходы- Непокрытые переходы=
  39. 39. Все переходы Source coverage.parser.AstArcAnalyzer (from_line, to_line) coverage.parser.PythonParser
  40. 40. coverage.parser.AstArcAnalyzer ✤ Обходит все AST-дерево с корневой ноды ✤ В зависимости от типа ноды генерирует варианты перехода между строками
  41. 41. Обработка ноды class While(stmt): _fields = ( 'test', 'body', 'orelse', ) while i<10: print(i) i += 1
  42. 42. Обработка ноды class While(stmt): _fields = ( 'test', 'body', 'orelse', ) while i<10: print(i) i += 1 else: print('Done!')
  43. 43. Выполненные строки sys.settrace(tracefunc) Set the system’s trace function, which allows you to implement a Python source code debugger in Python. Trace functions should have three arguments: frame, event, and arg. frame is the current stack frame. event is a string: 'call', 'line', 'return', 'exception', 'c_call', 'c_return', or 'c_exception'. arg depends on the event type.
  44. 44. PyTracer «call» event ✤ Сохраняем данные предыдущего контекста ✤ Начинаем собирать данные нового контекста ✤ Учитываем особенности генераторов
  45. 45. PyTracer «line» event ✤ Запоминаем выполняемую строку ✤ Запоминаем переход между строками
  46. 46. PyTracer «return» event ✤ Отмечаем выход из контекста ✤ Помним о том, что yield это тоже выход из контекста
  47. 47. Отчет ✤ Что выполнялось ✤ Что должно было выполниться
  48. 48. Отчет ✤ Что выполнялось ✤ Что должно было выполниться ✤ Ругаемся
  49. 49. Зачем такие сложности? for i in some_list: if i == 'Hello': print(i + ' World!') elif i == 'Skip': continue else: break else: print(r'¯_(ツ)_/¯')
  50. 50. Серебряная пуля?
  51. 51. Не совсем…
  52. 52. Что может пойти не так? def make_dict(a, b, c): return { 'a': a, 'b': b if a > 1 else 0, 'c': [ i for i in range(c) if i < (a * 10) ] }
  53. 53. ✤ Проверить покрытие кода тестами ✤ ... ✤ ... Как сделать тесты лучше?
  54. 54. ✤ Проверить покрытие кода тестами ✤ Попробовать мутационное тестирование ✤ ... Как сделать тесты лучше?
  55. 55. Мутационное тестирование ✤ Берем тестируемый код ✤ Мутируем ✤ Тестируем мутантов нашими тестами ✤ Тест не упал -> плохой тест
  56. 56. Мутационное тестирование ✤ Берем тестируемый код ✤ Мутируем ✤ Тестируем мутантов нашими тестами ✤ Если тест не упал -> это плохой тест✤ Тест не упал -> плохой тест
  57. 57. Идея def mul(a, b): return a * b def test_mul(): assert mul(2, 2) == 4
  58. 58. Идея def mul(a, b): return a * b def test_mul(): assert mul(2, 2) == 4 def mul(a, b): return a ** b
  59. 59. Идея def mul(a, b): return a * b def test_mul(): assert mul(2, 2) == 4 def mul(a, b): return a ** b def mul(a, b): return a + b
  60. 60. Идея def mul(a, b): return a * b def mul(a, b): return a ** b def mul(a, b): return a + b def test_mul(): assert mul(2, 2) == 4 assert mul(2, 3) == 6
  61. 61. mutpy ✤ Анализирует исходный код ✤ Модифицирует некоторые AST-ноды ✤ Запускает тесты ✤ Проверяет результат запуска тестов
  62. 62. Реализация Source NodeTransformer compile run test
  63. 63. Мутации 2 for digit in digits_list: 3 if (not isinstance(digit, str)): 4 try: 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result
  64. 64. Мутации 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result 4 try: 5 if digit.startswith('mutpy'): 6 result = int(digit, base=16)
  65. 65. Мутации 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result 8 result = int(digit) 9 except ValueError: 10 pass
  66. 66. [*] Mutation score [1.28949 s]: 90.0% - all: 10 - killed: 7 (90.0%) - survived: 1 (10.0%) - incompetent: 0 (0.0%) - timeout: 0 (0.0%) 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result def test(): assert list(normalize_digits(['100', '0xA'])) == [100, 10] assert list(normalize_digits([1, 'o_0', None])) == [1]
  67. 67. [*] Mutation score [1.28949 s]: 70.0% - all: 10 - killed: 7 (70.0%) - survived: 3 (30.0%) - incompetent: 0 (0.0%) - timeout: 0 (0.0%) 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 digit = int(digit, base=16) 7 else: 8 digit = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 digit = digit 13 else: continue 14 yield digit def test(): assert list(normalize_digits(['1', '0x1'])) == [1, 1] assert list(normalize_digits([1, 'o_0', None])) == [1] - survived: 1 (10.0%)
  68. 68. … --------------------------------------------------------------------- 6: result = int(digit, base=16) 7: else: 8: result = int(digit) 9: except ValueError: ~10: break 11: elif isinstance(digit, (int, float)): 12: result = digit 13: else: 14: continue 15: yield result ---------------------------------------------------------------------- [0.03628 s] survived … [*] Mutation score [1.28949 s]: 90.0% - all: 10 - killed: 7 (90.0%) - survived: 1 (10.0%) - incompetent: 0 (0.0%) - timeout: 0 (0.0%)
  69. 69. [*] Mutation score [1.07053 s]: 100.0% - all: 10 - killed: 10 (100.0%) - survived: 0 (0.0%) - incompetent: 0 (0.0%) - timeout: 0 (0.0%) 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result def test(): assert list(normalize_digits(['100', '0xA'])) == [100, 10] assert list(normalize_digits([None, 'o_0', 1])) == [1]
  70. 70. Идея имеет право на жизнь и работает! Но требует много ресурсов.
  71. 71. Name Stmts Miss Cover Missing ---------------------------------------------------- src.py 13 0 100.00% def test(): assert list(normalize_digits(['100', '0xA'])) == [100, 10] assert list(normalize_digits([None, 'o_0', 1])) == [1] 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result
  72. 72. Name Stmts Miss Branch BrPart Cover Missing ------------------------------------------------------------------ src.py 13 0 8 0 100.00% def test(): assert list(normalize_digits(['100', '0xA'])) == [100, 10] assert list(normalize_digits([None, 'o_0', 1])) == [1] 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result
  73. 73. 1 def get_total_price(cart_prices):   2 result = {'TotalPrice': sum(cart_prices)} 3 if len(cart_prices) >= 2: 4 result['Discount'] = result['TotalPrice'] * 0.25 5   6 return result['TotalPrice'] - result.get(‘Discount’, 0) def test_get_total_price(): assert get_total_price([90, 10]) == 75 assert get_total_price( []) == 0 assert get_total_price([90]) == 90 Name Stmts Miss Branch BrPart Cover Missing ---------------------------------------------------------- target.py 5 0 2 0 100.00%
  74. 74. ✤ Проверить покрытие кода тестами ✤ Попробовать мутационное тестирование ✤ ... Как сделать тесты лучше?
  75. 75. ✤ Проверить покрытие кода тестами ✤ Попробовать мутационное тестирование ✤ Генерировать входные данные Как сделать тесты лучше?
  76. 76. Hypothesis ✤ Возможность генерации данных ✤ Позволяет проверить поведение кода
  77. 77. Hypothesis ✤ Возможность генерации данных ✤ Позволяет проверить поведение кода ✤ Подходит не для всех задач :(
  78. 78. Hypothesis. Генерация данных >>> from hypothesis import strategies as st >>> st.integers().example() 132598732931307445807900680032693714775
  79. 79. Hypothesis. Генерация данных >>> from hypothesis import strategies as st >>> st.integers().example() 132598732931307445807900680032693714775 >>> st.integers(min_value=0, max_value=100).example() 26
  80. 80. Hypothesis. Генерация данных >>> from hypothesis import strategies as st >>> st.integers().example() 132598732931307445807900680032693714775 >>> st.integers(min_value=0, max_value=100).example() 26 >>> st.text().example() 'U0007223d‫ﶝ‬U000d4ab2U000a477aU000c54e1# ' >>> st.text().example() '傖'
  81. 81. 
 keys = (
 st.integers()
 |
 st.floats()
 |
 st.text(alphabet=string.ascii_letters)
 ) 

  82. 82. 
 keys = (
 st.integers()
 |
 st.floats()
 |
 st.text(alphabet=string.ascii_letters)
 ) 
 simple_values = (
 keys
 |
 st.booleans()
 |
 st.none()
 |
 st.binary()
 |
 st.text()
 )
  83. 83. 
 keys = st.integers()|st.floats()|st.text(alphabet= ... simple_values = keys|st.booleans()|st.none()| ... x = st.dictionaries(keys, simple_values) x.example()
 {
 '': b'x9cR',
 'bHNdRVEdJBM': 'U000b2611U00106014x13',
 4.121937670036e+86: '.x16U0010e8c5U000b3ae6-',
 -128805459898745084717413287023876194805: None
 }
  84. 84. 
 keys = st.integers()|st.floats()|st.text(alphabet= ... simple_values = keys|st.booleans()|st.none()| ...
 
 nested_values = (
 simple_values
 |
 st.recursive(
 simple_values,
 lambda item: ( st.dictionaries(keys, item) | st.lists(item) )
 )
 )
  85. 85. keys = st.integers()|st.floats()|st.text(alphabet= ... simple_values = keys|st.booleans()|st.none()|st.bin ...
 nested_values = simple_values|st.recursive(simple_ ... x = st.dictionaries(keys, nested_values)
 {
 'UqcOU': {
 -2.00001: 'x', 'gZlJOoWxQ': [
 -2.264270, False
 ]
 }, 
 'WwVKZScYmVm': '', 
 'sBt': -4508536492226585216256
 }
  86. 86. ?
  87. 87. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result
  88. 88. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result values = ( st.text() | st.none() | st.integers() | st.floats() ) @given(st.lists(values)) def test_normalize_digits(digits_list): result = normalize_digits(digits_list) for item in result: assert isinstance(item, (int, float))
  89. 89. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result values = ( st.text() | st.none() | st.integers() | st.floats() ) @given(st.lists(values)) def test_normalize_digits(digits_list): result = normalize_digits(digits_list) for item in result: assert isinstance(item, (int, float)) values = ( st.integers() | st.floats(allow_infinity=False, allow_nan=False) ) @given(st.lists(values)) def test_normalize_digits(digits_list): strings_list = list(map(str, digits_list)) result = list(normalize_digits(strings_list)) assert result == digits_list
  90. 90. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result values = ( st.text() | st.none() | st.integers() | st.floats() ) @given(st.lists(values)) def test_normalize_digits(digits_list): result = normalize_digits(digits_list) for item in result: assert isinstance(item, (int, float)) values = ( st.integers() | st.floats(allow_infinity=False, allow_nan=False) ) @given(st.lists(values)) def test_normalize_digits(digits_list): strings_list = list(map(str, digits_list)) result = list(normalize_digits(strings_list)) assert result == digits_list digits_list = [0.0] @given(st.lists(values)) def test_normalize_digits(digits_list): strings_list = list(map(str, digits_list)) result = list(normalize_digits(strings_list)) > assert result == digits_list E assert [] == [0.0] E Right contains more items, first extra item: 0.0 E Use -v to get the full diff test.py: AssertionError Falsifying example: test_normalize_digits(digits_list=[0.0])
  91. 91. 1 def normalize_digits(digits_list): 2 for digit in digits_list: 3 if isinstance(digit, str): 4 try: 5 if digit.startswith('0x'): 6 result = int(digit, base=16) 7 else: 8 result = int(digit) 9 except ValueError: 10 continue 11 elif isinstance(digit, (int, float)): 12 result = digit 13 else: continue 14 yield result values = ( st.text() | st.none() | st.integers() | st.floats() ) @given(st.lists(values)) def test_normalize_digits(digits_list): result = normalize_digits(digits_list) for item in result: assert isinstance(item, (int, float)) values = ( st.integers() | st.floats(allow_infinity=False, allow_nan=False) ) @given(st.lists(values)) def test_normalize_digits(digits_list): strings_list = list(map(str, digits_list)) result = list(normalize_digits(strings_list)) assert result == digits_list digits_list = [0.0] @given(st.lists(values)) def test_normalize_digits(digits_list): strings_list = list(map(str, digits_list)) result = list(normalize_digits(strings_list)) > assert result == digits_list E assert [] == [0.0] E Right contains more items, first extra item: 0.0 E Use -v to get the full diff test.py: AssertionError Falsifying example: test_normalize_digits(digits_list=[0.0]) >>> int('0.0') Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: '0.0'
  92. 92. Каким же должен был быть код?!
  93. 93. def normalize_digits(digits_list): for digit in digits_list: if isinstance(digit, str): if digit.startswith('0x'): method = partial(int, base=16) elif '.' in digit or 'e' in digit: method = float else: method = int elif isinstance(digit, (int, float)): method = lambda x: x else: continue try: result = method(digit) except ValueError: continue yield result
  94. 94. def normalize_digits(digits_list): for digit in digits_list: if isinstance(digit, str): if digit.startswith('0x'): method = partial(int, base=16) elif '.' in digit or 'e' in digit: method = float else: method = int elif isinstance(digit, (int, float)): method = lambda x: x else: continue try: result = method(digit) except ValueError: continue yield result def test_normalize(): assert list(normalize_digits(['100', '1.1', '0xA'])) == [100, 1.1, 10] assert list(normalize_digits([None, 'o_0', 1, '1e-05'])) == [1, 1e-05]
  95. 95. def normalize_digits(digits_list): for digit in digits_list: if isinstance(digit, str): if digit.startswith('0x'): method = partial(int, base=16) elif '.' in digit or 'e' in digit: method = float else: method = int elif isinstance(digit, (int, float)): method = lambda x: x else: continue try: result = method(digit) except ValueError: continue yield result def test_normalize(): assert list(normalize_digits(['100', '1.1', '0xA'])) == [100, 1.1, 10] assert list(normalize_digits([None, 'o_0', 1, '1e-05'])) == [1, 1e-05] Name Stmts Miss Cover Missing ---------------------------------------------------- src.py 17 0 100.00%
  96. 96. def normalize_digits(digits_list): for digit in digits_list: if isinstance(digit, str): if digit.startswith('0x'): method = partial(int, base=16) elif '.' in digit or 'e' in digit: method = float else: method = int elif isinstance(digit, (int, float)): method = lambda x: x else: continue try: result = method(digit) except ValueError: continue yield result def test_normalize(): assert list(normalize_digits(['100', '1.1', '0xA'])) == [100, 1.1, 10] assert list(normalize_digits([None, 'o_0', 1, '1e-05'])) == [1, 1e-05] Name Stmts Miss Cover Missing ---------------------------------------------------- src.py 17 0 100.00% Name Stmts Miss Branch BrPart Cover Missing ------------------------------------------------------------------ src.py 17 0 12 0 100.00%
  97. 97. def normalize_digits(digits_list): for digit in digits_list: if isinstance(digit, str): if digit.startswith('0x'): method = partial(int, base=16) elif '.' in digit or 'e' in digit: method = float else: method = int elif isinstance(digit, (int, float)): method = lambda x: x else: continue try: result = method(digit) except ValueError: continue yield result def test_normalize(): assert list(normalize_digits(['100', '1.1', '0xA'])) == [100, 1.1, 10] assert list(normalize_digits([None, 'o_0', 1, '1e-05'])) == [1, 1e-05] Name Stmts Miss Cover Missing ---------------------------------------------------- src.py 17 0 100.00% Name Stmts Miss Branch BrPart Cover Missing ------------------------------------------------------------------ src.py 17 0 12 0 100.00% [*] Mutation score [0.88045 s]: 100.0% - all: 18 - killed: 18 (100.0%) - survived: 0 (0.0%) - incompetent: 0 (0.0%) - timeout: 0 (0.0%)
  98. 98. >>> sum(list(normalize_digits([False, True, 2, 3, 4]))) 10
  99. 99. >>> sum(list(normalize_digits([False, True, 2, 3, 4]))) 10 >>> isinstance(False, int) True >>> isinstance(True, int) True
  100. 100. Библиотеки несовершенны
  101. 101. 100% coverage расслабляет команду Библиотеки несовершенны
  102. 102. 100% coverage расслабляет команду Библиотеки несовершенны Наличие тестов - не показатель качества кода
  103. 103. Спасибо за внимание! mi.0-0.im

×