Skip to main content

Behavior-oriented, expressive, developer-friendly assertions library

Project description

grappa logo

Build Status PyPI Coverage Status Documentation Status Stability Code Climate Python Versions Say Thanks

About

grappa is a behavior-oriented, self-declarative, expressive and developer-friendly lightweight assertion library for Python that aims to make testing more productive and frictionless for humans.

grappa comes with two declarative assertion styles: expect and should.

It also comes with a detailed, human-friendly error reporting system that aims to reduce friction, provide better feedback and improve human speed and agility while identifying and fixing errors.

To get started, take a look to the showcase code, tutorial, available plugins and operators documentation.

For HTTP protocol assertions, see grappa-http.

Status

grappa is considered stable software, however it’s not mature, widely used software. New features may be added from time to time or minor bugs may be experienced.

Community contributions and bug reports are very welcome.

Showcase

A small example demonstrating some grappa features. See documentation and tutorial for more examples.

from grappa import should

True | should.be.true
False | should.be.false
None | should.be.none

'' | should.be.empty
[] | should.be.empty
'foo' | should.exists

3.14 | should.be.lower.than(4)
3.14 | should.be.higher.than(3)
3.14 | should.be.within(2, 4)

'bar' | should.be.equal.to('bar', msg='value is not "bar"')
[1, 2, 3] | should.be.equal.to([1, 2, 3])

'hello, grappa' | should.startswith('hello')
'hello, grappa' | should.endswith('grappa')
[1, 2, 3, 4] | should.startswith(1)
[1, 2, 3, 4] | should.endswith(4)

'Hello grappa' | should.match('(\W)+ grappa$')
'Hello grappa' | should.contain('grappa') | should.contain('he')
['foo', 'bar'] | should.contain('foo') | should.do_not.contain('baz')

'foo' | should.be.a('string')
{'foo': True} | should.be.a('dict')

iter([1, 2, 3]) | should.have.length.of(3)
[1, 2, 3] | should.be.a('list') > should.have.length.of(3)

(lambda x: x) | should.be.callable
(lambda x: x) | should.not_have.type.of('generator')

'foo' | should.pass_test(lambda x: x in 'foo bar')
'foo' | should.pass_function(lambda x: len(x) > 2)

(lambda: x) | should.raises(NameError)
(lambda: x) | should.does_not.raises(RuntimeError)

{'foo': 'bar'} | should.have.key('foo').that.should.be.equal('bar')
(1, 2, 3, 4) | should.be.a(tuple) > should.have.index.at(3) > should.be.equal.to(4)

an_object | should.have.properties('foo', 'bar', 'baz')
an_object | should.implement.methods('foo', 'bar', 'baz')

{'foo': True, 'bar': False} | should.all(should.have.key('foo'), should.have.key('bar'))
{'foo': True, 'bar': False} | should.any(should.have.key('foo'), should.have.key('baz'))

({'bar': [1, 2, 3]}
    | should.have.key('bar')
    > should.be.a('list')
    > should.have.length.of(3)
    > should.contain.item(3)
    > should.have.index.at(1)
    > should.be.equal.to(2))

with should('foo'):
    should.be.a(str)
    should.have.length.of(3)
    should.be.equal.to('foo')

Let’s see how the error report looks like in grappa running in pytest.

See error reporting documentation for more details about how grappa error report system works.

======================================================================
FAIL: tests.should_test.test_grappa_assert
======================================================================
Traceback (most recent call last):
File ".pyenv/versions/3.6.0/lib/python3.6/site-packages/nose/case.py", line 198, in runTest
self.test(*self.arg)
File "grappa/tests/should_test.py", line 16, in test_grappa_assert
x | should.be.have.length.of(4)
File "grappa/grappa/test.py", line 248, in __ror__
return self.__overload__(value)
File "grappa/grappa/test.py", line 236, in __overload__
return self.__call__(subject, overload=True)
File "grappa/grappa/test.py", line 108, in __call__
return self._trigger() if overload else Test(subject)
File "grappa/grappa/test.py", line 153, in _trigger
raise err
AssertionError: Oops! Something went wrong!

The following assertion was not satisfied
  subject "[1, 2, 3]" should be have length of "4"

Message
  subject list must have at least 4 items

Reasons
   unexpected object length: 3

What we expected
  an object that can be length measured and its length is equal to 4

What we got instead
  an object of type "list" with length 3

Information
   Object length is measured by using "len()" built-in
    Python function or consuming an lazy iterable, such as a
    generator. Most built-in types and objects in Python
    can be tested that way, such as str, list, tuple, dict...
    as well as any object that implements "__len__()" method.
     Reference: https://docs.python.org/3/library/functions.html#len

Where
  File "grappa/tests/should_test.py", line 16, in test_grappa_assert

 8|
 9|  def test_native_assert():
10|      x = [1, 2, 3]
11|      assert len(x) == 4
12|
13|
14|  def test_grappa_assert():
15|      x = [1, 2, 3]
16| >    x | should.be.have.length.of(4)
17|
18|
19|  def test_bool():
20|      True | should.be.true | should.be.present
21|      False | should.be.false | should.be.equal.to(False)
22|      False | should.be.false | should.not_be.equal.to(True)

Demo

showcase

Why grappa?

grappa aims to assist humans while doing a very recurrent and not very fun task in software development: testing things.

The core idea behind grappa comes from the fact that human time is considerably more expensive than machine time, and therefore any machine assistance to optimize processes and close the gap is beneficial.

With grappa you can express almost in plain English what the test contract actually is, but in a way that’s fun and easy to write but also more easy and pleasant to read or maintain by other developers.

The Zen of grappa

  • Testing is about feedback: detailed, easy to understand, human-friendly is always better.

  • Frictionless testing: introducing self-declarative behavior testing patterns can make testing more fun for test writers and more enjoyable for test readers.

  • Expressivity is paramount: humans should easily understand what the code is doing.

  • Human time is expensive: any modern software should assist people to identify and understand errors easily.

  • Make error reporting great again: feedback during testing is key, let’s make it more handy and less frustrating.

  • Testing patterns consolidation: software expectations are limited to the boundaries of language data types and structures.

  • Hurt less feelings: seeing errors is not a nice thing, but it can be painless if details are showed you in a more gentle way.

Features

  • Behavior-oriented expressive fluent API.

  • Built-in assertion DSL with English lexicon and semantics.

  • Supports both expect and should assertion styles.

  • Full-featured built-in assertion operators.

  • Human-friendly and detailed error reporting.

  • Built-in expectations difference comparison between subject and expected values.

  • Extensible assertions supporting third-party plugins.

  • Assertion chaining and composition.

  • Composable assertion via logical operators such as and & or.

  • Testing framework agnostic. Works with unittest, nosetests, pytest, behave

  • Easy to hack via programmatic API.

  • Lightweight and (almost) dependency-free.

  • Works with Python 2.7+, 3+, PyPy and potentially with other Python implementations.

Installation

Using pip package manager:

pip install --upgrade grappa

Or install the latest sources from Github:

pip install -e git+git://github.com/grappa-py/grappa.git#egg=grappa

History

1.0.0 / 2020-11-21

  • feat: major version v1

  • fix: #53, #55, #48, #59 (credits to @sgissinger)

  • feat: added mock call operators (credits to @sgissinger)

  • make github actions fail on every flake8

  • pass flake8

  • enforce implements operator

  • missing kwargs argument

  • add contain tests

  • normalize code between contain and key and add lists expectations to contain

  • improve been_called_with reasons when false

  • use os.path.join instead of remove and Foo class for spy tests

  • os.path.basename not a good candidate to count mock calls because it seems to call itself

  • missing experimental for pypy2

  • re-try to use pypy2 on github actions

  • skip tests which does not work on pypy2

  • add contain tests

  • contain now supports dicts and arrays. Partially fix #59

  • use isinstance

  • improve keys operator tests and add array tests

  • keys operator now supports arrays

  • set env var in step only

  • allow errors on 3.10-dev builds

  • allow insecure commands for github action nightly

  • upgrade action version

  • run 3.10-dev on ubuntu only

  • add python 3.10 nightly to github action pipeline

  • set 50 chars to normalization size

  • flake8 correction and docstrings upgrade

  • add key operator tests

  • key operator simplified and works with tuples, lists and sets. fixes #48 ?

  • do not upgrade pip on github actions

  • try to use pypy only on ubuntu for github actions

  • use only include for pypy

  • add python 3.9, prefer jobs over matrix, add pypy env var in travis ci

  • add mocker stop all

  • add python 3.9 to github actions

  • pypy2 does not seem to work in github actions like it does in TravisCI

  • exclude pypy2 on ubuntu

  • exclude macos pypy2 from ci matrix

  • use pypy2 instead

  • add pypy to github actions

  • Create python-package.yml

  • added mock impl validator docstring and error tips

  • added explicit docstrings to have_been operators

  • use pytest-mock 2.0.0 for python 2.7

  • make flake8 pass

  • set exception __cause__ to get direct causes when internal exceptions occurs

  • move mock implementation to decorators module

  • add tests using pytest-mock spies and stubs which are basic funcs

  • do not rely explicitly on MagicMock but test needed implementation

  • better get attribute handling

  • better called once messages

  • add been_called operator tests

  • add been_called operators

  • missing end line

  • fixes #55 should.not_have.key(…)

  • once negated a sentence stays negated

  • make accessor be called

  • fix(Makefile): lovely tabs

  • fix(Makefile): use twine for publishing

0.1.12 / 2020-02-26

  • feat(version): bimp

  • feat(setup): add python 3.7 & 3.8 classifiers

  • Merge pull request #56 from jdlourenco/collections-abc-six

  • update code according flake8 warnings and errors

  • feat(requirements): bump flake8

  • feat(travis): add python 3.7 & 3.8

  • fix(travis): remove python 3.3 & 3.4

  • change bump six to 0.14

  • change use six.moves.collections_abc module for importing classes that moved to the collections.abc module on python3

0.1.10 / 2018-10-02

  • feat: add only operator #45

0.1.9 / 2018-06-02

  • fix(#42): Add string comparison parity for Python 2.7

v0.1.8 / 2018-01-23

  • Merge pull request #39 from dancingcactus/master

  • Removes unused imports

  • Allow partials to be used with raises operators

  • fix(operator): minor type in exception message

  • Merge pull request #38 from dancingcactus/master

  • Updates the docs for Raises to encapsulate feedback from #37

  • Update README.rst

  • refactor(docs): remove codesponsor

  • feat(docs): add sponsor ad

  • feat(docs): update status note

  • feat(docs): update status note

  • Merge branch ‘master’ of https://github.com/grappa-py/grappa

  • fix(docs): use proper organization name

  • Update AUTHORS

  • refactor(docs): import AUTHORS file

  • feat: add AUTHORS file

  • fix(setup.py): update package URL

v0.1.7 / 2017-05-12

  • feat(#33): show available operators on attribute error

  • feat(#36): add allowed assertion attributes on error

v0.1.6 / 2017-04-28

  • fix(type): expose proper type value if a type value is the expected value

  • fix(reporter): use search() instead of match() for line code matching. fix(reporters): escape underscore sequences

v0.1.5 / 2017-04-28

  • feat(reporters): add code reporter

  • feat(operators): add “that_is”, “which_is” attribute DSL operators

  • refactor(reporter): match additional negation assertions

v0.1.4 / 2017-04-27

  • feat(reporters): match attribute expressions for proper code line reporting

  • feat(equal): enable show_diff report in operator

  • fix(index_test): bad file formatting

  • refactor(index_test): add error test case

  • refactor(index_test): remove commented code

  • feat(docs): add context assertion example in tutorial

  • feat(docs): add context manager example

  • fix(docs): update error exception example

  • refactor(docs): update showcase example

  • feat(operators): add not_satisfy attribute operator

v0.1.3 / 2017-03-29

  • feat(docs): add raise exception examples

  • refactor(docs): update showcase example

  • feat(reporter): normalize value output in subject/expect sections

  • feat(docs): update examples and FAQs. feat(operators): add aliases for start/end operator

  • feat(docs): add link to grappa-http plugin

  • refactor(docs): add operators type section

  • refactor(docs): add beta status documentation notice

  • feat(docs): update description

  • refactor(docs): update status description

  • feat(docs): update links

v0.1.2 / 2017-03-26

  • feat(docs): add matchers supported keyword arguments

  • feat(docs): improve descriptions

  • feat(operators): improve length operator for access based chaining

  • fix(docs): update error custom message example

  • feat(docs): improve documentation. adds operators composition section

  • fix(setup.py): add author email

v0.1.1 / 2017-03-23

  • refactor(diff): process expected values as tuple first

  • fix(contain): remove print statements

  • refactor(core): normalize yielding syntax, add missing documentation

  • refactor(core): normalize yielding syntax, add missing documentation

  • feat(#26): support disable operator chaining

  • feat(#28): better assertion reporting. feat(operators): add index operator

  • refactor(reporter): support raw mode with proper indent pretty printing

  • refactor(operators): add satisfy/satisfies attribute operators

  • feat(diff): consume diff specific subject/expected values

  • feat(operators): add is/is_not operator attributes

  • refactor(core): isolate reporters per module

  • feat(#13, #25): add suboperators support and diff output report

  • refactor(docs): update organization name

  • refactor(docs): update project image

  • refactor(reporter): ignore subject/expected output if empty

  • refactor(reporter): show diff if enabled

  • feat(docs): add in a nutshell section

  • feat(#24, #25): feature enhancements

  • feat(docs): add say thanks badge

  • refactor(reporter): load value from operator first

  • fix(docs): use proper badges

  • fix(docs): update type operator examples

  • fix(metadata): update

  • refactor(test): add chained test for keys

  • feat(Makefile): add publish commands

0.1.0 (2017-03-05)

  • First version (beta)

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

grappa-1.0.0.tar.gz (48.0 kB view hashes)

Uploaded Source

Built Distributions

grappa-1.0.0-py3-none-any.whl (67.3 kB view hashes)

Uploaded Python 3

grappa-1.0.0-py2-none-any.whl (67.3 kB view hashes)

Uploaded Python 2

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