# unittest-resources

Dynamic resource-based unittest suites made easy.

## Usage

First and foremost, add this package as a project dependency on your
`setup.py`.

```python

from setuptools import setup

setup(
    name='mymodule',
    # ...
    test_suite='tests',
    tests_require=['unittest-resources'],
    #...
    )

```

That said, this module can be used in three ways:
* [`unittest_resources.ResourceTestCase` base class](#unittest_resources.ResourceTestCase)
* [`unittest_resources.ResourceTestMeta` metaclass](#unittest_resources.ResourceTestMeta)
* [`unittest_resources.testing` tool integrations](#unittest_resources.testing)

And then you can run your tests as usual.

```sh
python setup.py test
```

Remember you still have to include your testing dependencies by yourself
(like [pycodestyle](https://pypi.org/project/pycodestyle/),
[pydocstyle](https://pypi.org/project/pydocstyle/),
[mypy](https://pypi.org/project/mypy/) or
[radon](https://pypi.org/project/radon/)) even if they're used only
through this package.

Other option, if you feel comfortable about installing all
**unittest-resources** optional dependencies (ie. especially if you're
using all classes defined on `unittest_resources.testing`), is to
specify `unittest-resources[testing]` as dependency.

### <a name="unittest_resources.ResourceTestCase"></a>Using `unittest_resources.ResourceTestCase` base class

With `unittest_resources.ResourceTestCase` your base (replacing
`unittest.TestCase`).

```python

import unittest_resources


class MyTestCase(unittest_resources.ResourceTestCase):
    """
    TestCase class with tests autogenerated based on resources.

    This regular :class:`unittest.TestCase`-derived class generates
    test methods by recursively inspecting a module for resources,
    based on special class attributes (prefixed by `meta_`).

    The following functions from :module:`importlib.resources` are
    available as :class:`staticmethod`s:
    - :func:`importlib.resources.contents` as `contents`
    - :func:`importlib.resources.is_resource` as `is_resource`
    - :func:`importlib.resources.open_binary` as `open_binary`
    - :func:`importlib.resources.open_text` as `open_text`
    - :func:`importlib.resources.path` as `path`
    - :func:`importlib.resources.read_binary` as `read_binary`
    - :func:`importlib.resources.read_text` as `read_text`
    """

    meta_module = 'mymodule'
    meta_prefix = 'mytest_'
    meta_resource_pattern = re.compile(r'\.test_file')

    def meta_test(self, module, resource):
        # type: (str, str) -> None
        """
        Function will be called with every resource found.

        This function received resource-containing module name, and the
        resource name itself.

        :param module: resource-containing module name
        :type module: str
        :param resource: resource name (usually a filename)
        :type resource: str
        """
        with self.path(module, resource) as path:
            self.assertPathStuff(str(path))

        with self.open_text(module, encoding='utf-8') as f:
            self.assertTextFileStuff(f)

        with self.open_binary(module) as f:
            self.assertBytesFileStuff(f)

        text = self.read_text(module, resource)
        self.assertTextStuff(text)

        data = self.read_binary(module, resource)
        self.assertBytesStuff(text)

```

### <a name="unittest_resources.ResourceTestMeta"></a>Using `unittest_resources.ResourceTestMeta` metaclass

Or, alternatively, using the provided metaclass.

```python

import unittest
import unittest_resources


class MyTestCase(unittest.TestCase,
                 metaclass=unittest_resources.ResourceTestMeta):
    """
    TestCase class with tests autogenerated based on resources.

    This regular :class:`unittest.TestCase`-derived class generates
    test methods by recursively inspecting a module for resources,
    based on special class attributes (prefixed by `meta_`).

    The following functions from :module:`importlib.resources` are
    available as :class:`staticmethod`s:
    - :func:`importlib.resources.contents` as `contents`
    - :func:`importlib.resources.is_resource` as `is_resource`
    - :func:`importlib.resources.open_binary` as `open_binary`
    - :func:`importlib.resources.open_text` as `open_text`
    - :func:`importlib.resources.path` as `path`
    - :func:`importlib.resources.read_binary` as `read_binary`
    - :func:`importlib.resources.read_text` as `read_text`
    """

    meta_module = 'mymodule'
    meta_prefix = 'mytest_'
    meta_resource_pattern = re.compile(r'\.test_file')

    def meta_test(self, module, resource):
        # type: (str, str) -> None
        """
        Function will be called with every resource found.

        This function received resource-containing module name, and the
        resource name itself.

        :param module: resource-containing module name
        :type module: str
        :param resource: resource name (usually a filename)
        :type resource: str
        """
        with self.path(module, resource) as path:
            self.assertPathStuff(str(path))

        with self.open_text(module, encoding='utf-8') as f:
            self.assertTextFileStuff(f)

        with self.open_binary(module) as f:
            self.assertBytesFileStuff(f)

        text = self.read_text(module, resource)
        self.assertTextStuff(text)

        data = self.read_binary(module, resource)
        self.assertBytesStuff(text)

```

### <a name="unittest_resources.testing"></a>Using `unittest_resources.testing` tool integrations

This package provides ready-to-use integrations of some common libraries

```python

import unittest
import unittest_resources.testing


class TestCodeStyle(unittest_resources.testing.CodeStyleTestCase):
    """
    This TestCase class applies pycodestyle to each python file located
    inside a python module.
    """

    meta_module = 'mymodule'

```

Refer to [unittest_resources.testing](./unittest_resources/testing.py)
module itself to see the available classes and
[unittest_resources.tests.test_code](./unittest_resources/tests/test_code.py)
to see how to use them.

## Examples

This package own [test suite](./unittest_resources/tests/test_code.py)
is self-explanatory, and covers covers the usage of this module to run
[pycodestyle](https://pypi.org/project/pycodestyle/),
[pydocstyle](https://pypi.org/project/pydocstyle/),
[mypy](https://pypi.org/project/mypy/) and
[radon](https://pypi.org/project/radon/) over project code.

You can either subclass and customize them to run on your code, or use
them as an example to integrate other tools.

## License

MIT (see [LICENSE](./LICENSE) file).

## Changelog

See [CHANGELOG](./CHANGELOG) file.
