Skip to main content

Use modern Python type hints to design expressive tests. Reject boilerplate. Embrace complexity.

Project description

pytest-embrace :gift_heart:

The pytest-embrace plugin enables judicious, repeatable, lucid unit testing.

Philosophy :bulb:

  1. Table-oriented (parametrized) tests are indespensible.
  2. Type hints and modern Python dataclasses are very good.
  3. Language-level APIs (like namespaces) are a honkin' great idea.
  4. Code generation is really underrated.
  5. The wave of type-driven Python tools like Pydantic and Typer (both dependencies of this library) is very cowabunga––and only just beginning :ocean:

Features :white_check_mark:

  • Completely customizable test design
  • Type hints everywhere
  • Table-oriented testing
  • Strongly-typed test namespaces
  • Highly cusomizable code generation––powered by Pep 593
  • Readable errors, early and often
  • Reporting / discovery

Basic Usage :wave:

Like any pytest plugin, pytest-embrace is configured in conftest.py.

The main ingredients are:

  1. The "case" –– which can be any class decorated with builtins.dataclasses.dataclass.
  2. The "runner" –– which is just a tricked out Pytest fixture to run assertions against your case.
  3. The "caller" –– which is is another tricked out fixture that your tests will use.
# conftest.py
from dataclasses import dataclass
from typing import Callable

from pytest_embrace import CaseArtifact, Embrace


@dataclass
class Case:
    arg: str
    func: Callable
    expect: str


embrace = Embrace(Case)

@embrace.register_case_runner
def run_simple(case: Case):
    result = case.func(case.arg)
    assert result == case.expect
    return result


simple_case = embrace.caller_fixture_factory('simple_case')

With the above conftest, you can write tests like so:

  1. Make a module with attributes matching your Embrace()'d object
  2. Request your caller fixture in normal Pytest fashion
# test_func.py
arg = 'Ainsley'
func = lambda x: x * 2
expect = 'AinsleyAinsley'


def test(simple_case):
	...

Or you can go table-oriented and run many tests from one module––just like with pytest.mark.parametrize.

# test_many_func.py
from conftest import Case

table = [
    Case(arg="haha", func=lambda x: x.upper(), expect="HAHA"),
    Case(arg="wow damn", func=lambda x: len(x), expect=8),
    Case(arg="sure", func=lambda x: hasattr(x, "beep"), expect=False),
]


def test(simple_case):
    ...

Strongly Typed Namespaces :muscle:

Before even completing the setup phase of your Embrace()'d tests, this plugin uses Pydantic to validate the values set in your test modules. No type hints required.

That means there's no waiting around for expensive setups before catching simple mistakes.

# given this case...
arg = "Ainsley"
func = lambda x: x * 2
expect = b"AinsleyAinsley"


def test(simple_case):
    ...

Running the above immediately produces this error:

E   pytest_embrace.exc.CaseConfigurationError: 1 invalid attr values in module 'test_wow':
E       Variable 'expect' should be of type str

The auxilary benefit of this feature is hardening the design of your code's interfaces––even interfaces that exist beyond the "vanishing point" of incoming data that you can't be certain of: Command line inputs, incoming HTTP requests, structured file inputs, etc.

Code Generation :robot:

Installing pytest-embrace adds a flag to pytest called --embrace.

It can be used to scaffold tests based on any of your registered cases.

With the example from above, you can do this out of the box:

pytest --embrace simple_case

Which puts this in your clipboard:

# test_more.py
from pytest_embrace import CaseArtifact
from conftest import Case

arg: str
func: "Callable"
expect: str


def test(simple_case: CaseArtifact[Case]):
    ...

Copypasta'd test cases like this can also be table-style: [Soon.]

pytest --embrace-table 3

The value passed to the --table flag will produce that many rows.

# test_table_style.py
from pytest_embrace import CaseArtifact
from conftest import Case

table = [
    # Case(arg=..., func=..., expect=...),
    # Case(arg=..., func=..., expect=...),
    # Case(arg=..., func=..., expect=...),
]

def test(simple_case: CaseArtifact[Case]):
    ...

By default, each item is commented out so you don't end up with linter errors upon opening your new file.

If that's not cool, don't worry! It's configurable. :sunglasses:

Config With Pep 593 :star2:

In order to customize the behavior of your test cases, pytest-embrace :flushed: embraces :flushed: the new Annotated type.

:information_source: If you've never heard of Pep 593 or Annotated, the tl;dr is that Annotated[<type>, ...] takes any number of arguments after the first one (the actual hint) that developers (me) can use at rumtime.

The pytest_embrace.anno namespace provides a number of utilities for controlling test parsing and code generation via Annotated.

from dataclasses import dataclass
from typing import Annotations

from pytest_embrace import anno


@dataclass
class FancyCase:
    prop_1: Annotated[str, anno.TopDown()]
    prop_2: Annotated[list[int], anno.OnlyWith('prop_3')]
    prop_3: Annotated[dict[str, set], anno.GenComment('Please enjoy prop_3!')]


e = Embrace(FancyCase, comment_out_table_values=False)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytest-embrace-0.2.0.tar.gz (10.1 kB view details)

Uploaded Source

Built Distribution

pytest_embrace-0.2.0-py3-none-any.whl (8.8 kB view details)

Uploaded Python 3

File details

Details for the file pytest-embrace-0.2.0.tar.gz.

File metadata

  • Download URL: pytest-embrace-0.2.0.tar.gz
  • Upload date:
  • Size: 10.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 pkginfo/1.8.3 readme-renderer/35.0 requests/2.28.0 requests-toolbelt/0.9.1 urllib3/1.26.9 tqdm/4.64.0 importlib-metadata/4.11.4 keyring/23.6.0 rfc3986/2.0.0 colorama/0.4.5 CPython/3.9.13

File hashes

Hashes for pytest-embrace-0.2.0.tar.gz
Algorithm Hash digest
SHA256 36b9018a1d8d183fea3479eea91f339e11684aab0f4db66700d2cdb0e9b37a72
MD5 ed00d6c64bef65e19ed0757fdb47c8f5
BLAKE2b-256 1ab77da161243e9622e1fd93a43703044d3626aea80bfee7de7dedb8367071e4

See more details on using hashes here.

File details

Details for the file pytest_embrace-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pytest_embrace-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 8.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 pkginfo/1.8.3 readme-renderer/35.0 requests/2.28.0 requests-toolbelt/0.9.1 urllib3/1.26.9 tqdm/4.64.0 importlib-metadata/4.11.4 keyring/23.6.0 rfc3986/2.0.0 colorama/0.4.5 CPython/3.9.13

File hashes

Hashes for pytest_embrace-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f26522da8812329f5debe6790b415bd4e91233d3656b4cf738862210ba72d67c
MD5 1169d1d1b49d355002dcc426f5e747a2
BLAKE2b-256 fc9031e65211fca3ece232b8af1c04b0eb07153b756a1b8afb797c6228dfc354

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page