Adding tests#

Tests are required for any contributions, and should have 100% statement coverage.

Tests are written with PyTest, and coverage is tested with coverage. These and other testing requirements are listed in the optional test dependencies in pyproject.toml.

Adding tests for a new module#

In order to make sure that any tests are run on installed versions of scikit-digital-health, the test directory is outside the src directory. Again, convenient base process testing classes are available to make setting up the basic testing easy and quick.

  1. Create a new directory for your new module under the test/ directory.

    • Add an empty __init__.py file to allow for relative importing.

    • Add the test file, eg test_preprocessing.py for this example.

    • Add a conftest.py file if desired:

      scikit-digital-health
      ├── src
      ├── test
      │   └──preprocessing
      │       ├── __init__.py
      │       ├── conftest.py
      │       └── test_preprocessing.py
      
  2. Inside test_preprocessing.py, import the base process testing class, set a few options, and the basic tests will be completed (when provided with sample and truth data!):

# test/preprocessing/test_preprocessing.py
import pytest

# import BaseProcessTester, and resolve_data_path - a useful utility for make sure
# tests run in multiple locations
from ..base_conftest import *

from skdh.preprocessing import Preprocessing

class TestPreprocessing(BaseProcessTester):
    @classmethod
    def setup_class(cls):
        super().setup_class()  # make sure to call the super method

        # override specific necessary attributes
        """
        resolve_data_path() takes 2 arguments:
        1. the file name
        2. the module name (ie the folder name this script is in)
        """
        cls.sample_data_file = resolve_data_path('preprocess_data.h5', 'preprocessing')
        cls.truth_data_file = resolve_data_path('preprocess_data.h5', 'preprocessing')
        cls.truth_suffix = None  # if not none, means that the truth data is in the path "Truth/{truth_suffix}" in the truth h5 file
        cls.truth_data_keys = [  # list of keys to check against truth data
            'accel'
        ]

        cls.process = Preprocessing(attr1=5, attr2=10.0)

    """
    Adding additional tests, for errors, edge cases, anything that can't be
    accomplished with the provided default structure can be easily added by
    defining new functions under this class.

    Note that if the default .test method is not working for your case, just overwrite it.
    """
    def test_error_attr1_string(self):
        with pytest.raises(ValueError):
            Preprocessing(attr1=5, attr2='string')
  • The above file would be all that is needed to test that the output accel matches the data contained in the truth file.

Creating the sample/truth data files#

  • The sample and truth data files are h5 files, with the below formats/keys:

sample.h5  # can have one or more of any of the below keys
├── time
├── accel
├── gyro
└── temperature
  • The BaseProcessTester class will automatically look for these keys. If you need to specify more/other keys, the list is stored in cls.sample_data_keys:

@classmethod
def setup_class(cls):
    ...
    # add more keys to look for
    cls.sample_data_keys.extend([
        'extra_key1',
        'extra_key2'
    ])
    ...
  • The truth data is very similar. For the preprocessing example, testing accel the h5 file would look like this:

truth.h5
├── Truth
│   └── accel
  • Alternatively, if the cls.truth_suffix is set to somethign else, (eg preproc) then the structure would be as follows:

truth.h5
├── Truth
│   └── preproc
│       └── accel
  • Finally, the sample and truth data can be in 1 file:

sample_truth.h5
├── Truth
│   └── accel
├── time
├── accel
├── gyro
└── temperature