/utest

Simple Python unit testing.

Primary LanguagePythonOtherNOASSERTION

writeup v0
Dedicated to the public domain under CC0: https://creativecommons.org/publicdomain/zero/1.0/.


# utest

utest is a tiny library for unit testing in Python. It is available via pip for easy distribution. Alternatively, the module can be freely copied into a project. utest has no dependencies beyond the Python standard library.

utest only supports Python 3. The module itself is a single source file, and can be embedded directly in any project. It is also available at <https://pypi.python.org/pypi/utest>.


# License

utest dedicated to the public domain. It is written and maintained by George King.


# Installation
$ pip3 install utest

# Usage

Tests are written as standalone scripts that call the various utest functions. Typical invocations look like this:
| utest(expected_value, fn_to_test, arg0, arg1, kw2=arg2, ...)
| utest_exc(expected_exception, fn_to_test, arg0, arg1, kw2=arg2, ...)

The `utest` function takes as arguments an expected return value, followed by a function to test and any number of positional and keyword arguments. The function is called with the provided arguments inside of a `try` block, and the return value is compared to the expected value.

| # Pass `True` to the lambda, and expect `True` as the result.
| utest(True, lambda b: b, True)

The `utest_exc` function is similar, except that it expects the function under test to raise an exception. Because Python does not implement value equality for exceptions, `utest_exc` implements its own exception comparison, which compares the exception type and the `args` property of the expected and actual exceptions. Improvements to this comparison may prove necessary, as exception types can potentially set attributes and fail to include them in `args`.

| # Invoke the hypothetical `raise_expected` function, expecting a particular `Exception` value.
| utest_exc(Exception('expected'), raise_expected)

The `utest_seq` and `utest_seq_exc` functions mirror those above, except that the returned value is converted to a sequence, and for `utest_seq` the expectation is also converted to a list. Therefore, these functions can be used to check the output of a generator or other returned iterable directly. Note that `utest_seq_exc` is necessary because unlike `utest_exc` it will consume the returned iterable.
| utest_seq([0, 1], range, 2)
| # Invoke the hypothetical `yield_then_raise` function, expecting a particular `Exception` value.
| utest_seq_exc(Exception('expected'), yield_then_raise, 2)

`utest_val` can be used to check a value. An optional description can be provided with the `desc` parameter.
| utest_val((0,1), (0,1), 'tuple test')

`usymmetric` takes one of the above utest functions and arguments. It applies the test function to the arguments as is, and then again with the last two positional arguments swapped. Thus, symmetric functions like binary operators (but also any function where the last two arguments may be freely swapped) can be tested for symmetry.
| usymmetric(utest, 3, operator.add, 1, 2)

When an expectation is not met, utest prints a message to stderr, and increments the failure count. When the process exits, if failures have occured, then an `atexit` handler (set by the utest module on import) prints a summary message and forces a hard exit with status 1.

See the `utest.py` for the complete docstrings, and the tests for more examples.


## Caveats

### `atexit` handlers

Note that as of Python 3.5.2, `atexit` suppresses `SystemExit` exceptions raised in handlers, which is unfortunate because it means that utest must use `_exit` to return a status code of 1, thereby bypassing any lower `atexit` handlers. If your unit tests require other `atexit` handlers to run, then make sure to include `utest` at the very top of the `__main__` file of your test process.

### `_utest_depth` parameter name
`_test_depth` is the only reserved parameter name. Passing this keyword parameter to a function under test is not supported, because it will also be passed along to internal utest failure handling function.


# Testing utest

utest is itself tested using iotest, a process-based integration test harness. iotest is also available via pip:
| $ pip3 install iotest

To run the tests:
| $ iotest test/

The tests are just python scripts, and can be invoked by themselves as well.


# Issues

Please file issues to the github repository: <https://github.com/gwk/utest>.