def main():
x = int(input('Введіть число: ')) # '2'
print(f'Число {x} в квадраті дорівнює {square(x)}')
def square(n):
return pow(n, 2)
main()6 Модульне тестування
До цього моменту ми писали код, запускаючи програми, вводили вхідні дані, отримували результати і якщо щось йшло не по плану - виправляли це. Набагато краще виробити звичку тестувати власний код, використовуючи інший код. І саме на цьому ми зосередимось і цій частині.
Повернемося до файлу calculator.py, який ми використовували для обчислення математичних виразів:
Число 2 в квадраті дорівнює 4
Все наче працює, але я не тестував цей код, і не обов’язково, що він повністю працює. Ми повинні протестувати деякі репрезентативні вхідні дані. Але перш ніж ми це зробимо, давайте візьмемо собі за звичку переконуватися, що main() не завжди викликається (див. 5.1). Тому слід переписати код:
def main():
x = int(input('Введіть число: ')) # '2'
print(f'Число {x} в квадраті дорівнює {square(x)}')
def square(n):
return pow(n, 2)
if __name__ == '__main__':
main()Напишемо код, який буде тестувати програму calculator.py. Для цього створимо новий файл test_calculator.py:
Terminal
code test_calculator.pyРаніше для тестування програм, кожного разу я вводив дані вручну. Це стає незручно. Було б добре, якби я міг автоматично тестувати свою програму знову і знову, за допомогою якогось автоматизованого процесу, що запускає мій власний код. Тож давайте зробимо це.
Імпортуємо функцію square з calculator.py та створимо функцію test_square, яка буде тестувати функцію square.
from calculator import square
def main():
test_square()
def test_square():
if square(2) != 4:
print('2 у квадраті не дорівнює 4')
if square(3) != 9:
print('3 у квадраті не дорівнює 9')
else:
print('Все працює')
if __name__ == '__main__':
main()Тепер запустимо тестування:
Terminal
python test_calculator.pyВсе працює
Тут мені пощастило, і все працює. Але якщо екстраполювати цей простий приклад не на два тести, а на значно більше, код стане набагато складнішим, ніж сама функція.
6.1 Тестування з використанням assert
Існують інші варіанти тестування, які допоможуть нам зробити код більш читабельним. Наприклад, використання функції assert - ключовим словом, яке перевіряє, чи є вираз істинним. Якщо вираз істинний, то нічого не відбувається. Але якщо вираз є хибним, то ви отримаєте помилку. Давайте перепишемо функцію test_square з використанням assert:
from calculator import square
def main():
test_square()
def test_square():
assert square(2) == 4
assert square(3) == 9
if __name__ == '__main__':
main()AssertionError - це помилка, яка виникає, коли вираз є хибним. Тож ми можемо використати try і except для того, щоб перехопити цю помилку і вивести повідомлення про те, що тест не пройдено:
from calculator import square
def main():
test_square()
def test_square():
try:
assert square(2) == 4
except AssertionError:
print('2 в квадраті не дорівнює 4')
assert square(3) == 9
except AssertionError:
print('3 в квадраті не дорівнює 9')
if __name__ == '__main__':
main()Але якщо продовжувати такі тести, то коду стане значно більше ніж самої програми. І в Python є спеціальні бібліотеки, які допоможуть нам зробити тести більш читабельними. Одна з таких бібліотек - pytest.
6.2 Тестування з використанням pytest
pytest - це програма сторонніх розробників, яку ви можете завантажити і встановити, і яка автоматизує тестування вашого коду. І що приємно в цій бібліотеці і подібних до неї, так це те, що вона приймає деякі конвенції, так що вам не доведеться писати багато рядків коду вручну.
Щоб встановити pytest, виконайте наступну команду в терміналі:
Terminal
pip install pytestТепер давайте перепишемо наш тест з використанням pytest:
from calculator import square
def test_square():
assert square(2) == 4
assert square(3) == 9
assert square(-2) == 4
assert square(-3) == 9
assert square(0) == 0Тепер запустимо тестування за допомогою pytest:
Terminal
pytest test_calculator.py. [100%]
1 passed in 0.00s
Документація pytest доступна за посиланням: docs.pytest.org
Ідеально було б запустити якомога більше тестів одночасно, щоб отримати якомога більше підказок для пошуку помилки. Тому дозвольте мені покращити дизайн мого тестового коду. Замість однієї великої функції під назвою test_square(), яка тестує всю функцію з великою кількістю різних вхідних даних, давайте розділимо тести на різні категорії: тести для додатніх чисел, тести для від’ємних чисел, тести для нуля:
from calculator import square
def test_square_positive():
assert square(2) == 4
assert square(3) == 9
def test_square_negative():
assert square(-2) == 4
assert square(-3) == 9
def test_square_zero():
assert square(0) == 0Що приємно в pytest та інших фреймворках для модульного тестування, так це те, що всі три ці тестові функції будуть запущені автоматично. Навіть якщо одна з них не спрацює, інші будуть запущені.
Тепер запустимо тести:
Terminal
pytest test_calculator.py. [100%]
3 passed in 0.00s
Давайте подивимось, що станеться, якщо функції square передати рядок замість числа? Для цього приберемо функцію int() з calculator.py:
def main():
x = input('Введіть число: ') # cat
print(f'Число {x} в квадраті дорівнює {square(x)}')
def square(n):
return pow(n, 2)
if __name__ == '__main__':
main()Тепер додамо тест в test_calculator.py:
from calculator import square
def test_square_positive():
assert square(2) == 4
assert square(3) == 9
def test_square_negative():
assert square(-2) == 4
assert square(-3) == 9
def test_square_zero():
assert square(0) == 0
def test_square_string():
with pytest.raises(TypeError):
square('cat')Тепер давайте розглянемо, як можна протестувати код, який очікує на вхід рядки. Для цього ми повернемося до файлу hello.py:
def main():
name = input('Введіть ваше ім\'я: ')
print(hello(name))
def hello(name='світ'):
return f'Привіт, {name}!'
if __name__ == '__main__':
main()Зверніть увагу, що ми дещо змінили базову версію програми hello.py. Тепер функція hello() повертає рядок замість того, щоб його виводити. Тому у функції main() ми викликаємо функцію hello() і виводимо результат на екран.
В міру того, як ваші програми стають все більш складними, найкращою практикою є відсутність побічних ефектів. Бажано їх уникнути, особливо якщо ви хочете, щоб ваш код можна було протестувати.
Тепер створимо тест в test_hello.py, але цього разу використаємо цикли:
Terminal
code test_hello.pyfrom hello import hello
def test_default():
assert hello() == 'Привіт, світ!'
def test_name():
for name in ['Гаррі', 'Рон', 'Герміона']:
assert hello(name) == f'Привіт, {name}!'Тепер запустимо тести:
Terminal
pytest test_hello.py. [100%]
2 passed in 0.00s
6.3 Тестування декількох файлів
Тепер давайте розглянемо, як можна тестувати декілька файлів. Для цього створимо нову директорію test:
Terminal
mkdir testВ середині цієї директорії створимо файл test_hello.py:
Terminal
code test/test_hello.pyfrom hello import hello
def test_default():
assert hello() == 'Привіт, світ!'
def test_augment():
assert hello('Гаррі') == 'Привіт, Гаррі!'Тепер в середині директорії необхідно створити файл __init__.py. Це дозволить Python розпізнати цю директорію як пакет:
Terminal
code test/__init__.pyТепер можемо провести тестування всієї директорії test:
Terminal
pytest test..
2 passed in 0.01s



