Skip to content

Testing

Summer Stapleton edited this page Aug 29, 2019 · 5 revisions

Running pytest within ALE

First ensure that you have a version of pytest installed. This can be easily installed into a conda environment (the environment used to build ale might be a good option) by activating the environment and running the command:

conda install -c conda-forge pytest

You may run the pytest command either on the tests/pytests directory to run tests from all files within the directory, or on an individual file within the directory with the following commands:

# This will run all ale pytests
pytest <path>/<to>/<repository>/tests/pytests

# This will run tests only within the test_util.py file
pytest <path>/<to>/<repository>/tests/pytests/test_util.py

Testing concrete drivers with pytest

In a concrete driver test, all functions or attributes declared in the concrete driver class itself should be tested. You don't need to worry about testing the inherited functions or attributes since they will be tested elsewhere. The python file itself should be named 'test_<file_to_be_tested>.py' (i.e. test_cassini_drivers.py). Each individual test should be named 'test_<attribute_name>' (i.e. test_instrument_id).

In order to avoid declaring a new driver in each test, drivers should be passed into the tests as fixtures like so:

@pytest.fixture
def driver():
    return CassiniIssPds3LabelNaifSpiceDriver("")

def test_instrument_id(driver):
    assert ...

Since we are not reading from a label at any point during this test, we can just pass in an empty string into the driver.

Mocking the spice module

We also have a SimpleSpice class and a get_mockkernels function to avoid having to retrieve information from spice kernels. The following lines of code will create a mock the spice module to avoid the use of spice kernels.

from conftest import SimpleSpice, get_mockkernels

simplespice = SimpleSpice()

data_naif.spice = simplespice
<file_to_be_tested_>.spice = simplespice
label_pds3.spice = simplespice

from ale.drivers.<file_to_be_tested_> import <Driver_to_be_tested>
<Driver_to_be_tested>.metakernel = get_mockkernels

Using propertymock and patch to avoid reading from labels

A concrete driver should be using the various mix-ins to extract the information for its attributes whenever possible. So instead of having a label that we test on, we can use property mock and/or patch to change returned values so that we don't have to read from anything during these tests.

If we need to test multiple return values of the same mix-in property in the same test, the following format works best:

def test_target_name(driver):
    with patch('ale.base.label_pds3.Pds3Label.target_name', new_callable=PropertyMock) as mock_target_name:
        mock_target_name.return_value = '4 VESTA'
        assert driver.target_name == 'VESTA'
        mock_target_name.return_value = 'VESTA'
        assert driver.target_name == 'VESTA'

If only one return value for any give in mix-in is needed in a test, we can use the following format in order to improve readability:

@patch('ale.base.label_pds3.Pds3Label.instrument_host_id', 'DAWN')
def test_spacecraft_name(driver):
    assert driver.spacecraft_name == 'DAWN'

The previous format also works for mocking the return value of several different properties (see example below). This is much better than the alternative of nesting several different with branches.

@patch('ale.base.label_pds3.Pds3Label.instrument_id', 1)
@patch('ale.base.label_pds3.Pds3Label.filter_number', 2)
def test_instrument_id(driver):
    assert driver.instrument_id == 'DAWN_1_FILTER_2'

In the case that the driver being tested must read directly from the label, we can use patch.dict to mock certain values in the label attribute like so:

with patch.dict(driver.label, {'INSTRUMENT_ID':'123'}) as f:
    assert ...