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

QA Fest 2017. Не смешите мой coverage!

QA Fest 2017. Не смешите мой coverage!

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

Ivan Tsyganov

September 27, 2017
Tweet

More Decks by Ivan Tsyganov

Other Decks in Programming

Transcript

  1. ✤ Более 15 лет практического опыта на рынке ИБ ✤

    Более 700 сотрудников в 9 странах ✤ Каждый год находим более 200 уязвимостей нулевого дня ✤ Проводим более 200 аудитов безопасности в крупнейших компаниях мира ежегодно
  2. MaxPatrol ✤ Pentest. Тестирование на проникновение. ✤ Audit. Системные проверки.

    ✤ Compliance. Соответствие стандартам. ✤ Одна из крупнейших баз знаний в мире Система контроля защищенности и соответствия стандартам.
  3. MaxPatrol ✤ Pentest. Тестирование на проникновение. ✤ Compliance. Соответствие стандартам.

    ✤ Одна из крупнейших баз знаний в мире Система контроля защищенности и соответствия стандартам. ✤ Audit. Системные проверки.
  4. Зачем мы тестируем? ✤ Уверенность, что написанный код работает ✤

    Ревью кода становится проще ✤ Гарантия, что ничего не сломалось при изменениях
  5. Давайте писать тесты! 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
  6. Плохой тест 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
  7. Неожиданные данные >>> 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: '¯\_(ツ)_/¯'
  8. Неожиданные данные >>> 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
  9. 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
  10. 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
  11. 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]
  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 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%
  13. 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%
  14. >>> 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
  15. >>> 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
  16. coverage.ini [report]
 show_missing = True
 precision = 2
 [run]
 branch

    = True py.test --cov-config=coverage.ini --cov=target test.py
  17. 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
  18. 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
  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 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
  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 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%
  21. 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
  22. 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
  23. 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%
  24. coverage.parser.PythonParser ✤ Обходит все токены и отмечает «интересные» факты ✤

    Компилирует код. Обходит code-object и сохраняет номера строк
  25. Обход байткода ✤ Рекурсивно обходит все code object в code.co_const

    ✤ Полностью повторяет метод dis.findlinestarts ✤ Анализирует code_obj.co_lnotab ✤ Генерирует пару (номер байткода, номер строки)
  26. coverage.parser.AstArcAnalyzer ✤ Обходит все AST-дерево с корневой ноды ✤ В

    зависимости от типа ноды генерирует варианты перехода между строками
  27. Выполненные строки 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.
  28. PyTracer «call» event ✤ Сохраняем данные предыдущего контекста ✤ Начинаем

    собирать данные нового контекста ✤ Учитываем особенности генераторов
  29. PyTracer «return» event ✤ Отмечаем выход из контекста ✤ Помним

    о том, что yield это тоже выход из контекста
  30. Зачем такие сложности? for i in some_list: if i ==

    'Hello': print(i + ' World!') elif i == 'Skip': continue else: break else: print(r'¯\_(ツ)_/¯')
  31. Что может пойти не так? 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) ] }
  32. Мутационное тестирование ✤ Берем тестируемый код ✤ Мутируем ✤ Тестируем

    мутантов нашими тестами ✤ Тест не упал -> плохой тест
  33. Мутационное тестирование ✤ Берем тестируемый код ✤ Мутируем ✤ Тестируем

    мутантов нашими тестами ✤ Если тест не упал -> это плохой тест ✤ Тест не упал -> плохой тест
  34. Идея def mul(a, b): return a * b def test_mul():

    assert mul(2, 2) == 4 def mul(a, b): return a ** b
  35. Идея 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
  36. Идея 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
  37. mutpy ✤ Анализирует исходный код ✤ Модифицирует некоторые AST-ноды ✤

    Запускает тесты ✤ Проверяет результат запуска тестов
  38. Мутации 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
  39. Мутации 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)
  40. Мутации 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
  41. [*] 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]
  42. [*] 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%)
  43. … --------------------------------------------------------------------- 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%)
  44. [*] 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]
  45. 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
  46. 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
  47. 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%
  48. ✤ Проверить покрытие кода тестами ✤ Попробовать мутационное тестирование ✤

    Генерировать входные данные Как сделать тесты лучше?
  49. Hypothesis. Генерация данных >>> from hypothesis import strategies as st

    >>> st.integers().example() 132598732931307445807900680032693714775
  50. Hypothesis. Генерация данных >>> from hypothesis import strategies as st

    >>> st.integers().example() 132598732931307445807900680032693714775 >>> st.integers(min_value=0, max_value=100).example() 26
  51. 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ﶝ\U000d4ab2\U000a477a\U000c54e1# ' >>> st.text().example() '傖'
  52. 
 keys = (
 st.integers()
 |
 st.floats()
 |
 st.text(alphabet=string.ascii_letters)
 )

    
 simple_values = (
 keys
 |
 st.booleans()
 |
 st.none()
 |
 st.binary()
 |
 st.text()
 )
  53. 
 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': '\U000b2611\U00106014\x13',
 4.121937670036e+86: '.\x16\U0010e8c5\U000b3ae6-',
 -128805459898745084717413287023876194805: None
 }
  54. 
 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) )
 )
 )
  55. 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
 }
  56. ?

  57. 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
  58. 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))
  59. 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
  60. 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])
  61. 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'
  62. 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
  63. 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]
  64. 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%
  65. 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%
  66. 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%)