Python extras#

Overview#

These notes cover useful python skills, particularly for finalizing your team’s project.

Logging#

These notes were adapted from [@pythonLoggingPythonReal]

See this article from Real Python for a complete explanation.

Important notes:

Unit testing#

These notes were adapted from:

  • [@pythonEffectivePythonTesting]

  • [@PytestDocumentation]

There are two main libraries for unit tests in python:

  • unittest: included in the standard python library

  • pytest: a very popular third party library for writing unit tests in python

I’ve used both before, and they’re both great. In this course, we’ll be using pytest. There’s a few reasons for that, which mostly aren’t that important, but in general, compared to unittest, pytest requires far less boilerplate code to accomplish the same things. For example:

To create one unit test that always passes, and one that always fails, using unittest:

from unittest import TestCase

class TryTesting(TestCase):
    def test_always_passes(self):
        self.assertTrue(True)

    def test_always_fails(self):
        self.assertTrue(False)

The following steps were necessary:

  • Import the TestCase class from unittest

  • Create TryTesting, a subclass of TestCase

  • Write a method in TryTesting for each test

  • Use one of the self.assert methods from unittest.TestCase to make assertions

This boilerplate code is required for every single test.

To create one unit test that always passes, and one that always fails, using pytest:

def test_always_passes():
    assert True

def test_always_fails():
    assert False

The following steps were necessary:

  • define functions beginning with the name test_

  • use Python’s built-in assert keyword directly

Much less boilerplate code required!

Installing pytest#

In your virtual environment, use pip to install pytest:

$ pip install pytest

Creating and running tests#

Create a new file called test_sample.py, containing a function, and a test:

def func(x):
    return x + 1


def test_answer():
    assert func(3) == 5

Run the test:

$ pytest

See pytest: Getting Started documentation for more details.

See my example_system unit tests for examles relevant to our project.

Dependency injection with pytest fixutres#

pytest is designed to follow a variant of the “functional” testing paradigm called Arrange-Act-Assert:

  • Arrange, or set up, the conditions for the test

  • Act by calling some function or method

  • Assert that some end condition is true

pytest enables the creation of fixtures, that is, test objects, that can be used across tests to simplify the Arrange step:

import pytest

# This annotation marks the function as a fixture generator
@pytest.fixture
def example_fixture():
    return 1

# Make tests use that fixture by providing the name as a parameter of the test function:
def test_with_fixture(example_fixture):
    assert example_fixture == 1

You can see how I use fixtures in the example_system conftest.py files of the sample test code in the final-project-upstream.

conftest.py#

You’ll notice in the example_system tests that I define fixtures in files named conftest.py:

#| source-line-numbers: "2,4"
final-project-upstream/iot_subsystems/tests/example_system/
├── conftest.py
├── integration
│   ├── conftest.py
│   ├── __init__.py
│   └── test_system.py
└── unit
    ├── __init__.py
    ├── test_aht20.py
    └── test_fan.py

This is a pytest convention for sharing fixtures across multiple test files, allowing you to keep your test files concise and to avoid needing to recreate dependencies. This is very useful for our project, where we have many custom classes we are creating and mocking.

You can see more about conftest.py in the pytest documentation

using pytest on asynchronous code#

In our final project, there are quite a few asynchronous functions to test.

See the example_system.py unit tests from the final-project-upstream code for an example.

using logot to test logs#

pytest does include a fixture called caplog for testing if logs were written.

I found that the 3rd party package logot was much simpler to use.

See the example_system.py unit tests from the final-project-upstream code for an example:

Further pytest reference#

I found the following documentation really useful when I was making the example_system/ starter code tests: