assamtest

mocha-style tests for python
git clone https://git.ce9e.org/assamtest.git

NameSize
README.md4897B
assamtest/__init__.py129B
assamtest/__main__.py748B
assamtest/decorators.py345B
assamtest/expect.py266B
assamtest/register.py1444B
assamtest/reporter.py1842B
assamtest/runner.py1153B
setup.py402B
tests.py2293B

assamtest is an experimental python test framework inspired by JavaScript libraries such as mocha or jasmine.

Usage

# tests.py
import assamtest
from assamtest import expect

@assamtest.suite('A suite is just a function')
def my_suite():
	@assamtest.test('and so is a test')
	def my_test():
		a = True
		expect.true(a)
$ pip install assamtest
$ assamtest
A suite is just a function
  ✓ and so is a test

✓ 1 passed

Why another test framework?

The idea for this library came out of my growing frustration with pytest, especially its parametrize feature.

In jasmine, parametrization is trivial:

describe('#isNumber', function() {
	[1, 1000000, 0, -1].forEach(function(i) {
		it('recognizes ' + i, function() {
			expect(isNumber(i)).toBe(true);
		});
	});
});

This is because the tests are registered explicitly. The popular python test frameworks (pytest, unittest) on the other hand use an implicit mechanism where each function that starts with 'test_' is registered. This makes parametrization way harder than it needs to be.

This library is an attempt to bring the explicit approach to python. However, there are two important differences between the languages that make this approach a bit less elegant in python:

Reference

@test(name=None, args=[], decorators=[])

Register a function as a test:

Async functions are automatically executed in an event loop.

import assamtest
from assamtest import expect

@assamtest.test(args=['+', 5])
@assamtest.test(args=['*', 6])
def my_test(op, value):
	assamtest.expect.equal(eval('2 %s 3' % op), value)

@suite(name=None, args=[], decorators=[])

Register a function as a suite:

import assamtest
from assamtest import expect

@assamtest.suite()
def my_suite():
	@assamtest.before_each()
	def _before_each():
		pass  # do some setup here

	@assamtest.test()
	def my_test():
		expect.equal(2 + 2, 4)

The optional parameters are the same as for test().

@before() / @after()

Register a function to run before/after the whole suite.

There can be only one before/after function per suite.

@before_each() / @after_each()

Register a function to run before/after every test.

There can be only one before_each/after_each function per suite.

expect

A wrapper around the asserts from unittest.TestCase using snake case:

from assamtest import expect

expect.equal(2 + 2, 4)
expect.not_equal(2 + 2, 5)
expect._in(2, [1, 2, 3])
with expect.raises(KeyError):
	{'foo': 0}['bar']

See also the full list of available assertions.

@decorators.skip

Do not execute the test at all::

import assamtest
from assamtest import expect
from assamtest.decorators import skip

@assamtest.test(decorators=[skip])
def my_test():
	expect.equal(2 + 2, 5)

@decorators.fail

Invert the result of the test: If it would fail, pass instead. If it would pass, fail instead::

import assamtest
from assamtest import expect
from assamtest.decorators import fail

@assamtest.test(args=[4])
@assamtest.test(args=[5], decorators=[fail])
def my_test(value):
	expect.equal(2 + 2, value)

Outcome(err, status, level)

Can be used to implement custom outcomes.

A good example of how this can be used is decorators.skip():

import functools
from assamtest import Outcome

def skip(fn):
	@functools.wraps(fn)
	def wrapper(*args, **kwargs):
		raise Outcome(None, 'skipped', 'INFO')
	return wrapper