Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unit Testing in Python

Unit Testing in Python

This talk was delivered during a BangPypers meetup on 18th July, 2020. Watch the video here - https://www.youtube.com/watch?v=eVYdPdvS2nQ

Rohit Sanjay

July 18, 2020
Tweet

More Decks by Rohit Sanjay

Other Decks in Programming

Transcript

  1. @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
  2. Agenda • Introduction to unit testing • Writing unit tests

    ❖ unittest vs pytest ❖ Mocking ❖ Using context managers ❖ PyTest features - fixtures and parametrized tests
  3. --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.
  4. @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
  5. @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
  6. @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
  7. @imrohitsanj • System calls • Network/API calls • DB calls

    • I/O operations • Unpredictable results (random) When to use mocks
  8. @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?
  9. @imrohitsanj With that context managed, we can now move on

    to the next part James Powell: So you want to be a Python expert?
  10. @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)
  11. @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
  12. @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
  13. @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
  14. @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
  15. @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
  16. @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
  17. @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
  18. @imrohitsanj • To mock, or not to mock - PyCon

    2016 • Context Managers and the “with” Statement in Python References