Skip to main content

Test Matchers for humans

Project description

pychoir - Python Test Matchers for humans

PyPI version Wheel PyPI Supported Python Versions GitHub Actions (Tests) Documentation Status

Super duper low cognitive overhead matching for Python developers reading or writing tests. Implemented in pure Python, without any dependencies. Runs and passes its tests on 3.6, 3.7, 3.8 and 3.9. PyPy (3.6, 3.7) works fine too.

pychoir has mostly been developed for use with pytest, but nothing prevents from using it in any other test framework (like vanilla unittest) or even outside of testing, if you feel like it.

Installation

  • With pip: pip install pychoir
  • With pipenv: pipenv install --dev pychoir

Documentation

Check out the API Reference on readthedocs for detailed info on all the available Matchers https://pychoir.readthedocs.io/en/stable/api.html

Why?

You have probably written quite a few tests where you assert something like

assert thing_under_test() == {'some_fields': 'some values'}

However, sometimes you do not expect exact equivalence. So you start

result = thing_under_test()

result_number = result.pop('number', None)
assert result_number is None or result_number < 3

result_list_of_strings = result.pop('list_of_strings', None)
assert (
    result_list_of_strings is not None
    and len(result_list_of_strings) == 5
    and all(isinstance(s, str) for s in result_list_of_strings)
)

assert result == {'some_fields': 'some values'}

...but this is not very convenient for anyone in the long run.

This is where pychoir comes in with matchers:

from pychoir import LessThan, All, HasLength, IsNoneOr, And, IsInstance

assert thing_under_test() == {
    'number': IsNoneOr(LessThan(3)),
    'list_of_strings': And(HasLength(5), All(IsInstance(str))),
    'some_fields': 'some values',
}

You can also check many things about the same value: for example And(IsInstance(int), 5) will make sure that the value is not only equal to 5, but is also an int (goodbye to accidental 5.0).

You can place a matcher almost anywhere where a value can be. pychoir matchers work well inside lists, tuples, dicts, dataclasses, ... You can also place normal values inside matchers, and they will match as with traditional == or !=.

A core principle is that pychoir Matchers are composable and can be used freely in various combinations. For example [Or(LessThan(3), 5)] is "equal to" a list with one item, holding a value equal to 5 or any value less than 3.

Can I write custom Matchers of my own

Yes, you can! pychoir Matcher baseclass has been designed to be usable by code outside the library. It also takes care of most of the generic plumbing, so your custom matcher typically needs very little code.

Here is the implementation of IsInstance as an example:

from typing import Any, Type
from pychoir import Matcher

class IsInstance(Matcher):
    def __init__(self, type_: Type[Any]):
        super().__init__()
        self.type = type_

    def _matches(self, other: Any) -> bool:
        return isinstance(other, self.type)

    def _description(self) -> str:
        return self.type.__name__

All you need to take care of is defining the parameters (if any) in __init__(), the match itself in _matches(), and a description of the parameters in _description().

Here is an even simpler Anything matcher that does not take parameters and matches literally anything:

from typing import Any
from pychoir import Matcher

class Anything(Matcher):
    def _matches(self, _: Any) -> bool:
        return True

    def _description(self) -> str:
        return ''

If your custom matcher is generic enough to be useful for everyone, please contribute (fork and make a pull request for now) and have it included in pychoir!

Why not <X>?

PyHamcrest

Nothing wrong with hamcrest as such, but pychoir aims to be better integrated with natural Python syntax, meaning for example that you do not need to use a custom assert function. pychoir matchers are drop-in replacements for your normal values alone or inside structures, even deeply nested ones. You can use hamcrest matchers through pychoir if you like, wrapping them in the Matches(my_hamcrest_matcher) matcher, although the idea is that pychoir would soon come with an equivalent set of matchers.

assertpy

What a nice fluent API for matching, allowing matching multiple things at once. However, you can only match one value at a time. With pychoir you'll be matching the whole result at once, be it a single value, list, tuple, dict, dataclass, you name it. Let's see if pychoir gets some of that fluent stuff going forward as well.

???

I'd be happy to hear from you about other similar libraries.

What is it based on?

Python has a rather peculiar way of handling equivalence, which allows customizing it in wild and imaginative ways. This is a very powerful feature, which one should usually avoid overusing. pychoir is built around the idea of using this power to build a lean and mean matcher implementation that looks like a custom DSL but is actually completely standard Python 3.

What is the project status?

Today, after about a month and a thousand downloads since the first release, pychoir has quite a nice range of Matchers built-in as well as basic API Reference documenting them. New ideas are still plenty and more can be discussed in Discussions. Documentation will receive updates as well. Most remarkably fancy examples are missing. Making pychoir easier to contribute to is also on the list.

Where does the name come from?

It comes from the French word pochoir which means a drawing technique using templates. For some reason this method of matching in tests reminds me of drawing with those. A French word was chosen because it happens to start with a p and a vowel ;)

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

pychoir-0.0.10.tar.gz (19.4 kB view details)

Uploaded Source

Built Distribution

pychoir-0.0.10-py3-none-any.whl (22.2 kB view details)

Uploaded Python 3

File details

Details for the file pychoir-0.0.10.tar.gz.

File metadata

  • Download URL: pychoir-0.0.10.tar.gz
  • Upload date:
  • Size: 19.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/51.0.0 requests-toolbelt/0.9.1 tqdm/4.57.0 CPython/3.9.2

File hashes

Hashes for pychoir-0.0.10.tar.gz
Algorithm Hash digest
SHA256 f8a103f5e9c199b1644048195487f5ed0ac17be82b9878c3b335efdd1fea0b12
MD5 a31130db23482806ba1dbb8b8b0ff847
BLAKE2b-256 3e5e1fcc492e2f2a1fd6e6f4252afa4c91ee7528082ec0d7ee80bfa047653fff

See more details on using hashes here.

Provenance

File details

Details for the file pychoir-0.0.10-py3-none-any.whl.

File metadata

  • Download URL: pychoir-0.0.10-py3-none-any.whl
  • Upload date:
  • Size: 22.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/51.0.0 requests-toolbelt/0.9.1 tqdm/4.57.0 CPython/3.9.2

File hashes

Hashes for pychoir-0.0.10-py3-none-any.whl
Algorithm Hash digest
SHA256 96dbba8cac2d8a5255bd19e247c1a370f9829da42075b32eddcb77ce223a3b77
MD5 6b6f0181cf83f0f57bab7e248f334518
BLAKE2b-256 721f2621893e52988901394d0a54554e2903492011c5afb1a7cec74501b93bdd

See more details on using hashes here.

Provenance

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