A Simple Unit Test Matcher Library for Python 3

Overview

pychoir - Python Test Matchers for humans

PyPI version 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, 3.9 and 3.10. 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
  • With poetry: poetry add --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?

pychoir has quite a semi-vast 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 ;)

You might also like...
A test fixtures replacement for Python

factory_boy factory_boy is a fixtures replacement based on thoughtbot's factory_bot. As a fixtures replacement tool, it aims to replace static, hard t

A test fixtures replacement for Python

factory_boy factory_boy is a fixtures replacement based on thoughtbot's factory_bot. As a fixtures replacement tool, it aims to replace static, hard t

splinter - python test framework for web applications

splinter - python tool for testing web applications splinter is an open source tool for testing web applications using Python. It lets you automate br

Parameterized testing with any Python test framework

Parameterized testing with any Python test framework Parameterized testing in Python sucks. parameterized fixes that. For everything. Parameterized te

A test fixtures replacement for Python

factory_boy factory_boy is a fixtures replacement based on thoughtbot's factory_bot. As a fixtures replacement tool, it aims to replace static, hard t

Flexible test automation for Python

Nox - Flexible test automation for Python nox is a command-line tool that automates testing in multiple Python environments, similar to tox. Unlike to

Python wrapper of Android uiautomator test tool.

uiautomator This module is a Python wrapper of Android uiautomator testing framework. It works on Android 4.1+ (API Level 16~30) simply with Android d

Ward is a modern test framework for Python with a focus on productivity and readability.
Ward is a modern test framework for Python with a focus on productivity and readability.

Ward is a modern test framework for Python with a focus on productivity and readability.

AllPairs is an open source test combinations generator written in Python

AllPairs is an open source test combinations generator written in Python

Comments
  • Allow Matchers in InAnyOrder in any order

    Allow Matchers in InAnyOrder in any order

    • Previously the first matching value was used for each
    • Now Matchers that match same values will have all variations tried until a match is found or variations exhausted
    opened by kajaste 0
  • Make pychoir PEP 561 compatible

    Make pychoir PEP 561 compatible

    Before the change, this code

    # test.py
    from pychoir import Matcher
    
    reveal_type(Matcher)
    

    resulted in this:

    $ mypy test.py
    test.py:1: error: Skipping analyzing 'pychoir': found module but no type hints or library stubs
    test.py:1: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
    test.py:3: note: Revealed type is 'Any'
    

    It's because py.typed file was not part of the pychoir package Screenshot 2021-06-01 at 15 16 19

    opened by jerry-git 0
Releases(v0.0.25)
Owner
Antti Kajander
Coding since 1996, for money since 2010. From embedded core routers to full stack web apps. Mostly C, C++, Scala and Python with a twist of TypeScript & Go.
Antti Kajander
Simple assertion library for unit testing in python with a fluent API

assertpy Simple assertions library for unit testing in Python with a nice fluent API. Supports both Python 2 and 3. Usage Just import the assert_that

null 19 Sep 10, 2022
A small automated test structure using python to test *.cpp codes

Get Started Insert C++ Codes Add Test Code Run Test Samples Check Coverages Insert C++ Codes you can easily add c++ files in /inputs directory there i

Alireza Zahiri 2 Aug 3, 2022
a plugin for py.test that changes the default look and feel of py.test (e.g. progressbar, show tests that fail instantly)

pytest-sugar pytest-sugar is a plugin for pytest that shows failures and errors instantly and shows a progress bar. Requirements You will need the fol

Teemu 963 Dec 28, 2022
Ab testing - The using AB test to test of difference of conversion rate

Facebook recently introduced a new type of offer that is an alternative to the current type of bidding called maximum bidding he introduced average bidding.

null 5 Nov 21, 2022
A pytest plugin to run an ansible collection's unit tests with pytest.

pytest-ansible-units An experimental pytest plugin to run an ansible collection's unit tests with pytest. Description pytest-ansible-units is a pytest

Community managed Ansible repositories 9 Dec 9, 2022
BDD library for the py.test runner

BDD library for the py.test runner pytest-bdd implements a subset of the Gherkin language to enable automating project requirements testing and to fac

pytest-dev 1.1k Jan 9, 2023
A simple tool to test internet stability.

pingtest Description A personal project for testing internet stability, intended for use in Linux and Windows.

chris 0 Oct 17, 2021
A simple serverless create api test repository. Please Ignore.

serverless-create-api-test A simple serverless create api test repository. Please Ignore. Things to remember: Setup workflow Change Name in workflow e

Sarvesh Bhatnagar 1 Jan 18, 2022
Green is a clean, colorful, fast python test runner.

Green -- A clean, colorful, fast python test runner. Features Clean - Low redundancy in output. Result statistics for each test is vertically aligned.

Nathan Stocks 756 Dec 22, 2022
splinter - python test framework for web applications

splinter - python tool for testing web applications splinter is an open source tool for testing web applications using Python. It lets you automate br

Cobra Team 2.6k Dec 27, 2022