PYTEST

YES, LET'S GO!

About me

Santiago Fraire

Software Engineer

🇦🇷 ➡️ 🇳🇱

About testing

It is an essential part of the lifecycle of a project

  • Catch bugs early
  • Guarantees a certain behaviour

Core philosphy of pytest

dive into it
  • Simple
  • Functional approach
  • Dependency injection

Unittest

small ugly kitchen

Pytest

big kitchen

Intuitive tracebacks

Unittest on failure

# unittest
def test_location(self):
  location = get_location()
  self.assertEqual(location, "bordeaux")
big kitchen

Pytest on failure

# pytest
def test_location():
  location = get_location()
  assert location == "bordeaux"
big kitchen

And more

  • Support for unittests
  • Select specific tests (-k EXPRESSION)
    pytest -k isupper tests/
  • Mark tests
    @pytest.mark.skip
    @pytest.mark.xfail
  • Filter marked tests (-m)
    pytest -m awesome tests/
  • Run from last failed test (--lf)

TESTS ARE DOCUMENTATION

Scopes

  • function (default)
  • class
  • module
  • package
  • session
Docs

Scope: "function"

@pytest.fixture()
scope function

Scope: "module"

@pytest.fixture(scope="module")
scope module

Factories

@pytest.fixture()
def new_customer():
    def _new_customer(name):
        return {"name": name}

    return _new_customer


def test_multi_customers(new_customer):
    customer_1 = new_customer("jon")
    customer_2 = new_customer("mike")

    assert customer_1["name"] == "jon"
    assert customer_2["name"] == "mike"
factoryboy

PARAMETRIZE

@pytest.mark.parametrize(
    "current_version, increment,expected",
    [
        ("2.0.0", "PATCH", "2.0.1"),
        ("2.0.1", "PATCH", "2.0.2"),
        ("2.0.2", "MINOR", "2.1.0"),
        ("2.1.0", "MAJOR", "3.0.0"),
    ],
)
def test_generate_version(current_version, increment, expected):
    new_version = generate_version(current_version, increment)
    assert new_version == expected

Configuring pytest

  • pytest.ini
  • conftest.py

Mocks

Mocks are callable and create attributes as new mocks when you access them

Mocks record how you use them, allowing you to make assertions about what your code has done to them

class Serializer:
    def __init__(self, protocol):
        self.protocol = protocol

    def serialize(self, payload):
        return self.protocol.serialize(payload)

mock_protocol = mock.Mock()
s = Serializer(mock_protocol)
payload = {"name": "jon"}
s.serialize(payload)
mock_protocol.serialize.assert_called_with(payload)
No hardcoded protocol Our mock as protocol Assert mock was called

TDD

  • Robust and simpler code
  • Let the tests guide you, not requirements

Requirement

Generate status of a meetup
  • running: if it's happening now
  • over: it has already happen
  • coming_soon: if it has not happen yet

Refactoring with tests in mind

  • Benefit composition over inheritance
  • Easy to test usually means easy to use

Plugins

Resources

Questions?

questions

Thanks!