yaml-testing-framework


Nameyaml-testing-framework JSON
Version 0.0.5 PyPI version JSON
download
home_pagehttps://github.com/fjemi/yaml-testing-framework
SummaryA testing framework where tests are defined in YAML files
upload_time2024-04-26 21:45:19
maintainerNone
docs_urlNone
authorOlufemi Jemilohun
requires_python>=3.7
licenseMIT license
keywords pytest yaml testing
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # YAML Testing Framework

A simple, low-code framework for unit testing in Python with tests are defined in YAML files.

## Features

- Standardized data/tests defined in YAML files
- App functions as a pytest plugin
- Support functional programming
- Can be used to test synchronous and asynchronous logic
- Easy to use API


## Requirements

Python 3.7+


## Installation

```bash
pip install yaml-testing-framework
```


## Example


Create the files
- `test_entrypoint.py` - uses pytest to collect and run tests
```python
from types import SimpleNamespace as sns

import pytest


@pytest.mark.parametrize(argnames='test', argvalues=pytest.yaml_tests)
def test_(test: sns) -> None:
  assert test.expected == test.output
```

- `assertions.py` - contains logic for verifying the output from a function
```python
from types import SimpleNamespace as sns
from typing import Any


 equals(expected: Any, output: Any) -> sns:
  passed = expected == output
  return sns(**locals())
```

- `add.py` - contains the function to test
```python
  def main(a: int, b: int) -> int:
    return a + b
```

- `add_test.yaml` - contains tests for the function
```yaml
configurations:
  resources:
  - assertions.py

tests:
- function: main
  tests:
  - arguments:
      a: 1
      b: 1
    assertions:
    - method: assertions.equals
      expected: 2
  - arguments:
      a: 1
      b: 2
    assertions:
    - method: assertions.equals
      expected: 3
  - arguments:
      a: 1
      b: '1'
    assertions:
    - method: assertions.equals
      field: 2
```

Execute the following command in your command line to run the tests.
```bash
pytest --project-directory=add.py
```


## Configuration

The app can be configured within the pytest settings of a configuration file,
 such as a `pytest.ini`, or in the console when invoking pytest. The
 configurations are

| Field | Type | Description | Default |
| - | - | - | - |
| project-directory | str | Location of a directory containing files or an an individual module or YAML file to test. | . |
| exclude-files | str or list| A list of patterns. Exclude files from testing that match a specified pattern . | [] |
| resources | str or list | The locations of modules to use as resources during tests | [] |
| resources_folder_name | str | Name of folders containing resources to use for tests| _resources |
| yaml-suffix | str | Suffix in the names of YAML files containing tests | _test |


#### Configure pytest.ini


```ini
[pytest]
project-directory = .
exclude_files =
  matching
  patterns
  to
  exclude
resources =
  resource_location_a
  resource_location_b
resources_folder_name = _resources
yaml_suffix = _test
```

#### Configure command line command

```console
pytest \
--project-directory=.app.py \
--exclude_files matching patterns to exclude \
--resources resource_location_a resource_location_b \
--resource-folder-name _resources \
--yaml-suffix _test
```


## YAML Test Files

Tests are defined in YAML files with the top level keys picked up by the app
being:
- `configurations` - Configurations to be used locally for each test in the YAML files
- `tests` - Configurations used for multiple of individual tests.


### Expanding and Collating Tests

Using the app we can define configurations for tests at various levels
(configurations, tests, nested tests), expand those configurations to lower
configurations, and collate individual tests. This allows us to reuse
configurations and reduce the duplication of content across a YAML file. This is
similar to [anchors](https://yaml.org/spec/1.2.2/#anchors-and-aliases) in YAML,
which we can take advantage, along with the other features available in YAML.

#### Example

This is an abstract example of the expanding/collating configurations done by
the app, where the configurations for tests are comprised of:
- `config_a` - a list
- `config_b` - an object
- `config_c` - a string
- `config_d` - null

In this example, we set these configurations at various levels, globally, tests,
and nested tests; and the expanded/collated results are three individual tests
containing various values for each configuration.

```yaml
# Defined

configurations:
  config_a:
  - A
  config_b:
    b: B
  config_c: C


tests:
- config_a:
  - B
- config_b:
    c: C
  tests:
  - config_a:
    - C
    config_c: C0
  - config_d: D
    tests:
    - config_a:
      - B
      config_b:
        b: B0
```

```yaml
# Expanded

tests:
- config_a: # test 1
  - A
  - B  # Appended item
  config_b:
    b: B
  config_c: C
  config_d: null  # Standard test config not defined
- config_a: # test 2
  - A
  - C  # Appended item
  config_b:
    b: B
    c: C  # Added key/value
  config_c: C0  # Replace string
  config_d: null
- config_a: # test 3
  - A
  config_b:
    b: B0  # Updated key/value pair
    c: C
  config_c: C
  config_d: D  # Standard test config defined
```


### Schema

Details for configurations or fields of an actual test are defined below. These
fields can be defined globally or at different test levels.

| Field | Type | Description | Expand Action |
| - | - | - | - |
| function | str | Name of function to test | replace |
| environment | dict | Environment variables used by functions in a module | update |
| description | str or list | Additional details about the module, function, or test | append |
| resources | str or list | Resources or modules to use during test | append |
| patches | dict or list | Objects in a module to patch for tests | append |
| cast_arguments | dict or list | Convert function arguments to other data types | append |
| cast_output | dict or list | Convert function output to other data types | append |
| assertions | dict or list | Verifies the output of functions | append |
| tests | dict or list | Nested configurations that get expanded into individual tests | append |


## Resources

Resources represent the location of modules to import and use during tests. Resources can be defined globally when configuring the app, or at the module or test levels  under the key `resources` in a YAML file.

```yaml
configurations:
  resources:  # module level definition
  - resource_a.py

tests:
- resources:  # test level definition
  - resource_b.py
```

Resources are defined at various levels are aggregated into a single list for each test. Each resource listed is imported into the module to test, and is accessible from the module using dot notation based on the locations of the resource and module: `[module_name].[resource_name]`.

**Note**: Since resource modules are imported into the module to test, there is
a risk that attributes of the modules to test can be overwritten. To avoid this
it is important to pick unique names for resource folders or structure your
project in a way to avoid naming conflicts.



## Assertions

### Methods

We can define methods to compare expected and actual output from a function being tested. Methods should have the parameters `expected` and `output`, and return a **SimpleNamespace** object containing `expected`, `output`, `passed` (a boolean indicating whether the assertion passed or failed). Methods can also be reused between tests.

#### Example

Here we define a method for verifying that a function's output is of the correct type.

```python
from types import SimpleNamespace as sns
from typing import Any


def check_type(
  output: Any,
  expected: str,
) -> sns:
  passed = expected == type(output).__name__
  return sns(**locals())
```

### Schema

Assertions are defined in YAML test files under the key `assertions`, and a
single assertion has the following fields:

| Field | Type | Description | Default |
| - | - | - | - |
| method | str | Function or method used to verify the result of test | pass_through |
| expected | Any | The expected output of the function | null |
| field | str | Sets the output to a dot-delimited route to an attribute or key within the output. | null |
| cast_output | dict or list | Converts output or an attribute or key in the output before processing an assertion method | null |


And single test can have multiple assertions

```yaml
tests:
- assertions:
  - method: method_1
    expected: expected_1
    field: null
    cast_output: []
  - method: method_2
    expected: expected_2
    field: null
    cast_output: []
```

## Cast arguments and output

We can convert arguments passed to functions and output from functions to other data types. To do this we define cast objects and list them under the keys `cast_arguments` and `cast_output` for tests or `cast_output` for assertions.

### Schema

The following fields make up a cast object:

| Field | Description | Default |
| ----- | ----------- | ------- |
| method | Dot-delimited route to a function or object to cast a value to| null |
| field | Dot-delimited route to a field, attribute, or key of an object. When set the specified field of the object is cast | null |
| unpack | Boolean indicating whether to unpack an object when casting| False |

```yaml
tests:
- cast_arguments:
  - method: method_0
    field: field_0
    unpack: false
  - method: method_1
    field: field_1
    unpack: false
    ...
  assertions:
  - cast_output:
    - method: method_2
      field: field_2
      unpack: false
  ...
```

## Patches

We can patch objects in the module to test before running tests, and since tests are run in individual threads we can different patches for the same object without interference between tests.

### Methods

There are four patch methods:

- `value` - A value to return when the patched object is used.
- `callable` - A value to return when the patched object is called as function.
- `side_effect_list` - A list of values to call based off of the number of
times the object is called. Returns the item at index `n - 1` of the list for
the `nth` call of the object. Reverts to index 0 when number of calls exceeds
the length of the list.
- `side_effect_dict` - A dictionary of key, values for to patch an object
with. When the patched object is called with a key, the key's associated value
is returned

### Schema

Patches are defined at a list of objects in YAML test files under the key
`patches`, and a single patch object has the following fields:

| Field | Type | Description | Default |
| - | - | - | - |
| method | str | One of the four patch methods defined above | null |
| value | Any | The value the patched object should return when called or used | null
| name | str | The dot-delimited route to the object we wish to patch, in the module to test | null |


```yaml
tests:
- patches:
  - method: value
    value: value
    name: name
```


## Environment

For modules containing a global variable `CONFIG`, we can perform tests using different environment variables by the variables as adding key/value pairs under the key `set_environment` in YAML files. The environment variables are accessible from `CONFIG.environment.[name]`, where `[name]` is the name of the variable.

### Example

```yaml
configurations:
  set_environment:
    NAME_A: a
    NAME_C: c
  

tests:
- set_environment:
    NAME_A: A
    NAME_B: b
```


## Advanced example

You can find examples with more advanced usage of the app here: https://github.com/fjemi/yaml-testing-framework/tree/main/examples.


<br>
<a
  href="https://www.buymeacoffee.com/olufemijemo"
  target="_blank"
>
  <img
    src="https://cdn.buymeacoffee.com/buttons/default-orange.png"
    alt="Buy Me A Coffee"
    height="41"
    width="174"
  >
</a>

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/fjemi/yaml-testing-framework",
    "name": "yaml-testing-framework",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": null,
    "keywords": "pytest, yaml, testing",
    "author": "Olufemi Jemilohun",
    "author_email": "olufemi.jemilohun@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/0b/ed/cbbc58af53e163a4fcce119ce73711773c69573e1bb6ec0e87f80d8c1b27/yaml_testing_framework-0.0.5.tar.gz",
    "platform": null,
    "description": "# YAML Testing Framework\n\nA simple, low-code framework for unit testing in Python with tests are defined in YAML files.\n\n## Features\n\n- Standardized data/tests defined in YAML files\n- App functions as a pytest plugin\n- Support functional programming\n- Can be used to test synchronous and asynchronous logic\n- Easy to use API\n\n\n## Requirements\n\nPython 3.7+\n\n\n## Installation\n\n```bash\npip install yaml-testing-framework\n```\n\n\n## Example\n\n\nCreate the files\n- `test_entrypoint.py` - uses pytest to collect and run tests\n```python\nfrom types import SimpleNamespace as sns\n\nimport pytest\n\n\n@pytest.mark.parametrize(argnames='test', argvalues=pytest.yaml_tests)\ndef test_(test: sns) -> None:\n  assert test.expected == test.output\n```\n\n- `assertions.py` - contains logic for verifying the output from a function\n```python\nfrom types import SimpleNamespace as sns\nfrom typing import Any\n\n\n equals(expected: Any, output: Any) -> sns:\n  passed = expected == output\n  return sns(**locals())\n```\n\n- `add.py` - contains the function to test\n```python\n  def main(a: int, b: int) -> int:\n    return a + b\n```\n\n- `add_test.yaml` - contains tests for the function\n```yaml\nconfigurations:\n  resources:\n  - assertions.py\n\ntests:\n- function: main\n  tests:\n  - arguments:\n      a: 1\n      b: 1\n    assertions:\n    - method: assertions.equals\n      expected: 2\n  - arguments:\n      a: 1\n      b: 2\n    assertions:\n    - method: assertions.equals\n      expected: 3\n  - arguments:\n      a: 1\n      b: '1'\n    assertions:\n    - method: assertions.equals\n      field: 2\n```\n\nExecute the following command in your command line to run the tests.\n```bash\npytest --project-directory=add.py\n```\n\n\n## Configuration\n\nThe app can be configured within the pytest settings of a configuration file,\n such as a `pytest.ini`, or in the console when invoking pytest. The\n configurations are\n\n| Field | Type | Description | Default |\n| - | - | - | - |\n| project-directory | str | Location of a directory containing files or an an individual module or YAML file to test. | . |\n| exclude-files | str or list| A list of patterns. Exclude files from testing that match a specified pattern . | [] |\n| resources | str or list | The locations of modules to use as resources during tests | [] |\n| resources_folder_name | str | Name of folders containing resources to use for tests| _resources |\n| yaml-suffix | str | Suffix in the names of YAML files containing tests | _test |\n\n\n#### Configure pytest.ini\n\n\n```ini\n[pytest]\nproject-directory = .\nexclude_files =\n  matching\n  patterns\n  to\n  exclude\nresources =\n  resource_location_a\n  resource_location_b\nresources_folder_name = _resources\nyaml_suffix = _test\n```\n\n#### Configure command line command\n\n```console\npytest \\\n--project-directory=.app.py \\\n--exclude_files matching patterns to exclude \\\n--resources resource_location_a resource_location_b \\\n--resource-folder-name _resources \\\n--yaml-suffix _test\n```\n\n\n## YAML Test Files\n\nTests are defined in YAML files with the top level keys picked up by the app\nbeing:\n- `configurations` - Configurations to be used locally for each test in the YAML files\n- `tests` - Configurations used for multiple of individual tests.\n\n\n### Expanding and Collating Tests\n\nUsing the app we can define configurations for tests at various levels\n(configurations, tests, nested tests), expand those configurations to lower\nconfigurations, and collate individual tests. This allows us to reuse\nconfigurations and reduce the duplication of content across a YAML file. This is\nsimilar to [anchors](https://yaml.org/spec/1.2.2/#anchors-and-aliases) in YAML,\nwhich we can take advantage, along with the other features available in YAML.\n\n#### Example\n\nThis is an abstract example of the expanding/collating configurations done by\nthe app, where the configurations for tests are comprised of:\n- `config_a` - a list\n- `config_b` - an object\n- `config_c` - a string\n- `config_d` - null\n\nIn this example, we set these configurations at various levels, globally, tests,\nand nested tests; and the expanded/collated results are three individual tests\ncontaining various values for each configuration.\n\n```yaml\n# Defined\n\nconfigurations:\n  config_a:\n  - A\n  config_b:\n    b: B\n  config_c: C\n\n\ntests:\n- config_a:\n  - B\n- config_b:\n    c: C\n  tests:\n  - config_a:\n    - C\n    config_c: C0\n  - config_d: D\n    tests:\n    - config_a:\n      - B\n      config_b:\n        b: B0\n```\n\n```yaml\n# Expanded\n\ntests:\n- config_a: # test 1\n  - A\n  - B  # Appended item\n  config_b:\n    b: B\n  config_c: C\n  config_d: null  # Standard test config not defined\n- config_a: # test 2\n  - A\n  - C  # Appended item\n  config_b:\n    b: B\n    c: C  # Added key/value\n  config_c: C0  # Replace string\n  config_d: null\n- config_a: # test 3\n  - A\n  config_b:\n    b: B0  # Updated key/value pair\n    c: C\n  config_c: C\n  config_d: D  # Standard test config defined\n```\n\n\n### Schema\n\nDetails for configurations or fields of an actual test are defined below. These\nfields can be defined globally or at different test levels.\n\n| Field | Type | Description | Expand Action |\n| - | - | - | - |\n| function | str | Name of function to test | replace |\n| environment | dict | Environment variables used by functions in a module | update |\n| description | str or list | Additional details about the module, function, or test | append |\n| resources | str or list | Resources or modules to use during test | append |\n| patches | dict or list | Objects in a module to patch for tests | append |\n| cast_arguments | dict or list | Convert function arguments to other data types | append |\n| cast_output | dict or list | Convert function output to other data types | append |\n| assertions | dict or list | Verifies the output of functions | append |\n| tests | dict or list | Nested configurations that get expanded into individual tests | append |\n\n\n## Resources\n\nResources represent the location of modules to import and use during tests. Resources can be defined globally when configuring the app, or at the module or test levels  under the key `resources` in a YAML file.\n\n```yaml\nconfigurations:\n  resources:  # module level definition\n  - resource_a.py\n\ntests:\n- resources:  # test level definition\n  - resource_b.py\n```\n\nResources are defined at various levels are aggregated into a single list for each test. Each resource listed is imported into the module to test, and is accessible from the module using dot notation based on the locations of the resource and module: `[module_name].[resource_name]`.\n\n**Note**: Since resource modules are imported into the module to test, there is\na risk that attributes of the modules to test can be overwritten. To avoid this\nit is important to pick unique names for resource folders or structure your\nproject in a way to avoid naming conflicts.\n\n\n\n## Assertions\n\n### Methods\n\nWe can define methods to compare expected and actual output from a function being tested. Methods should have the parameters `expected` and `output`, and return a **SimpleNamespace** object containing `expected`, `output`, `passed` (a boolean indicating whether the assertion passed or failed). Methods can also be reused between tests.\n\n#### Example\n\nHere we define a method for verifying that a function's output is of the correct type.\n\n```python\nfrom types import SimpleNamespace as sns\nfrom typing import Any\n\n\ndef check_type(\n  output: Any,\n  expected: str,\n) -> sns:\n  passed = expected == type(output).__name__\n  return sns(**locals())\n```\n\n### Schema\n\nAssertions are defined in YAML test files under the key `assertions`, and a\nsingle assertion has the following fields:\n\n| Field | Type | Description | Default |\n| - | - | - | - |\n| method | str | Function or method used to verify the result of test | pass_through |\n| expected | Any | The expected output of the function | null |\n| field | str | Sets the output to a dot-delimited route to an attribute or key within the output. | null |\n| cast_output | dict or list | Converts output or an attribute or key in the output before processing an assertion method | null |\n\n\nAnd single test can have multiple assertions\n\n```yaml\ntests:\n- assertions:\n  - method: method_1\n    expected: expected_1\n    field: null\n    cast_output: []\n  - method: method_2\n    expected: expected_2\n    field: null\n    cast_output: []\n```\n\n## Cast arguments and output\n\nWe can convert arguments passed to functions and output from functions to other data types. To do this we define cast objects and list them under the keys `cast_arguments` and `cast_output` for tests or `cast_output` for assertions.\n\n### Schema\n\nThe following fields make up a cast object:\n\n| Field | Description | Default |\n| ----- | ----------- | ------- |\n| method | Dot-delimited route to a function or object to cast a value to| null |\n| field | Dot-delimited route to a field, attribute, or key of an object. When set the specified field of the object is cast | null |\n| unpack | Boolean indicating whether to unpack an object when casting| False |\n\n```yaml\ntests:\n- cast_arguments:\n  - method: method_0\n    field: field_0\n    unpack: false\n  - method: method_1\n    field: field_1\n    unpack: false\n    ...\n  assertions:\n  - cast_output:\n    - method: method_2\n      field: field_2\n      unpack: false\n  ...\n```\n\n## Patches\n\nWe can patch objects in the module to test before running tests, and since tests are run in individual threads we can different patches for the same object without interference between tests.\n\n### Methods\n\nThere are four patch methods:\n\n- `value` - A value to return when the patched object is used.\n- `callable` - A value to return when the patched object is called as function.\n- `side_effect_list` - A list of values to call based off of the number of\ntimes the object is called. Returns the item at index `n - 1` of the list for\nthe `nth` call of the object. Reverts to index 0 when number of calls exceeds\nthe length of the list.\n- `side_effect_dict` - A dictionary of key, values for to patch an object\nwith. When the patched object is called with a key, the key's associated value\nis returned\n\n### Schema\n\nPatches are defined at a list of objects in YAML test files under the key\n`patches`, and a single patch object has the following fields:\n\n| Field | Type | Description | Default |\n| - | - | - | - |\n| method | str | One of the four patch methods defined above | null |\n| value | Any | The value the patched object should return when called or used | null\n| name | str | The dot-delimited route to the object we wish to patch, in the module to test | null |\n\n\n```yaml\ntests:\n- patches:\n  - method: value\n    value: value\n    name: name\n```\n\n\n## Environment\n\nFor modules containing a global variable `CONFIG`, we can perform tests using different environment variables by the variables as adding key/value pairs under the key `set_environment` in YAML files. The environment variables are accessible from `CONFIG.environment.[name]`, where `[name]` is the name of the variable.\n\n### Example\n\n```yaml\nconfigurations:\n  set_environment:\n    NAME_A: a\n    NAME_C: c\n  \n\ntests:\n- set_environment:\n    NAME_A: A\n    NAME_B: b\n```\n\n\n## Advanced example\n\nYou can find examples with more advanced usage of the app here: https://github.com/fjemi/yaml-testing-framework/tree/main/examples.\n\n\n<br>\n<a\n  href=\"https://www.buymeacoffee.com/olufemijemo\"\n  target=\"_blank\"\n>\n  <img\n    src=\"https://cdn.buymeacoffee.com/buttons/default-orange.png\"\n    alt=\"Buy Me A Coffee\"\n    height=\"41\"\n    width=\"174\"\n  >\n</a>\n",
    "bugtrack_url": null,
    "license": "MIT license",
    "summary": "A testing framework where tests are defined in YAML files",
    "version": "0.0.5",
    "project_urls": {
        "Homepage": "https://github.com/fjemi/yaml-testing-framework"
    },
    "split_keywords": [
        "pytest",
        " yaml",
        " testing"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "85ba8f954f2c2854b65733cc3d27924022f59c9c3a55d6139dcf995377f63904",
                "md5": "3a27864d49de8389ab412af5ad792079",
                "sha256": "b3989e7160c673d17f876b046e4976c4d60fbf7b60ffaa226a4f6884e9dab3b1"
            },
            "downloads": -1,
            "filename": "yaml_testing_framework-0.0.5-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "3a27864d49de8389ab412af5ad792079",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 32944,
            "upload_time": "2024-04-26T21:45:18",
            "upload_time_iso_8601": "2024-04-26T21:45:18.236060Z",
            "url": "https://files.pythonhosted.org/packages/85/ba/8f954f2c2854b65733cc3d27924022f59c9c3a55d6139dcf995377f63904/yaml_testing_framework-0.0.5-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0bedcbbc58af53e163a4fcce119ce73711773c69573e1bb6ec0e87f80d8c1b27",
                "md5": "7960bb0bd35744d9d7bfff48f410f72c",
                "sha256": "ff97c534bacaa7018ae8c71e0e5076bf6eab3e1f83f653a59ddf85d1421f65d6"
            },
            "downloads": -1,
            "filename": "yaml_testing_framework-0.0.5.tar.gz",
            "has_sig": false,
            "md5_digest": "7960bb0bd35744d9d7bfff48f410f72c",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 14542,
            "upload_time": "2024-04-26T21:45:19",
            "upload_time_iso_8601": "2024-04-26T21:45:19.505008Z",
            "url": "https://files.pythonhosted.org/packages/0b/ed/cbbc58af53e163a4fcce119ce73711773c69573e1bb6ec0e87f80d8c1b27/yaml_testing_framework-0.0.5.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-26 21:45:19",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "fjemi",
    "github_project": "yaml-testing-framework",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "yaml-testing-framework"
}
        
Elapsed time: 0.24090s