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

Q
Не смешите мой coverage!
Цыганов Иван
Positive Technologies
Обо мне
✤ Люблю OpenSource
✤ Не умею frontend
✤ Добровольно пишу тесты
✤ Более 15 лет практического опыта на рынке ИБ
✤ Более 700 сотрудников в 9 странах
✤ Каждый год находим более 200 уязвимостей
нулевого дня
✤ Проводим более 200 аудитов безопасности в
крупнейших компаниях мира ежегодно
MaxPatrol
✤ Pentest. Тестирование на проникновение.
✤ Audit. Системные проверки.
✤ Compliance. Соответствие стандартам.
✤ Одна из крупнейших баз знаний в мире
Система контроля защищенности и соответствия
стандартам.
MaxPatrol
✤ Pentest. Тестирование на проникновение.
✤ Compliance. Соответствие стандартам.
✤ Одна из крупнейших баз знаний в мире
Система контроля защищенности и соответствия
стандартам.
✤ Audit. Системные проверки.
> 50 000 строк кода
Зачем мы тестируем?
✤ Уверенность, что написанный код работает
✤ Ревью кода становится проще
✤ Гарантия, что ничего не сломалось при изменениях
Давайте писать тесты!
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
Плохой тест
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
Неожиданные данные
>>> list(normalize_digits(['1', '2', '¯_(ツ)_/¯']))
Неожиданные данные
>>> 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: '¯_(ツ)_/¯'
Неожиданные данные
>>> 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
Наличие тестов - не показатель
качества
✤ Проверить покрытие кода тестами
✤ ...
✤ ...
Как сделать тесты лучше?
coverage.py
✤ Позволяет проверить покрытие кода тестами
✤ Есть плагин для pytest
coverage.py
✤ Позволяет проверить покрытие кода тестами
✤ Есть плагин для pytest
✤ В основном работает
coverage.ini
[report]

show_missing = True

precision = 2
py.test --cov-config=coverage.ini --cov=target test.py
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
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
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]
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%
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%
>>> 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
>>> 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
QA Fest 2017. Иван Цыганов. Не смешите мой coverage
coverage.ini
[report]

show_missing = True

precision = 2

[run]

branch = True
py.test --cov-config=coverage.ini --cov=target test.py
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
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
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
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%
QA Fest 2017. Иван Цыганов. Не смешите мой coverage
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
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
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%
QA Fest 2017. Иван Цыганов. Не смешите мой coverage
Как считать coverage?
Все строки
Реально выполненные
строки- Непокрытые
строки=
Все строки
Source
coverage.parser.PythonParser
Statements
coverage.parser.PythonParser
✤ Обходит все токены и отмечает «интересные» факты
✤ Компилирует код. Обходит code-object и сохраняет
номера строк
Обход токенов
✤ Запоминает определения классов
✤ «Сворачивает» многострочные выражения
✤ Исключает комментарии
Обход байткода
✤ Рекурсивно обходит все code object в code.co_const
✤ Полностью повторяет метод dis.findlinestarts
✤ Анализирует code_obj.co_lnotab
✤ Генерирует пару (номер байткода, номер строки)
Как считать coverage --branch?
Все переходы
Реально выполненные
переходы- Непокрытые
переходы=
Все переходы
Source
coverage.parser.AstArcAnalyzer
(from_line, to_line)
coverage.parser.PythonParser
coverage.parser.AstArcAnalyzer
✤ Обходит все AST-дерево с корневой ноды
✤ В зависимости от типа ноды генерирует варианты
перехода между строками
Обработка ноды
class While(stmt):
_fields = (
'test',
'body',
'orelse',
)
while i<10:
print(i)
i += 1
Обработка ноды
class While(stmt):
_fields = (
'test',
'body',
'orelse',
)
while i<10:
print(i)
i += 1
else:
print('Done!')
Выполненные строки
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.
PyTracer «call» event
✤ Сохраняем данные предыдущего контекста
✤ Начинаем собирать данные нового контекста
✤ Учитываем особенности генераторов
PyTracer «line» event
✤ Запоминаем выполняемую строку
✤ Запоминаем переход между строками
PyTracer «return» event
✤ Отмечаем выход из контекста
✤ Помним о том, что yield это тоже выход из контекста
Отчет
✤ Что выполнялось
✤ Что должно было выполниться
Отчет
✤ Что выполнялось
✤ Что должно было выполниться
✤ Ругаемся
Зачем такие сложности?
for i in some_list:
if i == 'Hello':
print(i + ' World!')
elif i == 'Skip':
continue
else:
break
else:
print(r'¯_(ツ)_/¯')
Серебряная пуля?
Не совсем…
Что может пойти не так?
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)
]
}
QA Fest 2017. Иван Цыганов. Не смешите мой coverage
✤ Проверить покрытие кода тестами
✤ ...
✤ ...
Как сделать тесты лучше?
✤ Проверить покрытие кода тестами
✤ Попробовать мутационное тестирование
✤ ...
Как сделать тесты лучше?
Мутационное тестирование
✤ Берем тестируемый код
✤ Мутируем
✤ Тестируем мутантов нашими тестами
✤ Тест не упал -> плохой тест
Мутационное тестирование
✤ Берем тестируемый код
✤ Мутируем
✤ Тестируем мутантов нашими тестами
✤ Если тест не упал -> это плохой тест✤ Тест не упал -> плохой тест
Идея
def mul(a, b):
return a * b
def test_mul():
assert mul(2, 2) == 4
Идея
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
def test_mul():
assert mul(2, 2) == 4
def mul(a, b):
return a ** b
def mul(a, b):
return a + b
Идея
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
mutpy
✤ Анализирует исходный код
✤ Модифицирует некоторые AST-ноды
✤ Запускает тесты
✤ Проверяет результат запуска тестов
Реализация
Source
NodeTransformer
compile
run test
Мутации
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
Мутации
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)
Мутации
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
[*] 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]
[*] 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%)
…
---------------------------------------------------------------------
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%)
[*] 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]
Идея имеет право на жизнь и работает!
Но требует много ресурсов.
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
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
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%
✤ Проверить покрытие кода тестами
✤ Попробовать мутационное тестирование
✤ ...
Как сделать тесты лучше?
✤ Проверить покрытие кода тестами
✤ Попробовать мутационное тестирование
✤ Генерировать входные данные
Как сделать тесты лучше?
Hypothesis
✤ Возможность генерации данных
✤ Позволяет проверить поведение кода
Hypothesis
✤ Возможность генерации данных
✤ Позволяет проверить поведение кода
✤ Подходит не для всех задач :(
Hypothesis. Генерация данных
>>> from hypothesis import strategies as st
>>> st.integers().example()
132598732931307445807900680032693714775
Hypothesis. Генерация данных
>>> from hypothesis import strategies as st
>>> st.integers().example()
132598732931307445807900680032693714775
>>> st.integers(min_value=0, max_value=100).example()
26
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()
'傖'


keys = (

st.integers()

|

st.floats()

|

st.text(alphabet=string.ascii_letters)

)




keys = (

st.integers()

|

st.floats()

|

st.text(alphabet=string.ascii_letters)

)


simple_values = (

keys

|

st.booleans()

|

st.none()

|

st.binary()

|

st.text()

)


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

}


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)
)

)

)
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

}
?
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
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))
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
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])
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'
Каким же должен был быть код?!
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 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]
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%
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%
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%)
QA Fest 2017. Иван Цыганов. Не смешите мой coverage
>>> sum(list(normalize_digits([False, True, 2, 3, 4])))
10
>>> sum(list(normalize_digits([False, True, 2, 3, 4])))
10
>>> isinstance(False, int)
True
>>> isinstance(True, int)
True
Библиотеки несовершенны
100% coverage расслабляет команду
Библиотеки несовершенны
100% coverage расслабляет команду
Библиотеки несовершенны
Наличие тестов - не показатель
качества кода
Спасибо за внимание!
mi.0-0.im
1 of 108

Recommended

Теоретические основы Application Security by
Теоретические основы Application SecurityТеоретические основы Application Security
Теоретические основы Application SecurityPositive Hack Days
738 views88 slides
Производительность в Django by
Производительность в DjangoПроизводительность в Django
Производительность в DjangoMoscowDjango
2.5K views34 slides
Профилирование и отладка Django by
Профилирование и отладка DjangoПрофилирование и отладка Django
Профилирование и отладка DjangoVladimir Rudnyh
2.1K views131 slides
[JAM 1.1] Clean Code (Paul Malikov) by
[JAM 1.1] Clean Code (Paul Malikov)[JAM 1.1] Clean Code (Paul Malikov)
[JAM 1.1] Clean Code (Paul Malikov)Evgeny Kaziak
551 views55 slides
Meet Magento Belarus debug Pavel Novitsky (rus) by
Meet Magento Belarus debug Pavel Novitsky (rus)Meet Magento Belarus debug Pavel Novitsky (rus)
Meet Magento Belarus debug Pavel Novitsky (rus)Pavel Novitsky
630 views46 slides
Декораторы в Python и их практическое использование by
Декораторы в Python и их практическое использование Декораторы в Python и их практическое использование
Декораторы в Python и их практическое использование Sergey Schetinin
5.1K views24 slides

More Related Content

What's hot

SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во... by
SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...
SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...DevDay
577 views48 slides
Python и его тормоза by
Python и его тормозаPython и его тормоза
Python и его тормозаAlexander Shigin
1.3K views53 slides
XML Magic by
XML MagicXML Magic
XML MagicIgor Khotin
951 views39 slides
Лекция 7. Исключения и менеджеры контекста. by
Лекция 7. Исключения и менеджеры контекста.Лекция 7. Исключения и менеджеры контекста.
Лекция 7. Исключения и менеджеры контекста.Roman Brovko
27.4K views39 slides
Магия в Python: Дескрипторы. Что это? by
Магия в Python: Дескрипторы. Что это?Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?PyNSK
3.1K views16 slides
Тестирование программных фильтров безопасности by
Тестирование программных фильтров безопасностиТестирование программных фильтров безопасности
Тестирование программных фильтров безопасностиZestranec
752 views23 slides

What's hot(20)

SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во... by DevDay
SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...
SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...
DevDay577 views
Лекция 7. Исключения и менеджеры контекста. by Roman Brovko
Лекция 7. Исключения и менеджеры контекста.Лекция 7. Исключения и менеджеры контекста.
Лекция 7. Исключения и менеджеры контекста.
Roman Brovko27.4K views
Магия в Python: Дескрипторы. Что это? by PyNSK
Магия в Python: Дескрипторы. Что это?Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?
PyNSK3.1K views
Тестирование программных фильтров безопасности by Zestranec
Тестирование программных фильтров безопасностиТестирование программных фильтров безопасности
Тестирование программных фильтров безопасности
Zestranec752 views
Лекция 2. Всё, что вы хотели знать о функциях в Python. by Roman Brovko
Лекция 2. Всё, что вы хотели знать о функциях в Python.Лекция 2. Всё, что вы хотели знать о функциях в Python.
Лекция 2. Всё, что вы хотели знать о функциях в Python.
Roman Brovko27.7K views
Лекция 11. Тестирование. by Roman Brovko
Лекция 11. Тестирование.Лекция 11. Тестирование.
Лекция 11. Тестирование.
Roman Brovko27.2K views
Making of external DSL for Django ORM - Павел Петлинский, Rambler&Co by it-people
Making of external DSL for Django ORM - Павел Петлинский, Rambler&CoMaking of external DSL for Django ORM - Павел Петлинский, Rambler&Co
Making of external DSL for Django ORM - Павел Петлинский, Rambler&Co
it-people624 views
Лекция 1. Начало. by Roman Brovko
Лекция 1. Начало.Лекция 1. Начало.
Лекция 1. Начало.
Roman Brovko27.8K views
Лекция 5. Встроенные коллекции и модуль collections. by Roman Brovko
Лекция 5. Встроенные коллекции и модуль collections.Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.
Roman Brovko27.2K views
kranonitS20 Сергей Бурма. Django - легко, быстро, эффективно by Krivoy Rog IT Community
kranonitS20 Сергей Бурма. Django - легко, быстро, эффективноkranonitS20 Сергей Бурма. Django - легко, быстро, эффективно
kranonitS20 Сергей Бурма. Django - легко, быстро, эффективно
Web осень 2013 лекция 6 by Technopark
Web осень 2013 лекция 6Web осень 2013 лекция 6
Web осень 2013 лекция 6
Technopark701 views
Одномерные массивы целых чисел by Andrey Dolinin
Одномерные массивы целых чиселОдномерные массивы целых чисел
Одномерные массивы целых чисел
Andrey Dolinin2.4K views
Красота и изящность стандартной библиотеки Python by Python Meetup
Красота и изящность стандартной библиотеки PythonКрасота и изящность стандартной библиотеки Python
Красота и изящность стандартной библиотеки Python
Python Meetup6.5K views
Исключительно простая теория AppSec .NET by Vladimir Kochetkov
Исключительно простая теория AppSec .NETИсключительно простая теория AppSec .NET
Исключительно простая теория AppSec .NET
Vladimir Kochetkov1.7K views
Григорий Демченко, Универсальный адаптер by Sergey Platonov
Григорий Демченко, Универсальный адаптерГригорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптер
Sergey Platonov6.1K views
Лекция 12. Быстрее, Python, ещё быстрее. by Roman Brovko
Лекция 12. Быстрее, Python, ещё быстрее.Лекция 12. Быстрее, Python, ещё быстрее.
Лекция 12. Быстрее, Python, ещё быстрее.
Roman Brovko27.2K views
Лекция 3. Декораторы и модуль functools. by Roman Brovko
Лекция 3. Декораторы и модуль functools.Лекция 3. Декораторы и модуль functools.
Лекция 3. Декораторы и модуль functools.
Roman Brovko27.3K views

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

«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb... by
«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...
«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...Mail.ru Group
9.5K views36 slides
Формальные методы защиты приложений by
Формальные методы защиты приложенийФормальные методы защиты приложений
Формальные методы защиты приложенийPositive Hack Days
887 views43 slides
DSLs in Lisp and Clojure by
DSLs in Lisp and ClojureDSLs in Lisp and Clojure
DSLs in Lisp and ClojureVasil Remeniuk
667 views31 slides
Algo 00 by
Algo 00Algo 00
Algo 00Alex Tarasov
564 views46 slides
Python by
PythonPython
Pythonpelid
579 views16 slides

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

«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb... by Mail.ru Group
«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...
«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...
Mail.ru Group9.5K views
Формальные методы защиты приложений by Positive Hack Days
Формальные методы защиты приложенийФормальные методы защиты приложений
Формальные методы защиты приложений
Positive Hack Days887 views
Python by pelid
PythonPython
Python
pelid579 views
Улучшение качества открытого программного обеспечения с помощью инструментов ... by Andrey Karpov
Улучшение качества открытого программного обеспечения с помощью инструментов ...Улучшение качества открытого программного обеспечения с помощью инструментов ...
Улучшение качества открытого программного обеспечения с помощью инструментов ...
Andrey Karpov36 views
20130429 dynamic c_c++_program_analysis-alexey_samsonov by Computer Science Club
20130429 dynamic c_c++_program_analysis-alexey_samsonov20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonov
Moscow Python Conf 2016. Почему 100% покрытие это плохо? by Ivan Tsyganov
Moscow Python Conf 2016. Почему 100% покрытие это плохо?Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Ivan Tsyganov952 views
Статический анализ кода by Pavel Tsukanov
Статический анализ кода Статический анализ кода
Статический анализ кода
Pavel Tsukanov689 views
статический анализ кода by Andrey Karpov
статический анализ кодастатический анализ кода
статический анализ кода
Andrey Karpov387 views
8 встреча — Язык программирования Python (В. Ананьев) by Smolensk Computer Science Club
8 встреча — Язык программирования Python (В. Ананьев)8 встреча — Язык программирования Python (В. Ананьев)
8 встреча — Язык программирования Python (В. Ананьев)

More from QAFest

QA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилин by
QA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилинQA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилин
QA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилинQAFest
979 views44 slides
QA Fest 2019. Анна Чернышова. Self-healing test automation 2.0. The Future by
QA Fest 2019. Анна Чернышова. Self-healing test automation 2.0. The FutureQA Fest 2019. Анна Чернышова. Self-healing test automation 2.0. The Future
QA Fest 2019. Анна Чернышова. Self-healing test automation 2.0. The FutureQAFest
931 views44 slides
QA Fest 2019. Doug Sillars. It's just too Slow: Testing Mobile application pe... by
QA Fest 2019. Doug Sillars. It's just too Slow: Testing Mobile application pe...QA Fest 2019. Doug Sillars. It's just too Slow: Testing Mobile application pe...
QA Fest 2019. Doug Sillars. It's just too Slow: Testing Mobile application pe...QAFest
322 views131 slides
QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и... by
QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...
QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...QAFest
336 views92 slides
QA Fest 2019. Никита Галкин. Как зарабатывать больше by
QA Fest 2019. Никита Галкин. Как зарабатывать большеQA Fest 2019. Никита Галкин. Как зарабатывать больше
QA Fest 2019. Никита Галкин. Как зарабатывать большеQAFest
389 views40 slides
QA Fest 2019. Сергей Пирогов. Why everything is spoiled by
QA Fest 2019. Сергей Пирогов. Why everything is spoiledQA Fest 2019. Сергей Пирогов. Why everything is spoiled
QA Fest 2019. Сергей Пирогов. Why everything is spoiledQAFest
342 views33 slides

More from QAFest(20)

QA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилин by QAFest
QA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилинQA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилин
QA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилин
QAFest979 views
QA Fest 2019. Анна Чернышова. Self-healing test automation 2.0. The Future by QAFest
QA Fest 2019. Анна Чернышова. Self-healing test automation 2.0. The FutureQA Fest 2019. Анна Чернышова. Self-healing test automation 2.0. The Future
QA Fest 2019. Анна Чернышова. Self-healing test automation 2.0. The Future
QAFest931 views
QA Fest 2019. Doug Sillars. It's just too Slow: Testing Mobile application pe... by QAFest
QA Fest 2019. Doug Sillars. It's just too Slow: Testing Mobile application pe...QA Fest 2019. Doug Sillars. It's just too Slow: Testing Mobile application pe...
QA Fest 2019. Doug Sillars. It's just too Slow: Testing Mobile application pe...
QAFest322 views
QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и... by QAFest
QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...
QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...
QAFest336 views
QA Fest 2019. Никита Галкин. Как зарабатывать больше by QAFest
QA Fest 2019. Никита Галкин. Как зарабатывать большеQA Fest 2019. Никита Галкин. Как зарабатывать больше
QA Fest 2019. Никита Галкин. Как зарабатывать больше
QAFest389 views
QA Fest 2019. Сергей Пирогов. Why everything is spoiled by QAFest
QA Fest 2019. Сергей Пирогов. Why everything is spoiledQA Fest 2019. Сергей Пирогов. Why everything is spoiled
QA Fest 2019. Сергей Пирогов. Why everything is spoiled
QAFest342 views
QA Fest 2019. Сергей Новик. Между мотивацией и выгоранием by QAFest
QA Fest 2019. Сергей Новик. Между мотивацией и выгораниемQA Fest 2019. Сергей Новик. Между мотивацией и выгоранием
QA Fest 2019. Сергей Новик. Между мотивацией и выгоранием
QAFest249 views
QA Fest 2019. Владимир Никонов. Код Шредингера или зачем и как мы тестируем н... by QAFest
QA Fest 2019. Владимир Никонов. Код Шредингера или зачем и как мы тестируем н...QA Fest 2019. Владимир Никонов. Код Шредингера или зачем и как мы тестируем н...
QA Fest 2019. Владимир Никонов. Код Шредингера или зачем и как мы тестируем н...
QAFest338 views
QA Fest 2019. Владимир Трандафилов. GUI automation of WEB application with SV... by QAFest
QA Fest 2019. Владимир Трандафилов. GUI automation of WEB application with SV...QA Fest 2019. Владимир Трандафилов. GUI automation of WEB application with SV...
QA Fest 2019. Владимир Трандафилов. GUI automation of WEB application with SV...
QAFest227 views
QA Fest 2019. Иван Крутов. Bulletproof Selenium Cluster by QAFest
QA Fest 2019. Иван Крутов. Bulletproof Selenium ClusterQA Fest 2019. Иван Крутов. Bulletproof Selenium Cluster
QA Fest 2019. Иван Крутов. Bulletproof Selenium Cluster
QAFest282 views
QA Fest 2019. Николай Мижигурский. Миссия /*не*/выполнима: гуманитарий собесе... by QAFest
QA Fest 2019. Николай Мижигурский. Миссия /*не*/выполнима: гуманитарий собесе...QA Fest 2019. Николай Мижигурский. Миссия /*не*/выполнима: гуманитарий собесе...
QA Fest 2019. Николай Мижигурский. Миссия /*не*/выполнима: гуманитарий собесе...
QAFest251 views
QA Fest 2019. Володимир Стиран. Чим раніше – тим вигідніше, але ніколи не піз... by QAFest
QA Fest 2019. Володимир Стиран. Чим раніше – тим вигідніше, але ніколи не піз...QA Fest 2019. Володимир Стиран. Чим раніше – тим вигідніше, але ніколи не піз...
QA Fest 2019. Володимир Стиран. Чим раніше – тим вигідніше, але ніколи не піз...
QAFest301 views
QA Fest 2019. Дмитрий Прокопук. Mocks and network tricks in UI automation by QAFest
QA Fest 2019. Дмитрий Прокопук. Mocks and network tricks in UI automationQA Fest 2019. Дмитрий Прокопук. Mocks and network tricks in UI automation
QA Fest 2019. Дмитрий Прокопук. Mocks and network tricks in UI automation
QAFest225 views
QA Fest 2019. Екатерина Дядечко. Тестирование медицинского софта — вызовы и в... by QAFest
QA Fest 2019. Екатерина Дядечко. Тестирование медицинского софта — вызовы и в...QA Fest 2019. Екатерина Дядечко. Тестирование медицинского софта — вызовы и в...
QA Fest 2019. Екатерина Дядечко. Тестирование медицинского софта — вызовы и в...
QAFest243 views
QA Fest 2019. Катерина Черникова. Tune your P’s: the pop-art of keeping testa... by QAFest
QA Fest 2019. Катерина Черникова. Tune your P’s: the pop-art of keeping testa...QA Fest 2019. Катерина Черникова. Tune your P’s: the pop-art of keeping testa...
QA Fest 2019. Катерина Черникова. Tune your P’s: the pop-art of keeping testa...
QAFest376 views
QA Fest 2019. Алиса Бойко. Какнезапутаться в коммуникативных сетях IT by QAFest
QA Fest 2019. Алиса Бойко. Какнезапутаться в коммуникативных сетях ITQA Fest 2019. Алиса Бойко. Какнезапутаться в коммуникативных сетях IT
QA Fest 2019. Алиса Бойко. Какнезапутаться в коммуникативных сетях IT
QAFest209 views
QA Fest 2019. Святослав Логин. Как найти уязвимости в мобильном приложении by QAFest
QA Fest 2019. Святослав Логин. Как найти уязвимости в мобильном приложенииQA Fest 2019. Святослав Логин. Как найти уязвимости в мобильном приложении
QA Fest 2019. Святослав Логин. Как найти уязвимости в мобильном приложении
QAFest607 views
QA Fest 2019. Катерина Шепелєва та Інна Оснач. Що українцям потрібно знати пр... by QAFest
QA Fest 2019. Катерина Шепелєва та Інна Оснач. Що українцям потрібно знати пр...QA Fest 2019. Катерина Шепелєва та Інна Оснач. Що українцям потрібно знати пр...
QA Fest 2019. Катерина Шепелєва та Інна Оснач. Що українцям потрібно знати пр...
QAFest321 views
QA Fest 2019. Антон Серпутько. Нагрузочное тестирование распределенных асинхр... by QAFest
QA Fest 2019. Антон Серпутько. Нагрузочное тестирование распределенных асинхр...QA Fest 2019. Антон Серпутько. Нагрузочное тестирование распределенных асинхр...
QA Fest 2019. Антон Серпутько. Нагрузочное тестирование распределенных асинхр...
QAFest296 views
QA Fest 2019. Петр Тарасенко. QA Hackathon - The Cookbook 22 by QAFest
QA Fest 2019. Петр Тарасенко. QA Hackathon - The Cookbook 22QA Fest 2019. Петр Тарасенко. QA Hackathon - The Cookbook 22
QA Fest 2019. Петр Тарасенко. QA Hackathon - The Cookbook 22
QAFest164 views

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

  • 1. Не смешите мой coverage! Цыганов Иван Positive Technologies
  • 2. Обо мне ✤ Люблю OpenSource ✤ Не умею frontend ✤ Добровольно пишу тесты
  • 3. ✤ Более 15 лет практического опыта на рынке ИБ ✤ Более 700 сотрудников в 9 странах ✤ Каждый год находим более 200 уязвимостей нулевого дня ✤ Проводим более 200 аудитов безопасности в крупнейших компаниях мира ежегодно
  • 4. MaxPatrol ✤ Pentest. Тестирование на проникновение. ✤ Audit. Системные проверки. ✤ Compliance. Соответствие стандартам. ✤ Одна из крупнейших баз знаний в мире Система контроля защищенности и соответствия стандартам.
  • 5. MaxPatrol ✤ Pentest. Тестирование на проникновение. ✤ Compliance. Соответствие стандартам. ✤ Одна из крупнейших баз знаний в мире Система контроля защищенности и соответствия стандартам. ✤ Audit. Системные проверки.
  • 6. > 50 000 строк кода
  • 7. Зачем мы тестируем? ✤ Уверенность, что написанный код работает ✤ Ревью кода становится проще ✤ Гарантия, что ничего не сломалось при изменениях
  • 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. Плохой тест 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
  • 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. Неожиданные данные >>> 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. Наличие тестов - не показатель качества
  • 14. ✤ Проверить покрытие кода тестами ✤ ... ✤ ... Как сделать тесты лучше?
  • 15. coverage.py ✤ Позволяет проверить покрытие кода тестами ✤ Есть плагин для pytest
  • 16. coverage.py ✤ Позволяет проверить покрытие кода тестами ✤ Есть плагин для pytest ✤ В основном работает
  • 17. coverage.ini [report]
 show_missing = True
 precision = 2 py.test --cov-config=coverage.ini --cov=target test.py
  • 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. 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. 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. 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. 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. >>> 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. >>> 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
  • 26. coverage.ini [report]
 show_missing = True
 precision = 2
 [run]
 branch = True py.test --cov-config=coverage.ini --cov=target test.py
  • 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. 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
  • 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 95.00% 11 ->13
  • 30. 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%
  • 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 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
  • 33. 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
  • 34. 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%
  • 36. Как считать coverage? Все строки Реально выполненные строки- Непокрытые строки=
  • 38. coverage.parser.PythonParser ✤ Обходит все токены и отмечает «интересные» факты ✤ Компилирует код. Обходит code-object и сохраняет номера строк
  • 39. Обход токенов ✤ Запоминает определения классов ✤ «Сворачивает» многострочные выражения ✤ Исключает комментарии
  • 40. Обход байткода ✤ Рекурсивно обходит все code object в code.co_const ✤ Полностью повторяет метод dis.findlinestarts ✤ Анализирует code_obj.co_lnotab ✤ Генерирует пару (номер байткода, номер строки)
  • 41. Как считать coverage --branch? Все переходы Реально выполненные переходы- Непокрытые переходы=
  • 43. coverage.parser.AstArcAnalyzer ✤ Обходит все AST-дерево с корневой ноды ✤ В зависимости от типа ноды генерирует варианты перехода между строками
  • 44. Обработка ноды class While(stmt): _fields = ( 'test', 'body', 'orelse', ) while i<10: print(i) i += 1
  • 45. Обработка ноды class While(stmt): _fields = ( 'test', 'body', 'orelse', ) while i<10: print(i) i += 1 else: print('Done!')
  • 46. Выполненные строки 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.
  • 47. PyTracer «call» event ✤ Сохраняем данные предыдущего контекста ✤ Начинаем собирать данные нового контекста ✤ Учитываем особенности генераторов
  • 48. PyTracer «line» event ✤ Запоминаем выполняемую строку ✤ Запоминаем переход между строками
  • 49. PyTracer «return» event ✤ Отмечаем выход из контекста ✤ Помним о том, что yield это тоже выход из контекста
  • 50. Отчет ✤ Что выполнялось ✤ Что должно было выполниться
  • 51. Отчет ✤ Что выполнялось ✤ Что должно было выполниться ✤ Ругаемся
  • 52. Зачем такие сложности? for i in some_list: if i == 'Hello': print(i + ' World!') elif i == 'Skip': continue else: break else: print(r'¯_(ツ)_/¯')
  • 55. Что может пойти не так? 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) ] }
  • 57. ✤ Проверить покрытие кода тестами ✤ ... ✤ ... Как сделать тесты лучше?
  • 58. ✤ Проверить покрытие кода тестами ✤ Попробовать мутационное тестирование ✤ ... Как сделать тесты лучше?
  • 59. Мутационное тестирование ✤ Берем тестируемый код ✤ Мутируем ✤ Тестируем мутантов нашими тестами ✤ Тест не упал -> плохой тест
  • 60. Мутационное тестирование ✤ Берем тестируемый код ✤ Мутируем ✤ Тестируем мутантов нашими тестами ✤ Если тест не упал -> это плохой тест✤ Тест не упал -> плохой тест
  • 61. Идея def mul(a, b): return a * b def test_mul(): assert mul(2, 2) == 4
  • 62. Идея def mul(a, b): return a * b def test_mul(): assert mul(2, 2) == 4 def mul(a, b): return a ** b
  • 63. Идея 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
  • 64. Идея 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
  • 65. mutpy ✤ Анализирует исходный код ✤ Модифицирует некоторые AST-ноды ✤ Запускает тесты ✤ Проверяет результат запуска тестов
  • 67. Мутации 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
  • 68. Мутации 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)
  • 69. Мутации 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
  • 70. [*] 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]
  • 71. [*] 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%)
  • 72. … --------------------------------------------------------------------- 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%)
  • 73. [*] 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]
  • 74. Идея имеет право на жизнь и работает! Но требует много ресурсов.
  • 75. 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
  • 76. 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
  • 77. 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%
  • 78. ✤ Проверить покрытие кода тестами ✤ Попробовать мутационное тестирование ✤ ... Как сделать тесты лучше?
  • 79. ✤ Проверить покрытие кода тестами ✤ Попробовать мутационное тестирование ✤ Генерировать входные данные Как сделать тесты лучше?
  • 80. Hypothesis ✤ Возможность генерации данных ✤ Позволяет проверить поведение кода
  • 81. Hypothesis ✤ Возможность генерации данных ✤ Позволяет проверить поведение кода ✤ Подходит не для всех задач :(
  • 82. Hypothesis. Генерация данных >>> from hypothesis import strategies as st >>> st.integers().example() 132598732931307445807900680032693714775
  • 83. Hypothesis. Генерация данных >>> from hypothesis import strategies as st >>> st.integers().example() 132598732931307445807900680032693714775 >>> st.integers(min_value=0, max_value=100).example() 26
  • 84. 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() '傖'
  • 86. 
 keys = (
 st.integers()
 |
 st.floats()
 |
 st.text(alphabet=string.ascii_letters)
 ) 
 simple_values = (
 keys
 |
 st.booleans()
 |
 st.none()
 |
 st.binary()
 |
 st.text()
 )
  • 87. 
 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
 }
  • 88. 
 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) )
 )
 )
  • 89. 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
 }
  • 90. ?
  • 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
  • 92. 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))
  • 93. 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
  • 94. 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])
  • 95. 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'
  • 96. Каким же должен был быть код?!
  • 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
  • 98. 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]
  • 99. 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%
  • 100. 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%
  • 101. 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%)
  • 104. >>> sum(list(normalize_digits([False, True, 2, 3, 4]))) 10 >>> isinstance(False, int) True >>> isinstance(True, int) True
  • 106. 100% coverage расслабляет команду Библиотеки несовершенны
  • 107. 100% coverage расслабляет команду Библиотеки несовершенны Наличие тестов - не показатель качества кода