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
pytest plugin that let you automate actions and assertions with test metrics reporting executing plain YAML files

pytest-play pytest-play is a codeless, generic, pluggable and extensible automation tool, not necessarily test automation only, based on the fantastic

pytest-dev 67 Dec 01, 2022
FFPuppet is a Python module that automates browser process related tasks to aid in fuzzing

FFPuppet FFPuppet is a Python module that automates browser process related tasks to aid in fuzzing. Happy bug hunting! Are you fuzzing the browser? G

Mozilla Fuzzing Security 24 Oct 25, 2022
Obsei is a low code AI powered automation tool.

Obsei is a low code AI powered automation tool. It can be used in various business flows like social listening, AI based alerting, brand image analysis, comparative study and more .

Obsei 782 Dec 31, 2022
Selenium Manager

SeleniumManager I'm fed up with always having to struggle unnecessarily when I have to use Selenium on a new machine, so I made this little python mod

Victor Vague 1 Dec 24, 2021
The (Python-based) mining software required for the Game Boy mining project.

ntgbtminer - Game Boy edition This is a version of ntgbtminer that works with the Game Boy bitcoin miner. ntgbtminer ntgbtminer is a no thrills getblo

Ghidra Ninja 31 Nov 04, 2022
Um scraper feito em python que gera arquivos de excel baseados nas tier lists do site LoLalytics.

LoLalytics-scraper Um scraper feito em python que gera arquivos de excel baseados nas tier lists do site LoLalytics. Começando por um único script com

Kevin Souza 1 Feb 19, 2022
Pytest support for asyncio.

pytest-asyncio: pytest support for asyncio pytest-asyncio is an Apache2 licensed library, written in Python, for testing asyncio code with pytest. asy

pytest-dev 1.1k Jan 02, 2023
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.

Darren Burns 1k Dec 31, 2022
Just a small test with lists in cython

Test for lists in cython Algorithm create a list of 10^4 lists each with 10^4 floats values (namely: 0.1) - 2 nested for iterate each list and compute

Federico Simonetta 32 Jul 23, 2022
create custom test databases that are populated with fake data

About Generate fake but valid data filled databases for test purposes using most popular patterns(AFAIK). Current support is sqlite, mysql, postgresql

Emir Ozer 2.2k Jan 04, 2023
:game_die: Pytest plugin to randomly order tests and control random.seed

pytest-randomly Pytest plugin to randomly order tests and control random.seed. Features All of these features are on by default but can be disabled wi

pytest-dev 471 Dec 30, 2022
pytest plugin for manipulating test data directories and files

pytest-datadir pytest plugin for manipulating test data directories and files. Usage pytest-datadir will look up for a directory with the name of your

Gabriel Reis 191 Dec 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 09, 2022
Enabling easy statistical significance testing for deep neural networks.

deep-significance: Easy and Better Significance Testing for Deep Neural Networks Contents ⁉️ Why 📥 Installation 🔖 Examples Intermezzo: Almost Stocha

Dennis Ulmer 270 Dec 20, 2022
Fidelipy - Semi-automated trading on fidelity.com

fidelipy fidelipy is a simple Python 3.7+ library for semi-automated trading on fidelity.com. The scope is limited to the Trade Stocks/ETFs simplified

Darik Harter 8 May 10, 2022
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

David Wolever 714 Dec 21, 2022
Cornell record & replay mock server

Cornell: record & replay mock server Cornell makes it dead simple, via its record and replay features to perform end-to-end testing in a fast and isol

HiredScoreLabs 134 Sep 15, 2022
masscan + nmap 快速端口存活检测和服务识别

masnmap masscan + nmap 快速端口存活检测和服务识别。 思路很简单,将masscan在端口探测的高速和nmap服务探测的准确性结合起来,达到一种相对比较理想的效果。 先使用masscan以较高速率对ip存活端口进行探测,再以多进程的方式,使用nmap对开放的端口进行服务探测。 安

starnightcyber 75 Dec 19, 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 03, 2022
Tattoo - System for automating the Gentoo arch testing process

Naming origin Well, naming things is very hard. Thankfully we have an excellent

Arthur Zamarin 4 Nov 07, 2022