Slide 1

Slide 1 text

ROHIT SANJAY Unit Testing in Python BELPY 2021

Slide 2

Slide 2 text

Hi I’m Rohit Sanjay. - Final Year Engineering Undergraduate at MIT Manipal - Author and maintainer of testbook (under nteract org) - Incoming Software Engineer Intern at Goldman Sachs, Bengaluru rohitsanjay.com

Slide 3

Slide 3 text

--Boris Beizer, in "Software testing techniques" [source] More than the act of testing, the act of designing tests is one of the best bug preventers known.

Slide 4

Slide 4 text

Agenda • Introduction to unit testing • Writing unit tests ❖ unittest vs pytest ❖ Mocking ❖ Using context managers ❖ PyTest features - f i xtures and parametrized tests

Slide 5

Slide 5 text

Introduction to Unit Testing

Slide 6

Slide 6 text

• A unit test is a test that checks that a single component operates in the right way. • A unit test helps you to isolate what is broken in your application and fix it faster. Unit Testing in two sentences That’s it! Moving on..

Slide 7

Slide 7 text

unittest vs pytest

Slide 8

Slide 8 text

unittest pytest

Slide 9

Slide 9 text

• Too much boilerplate code is needed. • The API can make the test code's intent hard to understand • camelCase naming unittest

Slide 10

Slide 10 text

• Detailed info on failing assert statements • Auto-discovery of test modules and functions (searches for test_*.py or *_test.py files) • Modular fixtures for managing small or parametrized long-lived test resources • Tests parametrization • Can run unittest test suites out of the box • Rich plugin architecture, with over 315+ external plugins and thriving community Why pytest is the better option..

Slide 11

Slide 11 text

Also, you have to admit, pytest looks so much better

Slide 12

Slide 12 text

Mocking Source: https://www.mathworks.com/help/matlab/mocking-framework.html Simulates the look and feel of real world objects.

Slide 13

Slide 13 text

unittest.mock Mock()

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

unittest.mock Mock() patch()

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

Mock an object where it is used, not where it came from.

Slide 18

Slide 18 text

When to use mocks

Slide 19

Slide 19 text

System calls jovian/utils/environment.py jovian/tests/utils/test_environment.py

Slide 20

Slide 20 text

Network/API calls jovian/utils/api.py jovian/tests/utils/test_api.py

Slide 21

Slide 21 text

Network/API calls jovian/tests/utils/test_api.py Using patch as decorator

Slide 22

Slide 22 text

Network/API calls jovian/tests/utils/test_api.py What is side_effect?

Slide 23

Slide 23 text

A little about side_effect When side_effect is an exception

Slide 24

Slide 24 text

A little about side_effect When side_effect is a function (or any callable)

Slide 25

Slide 25 text

A little about side_effect When side_effect is an iterable Let us get back to when we should use mocks

Slide 26

Slide 26 text

• System calls • Network/API calls • I/O operations • Unpredictable results (random) When to use mocks

Slide 27

Slide 27 text

Using context managers

Slide 28

Slide 28 text

• It’s a simple “protocol” (or interface) that your object needs to follow so it can be used with the with statement. • Basically all you need to do is add __enter__ and __exit__ methods to an object if you want it to function as a context manager. • Python will call these two methods at the appropriate times in the resource management cycle. Context Managers in Python Are you with me?

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

contextlib

Slide 31

Slide 31 text

With that context managed, we can now move on to the next part

Slide 32

Slide 32 text

Context managers in unit tests jovian/tests/resources/shared.py @contextmanager def temp_directory(): orig_path = os.getcwd() try: with TemporaryDirectory() as dir: os.chdir(dir) yield dir finally: os.chdir(orig_path)

Slide 33

Slide 33 text

@contextmanager def mock_git_repo(): orig_dir = os.getcwd() with temp_directory() as dir: os.mkdir("mock_git_repo") os.chdir("mock_git_repo") commands = textwrap.dedent(""" git init git remote add origin https://github.com/JovianML/mock_repo.git touch notebook.ipynb git add . git commit -m "initialcommit" """).splitlines()[1:] for command in commands: check_call(command.split()) yield dir os.chdir(orig_dir) jovian/tests/resources/shared.py

Slide 34

Slide 34 text

pytest

Slide 35

Slide 35 text

• capsys - Capture, as text, output to sys.stdout and sys.stderr. • testdir - Provide a temporary test directory to aid in running, and testing, pytest plugins. • tmp_path - Provide a pathlib.Path object to a temporary directory which is unique to each test function. • tmpdir - Provide a py.path.local object to a temporary directory which is unique to each test function; replaced by tmp_path. pytest fixtures

Slide 36

Slide 36 text

Custom pytest fixtures import pytest @pytest.fixture def smtp(): import smtplib return smtplib.SMTP("smtp.gmail.com") def test_ehlo(smtp): response, msg = smtp.ehlo() assert response == 250 assert 0 # for demo purposes

Slide 37

Slide 37 text

Custom pytest fixtures import pytest from click.testing import CliRunner @pytest.fixture(scope='module') def runner(): # Get instance of CliRunner runner = CliRunner() return runner jovian/tests/utils/test_cli.py

Slide 38

Slide 38 text

pytest parametrize import pytest @pytest.mark.parametrize( "test_input, expected", [ ("3+5", 8), ("2+4", 6), ("6*9", 42) ] ) def test_eval(test_input, expected): assert eval(test_input) == expected

Slide 39

Slide 39 text

pytest parametrize @pytest.mark.parametrize( "urls, expected_result", [ (["https://jovian.ml"], "https://jovian.ml"), (["https://jovian.ml/"], "https://jovian.ml/"), (["///user/siddhant"], "user/siddhant"), (["https://jovian.ml", "user/siddhant"], "https://jovian.ml/user/siddhant"), (["https://jovian.ml", "/user/siddhant"], "https://jovian.ml/user/siddhant"), ] ) def test_urljoin(urls, expected_result): assert urljoin(*urls) == expected_result jovian/tests/utils/test_misc.py

Slide 40

Slide 40 text

Memes

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

That’s all folks.

Slide 46

Slide 46 text

• To mock, or not to mock - PyCon 2016 • Context Managers and the “with” Statement in Python References