SlideShare uma empresa Scribd logo
1 de 108
Не смешите мой 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
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%
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%
Как считать 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)
]
}
✤ Проверить покрытие кода тестами
✤ ...
✤ ...
Как сделать тесты лучше?
✤ Проверить покрытие кода тестами
✤ Попробовать мутационное тестирование
✤ ...
Как сделать тесты лучше?
Мутационное тестирование
✤ Берем тестируемый код
✤ Мутируем
✤ Тестируем мутантов нашими тестами
✤ Тест не упал -> плохой тест
Мутационное тестирование
✤ Берем тестируемый код
✤ Мутируем
✤ Тестируем мутантов нашими тестами
✤ Если тест не упал -> это плохой тест✤ Тест не упал -> плохой тест
Идея
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%)
>>> 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

Mais conteúdo relacionado

Mais procurados

SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...
SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...
SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...DevDay
 
Python и его тормоза
Python и его тормозаPython и его тормоза
Python и его тормозаAlexander Shigin
 
Лекция 7. Исключения и менеджеры контекста.
Лекция 7. Исключения и менеджеры контекста.Лекция 7. Исключения и менеджеры контекста.
Лекция 7. Исключения и менеджеры контекста.Roman Brovko
 
Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?PyNSK
 
Тестирование программных фильтров безопасности
Тестирование программных фильтров безопасностиТестирование программных фильтров безопасности
Тестирование программных фильтров безопасностиZestranec
 
Лекция 2. Всё, что вы хотели знать о функциях в Python.
Лекция 2. Всё, что вы хотели знать о функциях в Python.Лекция 2. Всё, что вы хотели знать о функциях в Python.
Лекция 2. Всё, что вы хотели знать о функциях в Python.Roman Brovko
 
Лекция 11. Тестирование.
Лекция 11. Тестирование.Лекция 11. Тестирование.
Лекция 11. Тестирование.Roman Brovko
 
Making of external DSL for Django ORM - Павел Петлинский, Rambler&Co
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&Coit-people
 
Лекция 1. Начало.
Лекция 1. Начало.Лекция 1. Начало.
Лекция 1. Начало.Roman Brovko
 
Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.Roman Brovko
 
kranonitS20 Сергей Бурма. Django - легко, быстро, эффективно
kranonitS20 Сергей Бурма. Django - легко, быстро, эффективноkranonitS20 Сергей Бурма. Django - легко, быстро, эффективно
kranonitS20 Сергей Бурма. Django - легко, быстро, эффективноKrivoy Rog IT Community
 
Web осень 2013 лекция 6
Web осень 2013 лекция 6Web осень 2013 лекция 6
Web осень 2013 лекция 6Technopark
 
Одномерные массивы целых чисел
Одномерные массивы целых чиселОдномерные массивы целых чисел
Одномерные массивы целых чиселAndrey Dolinin
 
Красота и изящность стандартной библиотеки Python
Красота и изящность стандартной библиотеки PythonКрасота и изящность стандартной библиотеки Python
Красота и изящность стандартной библиотеки PythonPython Meetup
 
Исключительно простая теория AppSec .NET
Исключительно простая теория AppSec .NETИсключительно простая теория AppSec .NET
Исключительно простая теория AppSec .NETVladimir Kochetkov
 
Григорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптерГригорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптерSergey Platonov
 
Лекция 12. Быстрее, Python, ещё быстрее.
Лекция 12. Быстрее, Python, ещё быстрее.Лекция 12. Быстрее, Python, ещё быстрее.
Лекция 12. Быстрее, Python, ещё быстрее.Roman Brovko
 
Лекция 3. Декораторы и модуль functools.
Лекция 3. Декораторы и модуль functools.Лекция 3. Декораторы и модуль functools.
Лекция 3. Декораторы и модуль functools.Roman Brovko
 

Mais procurados (20)

SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...
SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...
SQL-ник DevDay. Каменский. Расширенный SQL в MySQL и PostgreSQL. Сравнение во...
 
Python и его тормоза
Python и его тормозаPython и его тормоза
Python и его тормоза
 
XML Magic
XML MagicXML Magic
XML Magic
 
Лекция 7. Исключения и менеджеры контекста.
Лекция 7. Исключения и менеджеры контекста.Лекция 7. Исключения и менеджеры контекста.
Лекция 7. Исключения и менеджеры контекста.
 
Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?
 
Тестирование программных фильтров безопасности
Тестирование программных фильтров безопасностиТестирование программных фильтров безопасности
Тестирование программных фильтров безопасности
 
Лекция 2. Всё, что вы хотели знать о функциях в Python.
Лекция 2. Всё, что вы хотели знать о функциях в Python.Лекция 2. Всё, что вы хотели знать о функциях в Python.
Лекция 2. Всё, что вы хотели знать о функциях в Python.
 
Лекция 11. Тестирование.
Лекция 11. Тестирование.Лекция 11. Тестирование.
Лекция 11. Тестирование.
 
Making of external DSL for Django ORM - Павел Петлинский, Rambler&Co
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
 
Лекция 1. Начало.
Лекция 1. Начало.Лекция 1. Начало.
Лекция 1. Начало.
 
Tricky Java Generics
Tricky Java GenericsTricky Java Generics
Tricky Java Generics
 
Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.
 
kranonitS20 Сергей Бурма. Django - легко, быстро, эффективно
kranonitS20 Сергей Бурма. Django - легко, быстро, эффективноkranonitS20 Сергей Бурма. Django - легко, быстро, эффективно
kranonitS20 Сергей Бурма. Django - легко, быстро, эффективно
 
Web осень 2013 лекция 6
Web осень 2013 лекция 6Web осень 2013 лекция 6
Web осень 2013 лекция 6
 
Одномерные массивы целых чисел
Одномерные массивы целых чиселОдномерные массивы целых чисел
Одномерные массивы целых чисел
 
Красота и изящность стандартной библиотеки Python
Красота и изящность стандартной библиотеки PythonКрасота и изящность стандартной библиотеки Python
Красота и изящность стандартной библиотеки Python
 
Исключительно простая теория AppSec .NET
Исключительно простая теория AppSec .NETИсключительно простая теория AppSec .NET
Исключительно простая теория AppSec .NET
 
Григорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптерГригорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптер
 
Лекция 12. Быстрее, Python, ещё быстрее.
Лекция 12. Быстрее, Python, ещё быстрее.Лекция 12. Быстрее, Python, ещё быстрее.
Лекция 12. Быстрее, Python, ещё быстрее.
 
Лекция 3. Декораторы и модуль functools.
Лекция 3. Декораторы и модуль functools.Лекция 3. Декораторы и модуль functools.
Лекция 3. Декораторы и модуль functools.
 

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

«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...
«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...
«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...Mail.ru Group
 
Формальные методы защиты приложений
Формальные методы защиты приложенийФормальные методы защиты приложений
Формальные методы защиты приложенийPositive Hack Days
 
DSLs in Lisp and Clojure
DSLs in Lisp and ClojureDSLs in Lisp and Clojure
DSLs in Lisp and ClojureVasil Remeniuk
 
Python
PythonPython
Pythonpelid
 
Улучшение качества открытого программного обеспечения с помощью инструментов ...
Улучшение качества открытого программного обеспечения с помощью инструментов ...Улучшение качества открытого программного обеспечения с помощью инструментов ...
Улучшение качества открытого программного обеспечения с помощью инструментов ...Andrey Karpov
 
20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonov20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonovComputer Science Club
 
Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?Ivan Tsyganov
 
статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кодаAndrey Karpov
 
Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода Pavel Tsukanov
 
8 встреча — Язык программирования Python (В. Ананьев)
8 встреча — Язык программирования Python (В. Ананьев)8 встреча — Язык программирования Python (В. Ананьев)
8 встреча — Язык программирования Python (В. Ананьев)Smolensk Computer Science Club
 

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

«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...
«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...
«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Ramb...
 
Формальные методы защиты приложений
Формальные методы защиты приложенийФормальные методы защиты приложений
Формальные методы защиты приложений
 
DSLs in Lisp and Clojure
DSLs in Lisp and ClojureDSLs in Lisp and Clojure
DSLs in Lisp and Clojure
 
Programming c++ (begin-if-else)
Programming c++ (begin-if-else)Programming c++ (begin-if-else)
Programming c++ (begin-if-else)
 
Algo 00
Algo 00Algo 00
Algo 00
 
Python
PythonPython
Python
 
About Python
About PythonAbout Python
About Python
 
Rgsu04
Rgsu04Rgsu04
Rgsu04
 
Rgsu04
Rgsu04Rgsu04
Rgsu04
 
Улучшение качества открытого программного обеспечения с помощью инструментов ...
Улучшение качества открытого программного обеспечения с помощью инструментов ...Улучшение качества открытого программного обеспечения с помощью инструментов ...
Улучшение качества открытого программного обеспечения с помощью инструментов ...
 
20130429 dynamic c_c++_program_analysis-alexey_samsonov
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% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?
 
Python
PythonPython
Python
 
Fiche Révision POO
Fiche Révision POOFiche Révision POO
Fiche Révision POO
 
статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кода
 
Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода
 
8 встреча — Язык программирования Python (В. Ананьев)
8 встреча — Язык программирования Python (В. Ананьев)8 встреча — Язык программирования Python (В. Ананьев)
8 встреча — Язык программирования Python (В. Ананьев)
 
Charming python sc2-8
Charming python sc2-8Charming python sc2-8
Charming python sc2-8
 
Server optimization
Server optimizationServer optimization
Server optimization
 
паскаль
паскальпаскаль
паскаль
 

Mais de QAFest

QA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилин
QA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилинQA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилин
QA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилинQAFest
 
QA Fest 2019. Анна Чернышова. Self-healing test automation 2.0. The Future
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
 
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...
QA Fest 2019. Doug Sillars. It's just too Slow: Testing Mobile application pe...QAFest
 
QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...
QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...
QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...QAFest
 
QA Fest 2019. Никита Галкин. Как зарабатывать больше
QA Fest 2019. Никита Галкин. Как зарабатывать большеQA Fest 2019. Никита Галкин. Как зарабатывать больше
QA Fest 2019. Никита Галкин. Как зарабатывать большеQAFest
 
QA Fest 2019. Сергей Пирогов. Why everything is spoiled
QA Fest 2019. Сергей Пирогов. Why everything is spoiledQA Fest 2019. Сергей Пирогов. Why everything is spoiled
QA Fest 2019. Сергей Пирогов. Why everything is spoiledQAFest
 
QA Fest 2019. Сергей Новик. Между мотивацией и выгоранием
QA Fest 2019. Сергей Новик. Между мотивацией и выгораниемQA Fest 2019. Сергей Новик. Между мотивацией и выгоранием
QA Fest 2019. Сергей Новик. Между мотивацией и выгораниемQAFest
 
QA Fest 2019. Владимир Никонов. Код Шредингера или зачем и как мы тестируем н...
QA Fest 2019. Владимир Никонов. Код Шредингера или зачем и как мы тестируем н...QA Fest 2019. Владимир Никонов. Код Шредингера или зачем и как мы тестируем н...
QA Fest 2019. Владимир Никонов. Код Шредингера или зачем и как мы тестируем н...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...
QA Fest 2019. Владимир Трандафилов. GUI automation of WEB application with SV...QAFest
 
QA Fest 2019. Иван Крутов. Bulletproof Selenium Cluster
QA Fest 2019. Иван Крутов. Bulletproof Selenium ClusterQA Fest 2019. Иван Крутов. Bulletproof Selenium Cluster
QA Fest 2019. Иван Крутов. Bulletproof Selenium ClusterQAFest
 
QA Fest 2019. Николай Мижигурский. Миссия /*не*/выполнима: гуманитарий собесе...
QA Fest 2019. Николай Мижигурский. Миссия /*не*/выполнима: гуманитарий собесе...QA Fest 2019. Николай Мижигурский. Миссия /*не*/выполнима: гуманитарий собесе...
QA Fest 2019. Николай Мижигурский. Миссия /*не*/выполнима: гуманитарий собесе...QAFest
 
QA Fest 2019. Володимир Стиран. Чим раніше – тим вигідніше, але ніколи не піз...
QA Fest 2019. Володимир Стиран. Чим раніше – тим вигідніше, але ніколи не піз...QA Fest 2019. Володимир Стиран. Чим раніше – тим вигідніше, але ніколи не піз...
QA Fest 2019. Володимир Стиран. Чим раніше – тим вигідніше, але ніколи не піз...QAFest
 
QA Fest 2019. Дмитрий Прокопук. Mocks and network tricks in UI automation
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 automationQAFest
 
QA Fest 2019. Екатерина Дядечко. Тестирование медицинского софта — вызовы и в...
QA Fest 2019. Екатерина Дядечко. Тестирование медицинского софта — вызовы и в...QA Fest 2019. Екатерина Дядечко. Тестирование медицинского софта — вызовы и в...
QA Fest 2019. Екатерина Дядечко. Тестирование медицинского софта — вызовы и в...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...
QA Fest 2019. Катерина Черникова. Tune your P’s: the pop-art of keeping testa...QAFest
 
QA Fest 2019. Алиса Бойко. Какнезапутаться в коммуникативных сетях IT
QA Fest 2019. Алиса Бойко. Какнезапутаться в коммуникативных сетях ITQA Fest 2019. Алиса Бойко. Какнезапутаться в коммуникативных сетях IT
QA Fest 2019. Алиса Бойко. Какнезапутаться в коммуникативных сетях ITQAFest
 
QA Fest 2019. Святослав Логин. Как найти уязвимости в мобильном приложении
QA Fest 2019. Святослав Логин. Как найти уязвимости в мобильном приложенииQA Fest 2019. Святослав Логин. Как найти уязвимости в мобильном приложении
QA Fest 2019. Святослав Логин. Как найти уязвимости в мобильном приложенииQAFest
 
QA Fest 2019. Катерина Шепелєва та Інна Оснач. Що українцям потрібно знати пр...
QA Fest 2019. Катерина Шепелєва та Інна Оснач. Що українцям потрібно знати пр...QA Fest 2019. Катерина Шепелєва та Інна Оснач. Що українцям потрібно знати пр...
QA Fest 2019. Катерина Шепелєва та Інна Оснач. Що українцям потрібно знати пр...QAFest
 
QA Fest 2019. Антон Серпутько. Нагрузочное тестирование распределенных асинхр...
QA Fest 2019. Антон Серпутько. Нагрузочное тестирование распределенных асинхр...QA Fest 2019. Антон Серпутько. Нагрузочное тестирование распределенных асинхр...
QA Fest 2019. Антон Серпутько. Нагрузочное тестирование распределенных асинхр...QAFest
 
QA Fest 2019. Петр Тарасенко. QA Hackathon - The Cookbook 22
QA Fest 2019. Петр Тарасенко. QA Hackathon - The Cookbook 22QA Fest 2019. Петр Тарасенко. QA Hackathon - The Cookbook 22
QA Fest 2019. Петр Тарасенко. QA Hackathon - The Cookbook 22QAFest
 

Mais de QAFest (20)

QA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилин
QA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилинQA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилин
QA Fest 2019. Сергій Короленко. Топ веб вразливостей за 40 хвилин
 
QA Fest 2019. Анна Чернышова. Self-healing test automation 2.0. The Future
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
 
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...
QA Fest 2019. Doug Sillars. It's just too Slow: Testing Mobile application pe...
 
QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...
QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...
QA Fest 2019. Катерина Спринсян. Параллельное покрытие автотестами и другие и...
 
QA Fest 2019. Никита Галкин. Как зарабатывать больше
QA Fest 2019. Никита Галкин. Как зарабатывать большеQA Fest 2019. Никита Галкин. Как зарабатывать больше
QA Fest 2019. Никита Галкин. Как зарабатывать больше
 
QA Fest 2019. Сергей Пирогов. Why everything is spoiled
QA Fest 2019. Сергей Пирогов. Why everything is spoiledQA Fest 2019. Сергей Пирогов. Why everything is spoiled
QA Fest 2019. Сергей Пирогов. Why everything is spoiled
 
QA Fest 2019. Сергей Новик. Между мотивацией и выгоранием
QA Fest 2019. Сергей Новик. Между мотивацией и выгораниемQA Fest 2019. Сергей Новик. Между мотивацией и выгоранием
QA Fest 2019. Сергей Новик. Между мотивацией и выгоранием
 
QA Fest 2019. Владимир Никонов. Код Шредингера или зачем и как мы тестируем н...
QA Fest 2019. Владимир Никонов. Код Шредингера или зачем и как мы тестируем н...QA Fest 2019. Владимир Никонов. Код Шредингера или зачем и как мы тестируем н...
QA Fest 2019. Владимир Никонов. Код Шредингера или зачем и как мы тестируем н...
 
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...
QA Fest 2019. Владимир Трандафилов. GUI automation of WEB application with SV...
 
QA Fest 2019. Иван Крутов. Bulletproof Selenium Cluster
QA Fest 2019. Иван Крутов. Bulletproof Selenium ClusterQA Fest 2019. Иван Крутов. Bulletproof Selenium Cluster
QA Fest 2019. Иван Крутов. Bulletproof Selenium Cluster
 
QA Fest 2019. Николай Мижигурский. Миссия /*не*/выполнима: гуманитарий собесе...
QA Fest 2019. Николай Мижигурский. Миссия /*не*/выполнима: гуманитарий собесе...QA Fest 2019. Николай Мижигурский. Миссия /*не*/выполнима: гуманитарий собесе...
QA Fest 2019. Николай Мижигурский. Миссия /*не*/выполнима: гуманитарий собесе...
 
QA Fest 2019. Володимир Стиран. Чим раніше – тим вигідніше, але ніколи не піз...
QA Fest 2019. Володимир Стиран. Чим раніше – тим вигідніше, але ніколи не піз...QA Fest 2019. Володимир Стиран. Чим раніше – тим вигідніше, але ніколи не піз...
QA Fest 2019. Володимир Стиран. Чим раніше – тим вигідніше, але ніколи не піз...
 
QA Fest 2019. Дмитрий Прокопук. Mocks and network tricks in UI automation
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
 
QA Fest 2019. Екатерина Дядечко. Тестирование медицинского софта — вызовы и в...
QA Fest 2019. Екатерина Дядечко. Тестирование медицинского софта — вызовы и в...QA Fest 2019. Екатерина Дядечко. Тестирование медицинского софта — вызовы и в...
QA Fest 2019. Екатерина Дядечко. Тестирование медицинского софта — вызовы и в...
 
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...
QA Fest 2019. Катерина Черникова. Tune your P’s: the pop-art of keeping testa...
 
QA Fest 2019. Алиса Бойко. Какнезапутаться в коммуникативных сетях IT
QA Fest 2019. Алиса Бойко. Какнезапутаться в коммуникативных сетях ITQA Fest 2019. Алиса Бойко. Какнезапутаться в коммуникативных сетях IT
QA Fest 2019. Алиса Бойко. Какнезапутаться в коммуникативных сетях IT
 
QA Fest 2019. Святослав Логин. Как найти уязвимости в мобильном приложении
QA Fest 2019. Святослав Логин. Как найти уязвимости в мобильном приложенииQA Fest 2019. Святослав Логин. Как найти уязвимости в мобильном приложении
QA Fest 2019. Святослав Логин. Как найти уязвимости в мобильном приложении
 
QA Fest 2019. Катерина Шепелєва та Інна Оснач. Що українцям потрібно знати пр...
QA Fest 2019. Катерина Шепелєва та Інна Оснач. Що українцям потрібно знати пр...QA Fest 2019. Катерина Шепелєва та Інна Оснач. Що українцям потрібно знати пр...
QA Fest 2019. Катерина Шепелєва та Інна Оснач. Що українцям потрібно знати пр...
 
QA Fest 2019. Антон Серпутько. Нагрузочное тестирование распределенных асинхр...
QA Fest 2019. Антон Серпутько. Нагрузочное тестирование распределенных асинхр...QA Fest 2019. Антон Серпутько. Нагрузочное тестирование распределенных асинхр...
QA Fest 2019. Антон Серпутько. Нагрузочное тестирование распределенных асинхр...
 
QA Fest 2019. Петр Тарасенко. QA Hackathon - The Cookbook 22
QA Fest 2019. Петр Тарасенко. QA Hackathon - The Cookbook 22QA Fest 2019. Петр Тарасенко. QA Hackathon - The Cookbook 22
QA Fest 2019. Петр Тарасенко. QA Hackathon - The Cookbook 22
 

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
  • 25.
  • 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%
  • 31.
  • 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%
  • 35.
  • 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) ] }
  • 56.
  • 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%)
  • 102.
  • 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 расслабляет команду Библиотеки несовершенны Наличие тестов - не показатель качества кода