Slide 1

Slide 1 text

@imrohitsanj ROHIT SANJAY Unit Testing in Python

Slide 2

Slide 2 text

@imrohitsanj • Final year electronics engineering student at MIT Manipal • Interned at a startup called Jovian.ml where I worked on setting up the unit test suite for the jovian-py python library- github.com/jovianml/jovian-py • Google Summer of Code 2020 intern at NumFOCUS - working on a project called testbook, which is a unit testing library for Jupyter Notebooks - github.com/nteract/testbook Find my irregularly updated blog at rohitsanjay.com Follow me on twitter at @imrohitsanj About me

Slide 3

Slide 3 text

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

Slide 4

Slide 4 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 5

Slide 5 text

Introduction to Unit Testing

Slide 6

Slide 6 text

@imrohitsanj • 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

Slide 7

Slide 7 text

@imrohitsanj To run the test..

Slide 8

Slide 8 text

Writing unit tests

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

@imrohitsanj Pytest • Detailed info on failing assert statements (no need to remember self.assert* names) • 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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

@imrohitsanj unittest.mock Mock()

Slide 14

Slide 14 text

@imrohitsanj

Slide 15

Slide 15 text

@imrohitsanj unittest.mock Mock() patch()

Slide 16

Slide 16 text

@imrohitsanj

Slide 17

Slide 17 text

@imrohitsanj

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

When to use mocks

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

@imrohitsanj A little about side_effect When side_effect is an exception

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Using context managers

Slide 29

Slide 29 text

@imrohitsanj • It’s a simple “protocol” (or interface) that your object needs to follow so it can be used with the with statement. • You need to 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 30

Slide 30 text

@imrohitsanj

Slide 31

Slide 31 text

@imrohitsanj contextlib

Slide 32

Slide 32 text

@imrohitsanj With that context managed, we can now move on to the next part James Powell: So you want to be a Python expert?

Slide 33

Slide 33 text

@imrohitsanj 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 34

Slide 34 text

@imrohitsanj @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 35

Slide 35 text

pytest

Slide 36

Slide 36 text

@imrohitsanj • 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 37

Slide 37 text

@imrohitsanj 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 38

Slide 38 text

@imrohitsanj 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 39

Slide 39 text

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

Slide 40

Slide 40 text

@imrohitsanj 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 41

Slide 41 text

@imrohitsanj • Write efficient and maintainable tests by making use of all the pytest features like fixtures and parametrize. Read more about it in their docs - docs.pytest.org • Use contextmanagers to perform setup and teardown functions • Use pytest plugins like pytest-cov to calculate code coverage on your tests Conclusion

Slide 42

Slide 42 text

@imrohitsanj

Slide 43

Slide 43 text

@imrohitsanj That’s all folks!

Slide 44

Slide 44 text

@imrohitsanj Questions?

Slide 45

Slide 45 text

Memes

Slide 46

Slide 46 text

@imrohitsanj

Slide 47

Slide 47 text

@imrohitsanj

Slide 48

Slide 48 text

@imrohitsanj That’s all folks.

Slide 49

Slide 49 text

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