Matcher templates for humans
Project description
PyChoir - Python Test Matchers for humans
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.
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 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. 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 of 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, the match itself, and a description of the parameters.
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 ''
Why not <X>?
Hamcrest
Nothing wrong with hamcrest as such, but PyChoir aims to be better integrated with natural Python syntax. You can use hamcrest matchers through PyChoir if you like, wrapping them in the Matches(my_hamcrest_matcher)
matcher.
???
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?
This is only the very start. PyChoir is, however, already useful in many use cases and more features are coming. Next improvements are most likely going to be related to documentation.
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file pychoir-0.0.1.tar.gz
.
File metadata
- Download URL: pychoir-0.0.1.tar.gz
- Upload date:
- Size: 11.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.2.0 pkginfo/1.6.1 requests/2.25.0 setuptools/50.3.2 requests-toolbelt/0.9.1 tqdm/4.54.1 CPython/3.8.6
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3cbe0b0fefb91f1140100a9dd9d5c14d9558900ee13c7523e2c96b9e1a9d38c5 |
|
MD5 | 74a13ad8d8a396fb91be30e723787f77 |
|
BLAKE2b-256 | e67c5a9b60f0c9e4729b91e0c2f244003f234d4921c2786053119093e0c39842 |
Provenance
File details
Details for the file pychoir-0.0.1-py3-none-any.whl
.
File metadata
- Download URL: pychoir-0.0.1-py3-none-any.whl
- Upload date:
- Size: 13.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.2.0 pkginfo/1.6.1 requests/2.25.0 setuptools/50.3.2 requests-toolbelt/0.9.1 tqdm/4.54.1 CPython/3.8.6
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7f299dee63c07146f1e7a05773aefdeec6deacd20006b0f15fa41c8a30285ece |
|
MD5 | 8cc7c38cc01329825ad0a6f16c57cade |
|
BLAKE2b-256 | 52ae17ab0a5179cc6653ffad0a1a77f0e1ebfc2e0142afbc08589935cdac879a |