Skip to content

Unit testing suites

Jeff Krzywon edited this page Nov 29, 2022 · 9 revisions

Unit tests in SasView

Two unit test suites exist within SasView; one to test the calculations performed in the sascalc package and another to test the GUI behavior in the qtgui package. The sascalc unit tests are housed in the test package in the base directory of SasView. The GUI unit tests are housed within each subpackage of the qtgui package in a directories named UnitTesting.

A successful sasview build requires all GUI and calculation unit tests to pass in Ubuntu but only the calculation tests need to pass in Windows and MacOS. Any new GUI or calculation feature should come with unit tests and all new tests must pass. Any existing tests that are currently succeeding must continue to pass.

For more information on unit testing, please see the wikipedia page on unit tests.

Calculation tests

Unit tests for the sascalc package are held within the /test/ directory in the base directory of the sasview repository. Each subdirectory in the test directory houses tests specific to each subpackage in sascalc. There are two scripts available to run the unit tests, one that runs the entire test suite and another that runs tests from a specific test file.

  • To run all tests from the base sasview directory: python test\utest_sasview.py
  • To run a single test package from the base sasview directory: python test\run_one.py <test_package>. The test_package can be either an absolute path to a python file or a relative path from the location the script was run.

Adding Calculator tests

The test runner, utest_sasview.py, recursively steps through all subdirectories in the /tests/ directory and looks for any files that match the name pattern utest_<test_name>.py and runs them as python files. Individual files use the unittest package to run their respective unit tests.

To add a new test to an existing python file, simply add a new method starting with the word test in any class that inherits from unittest.TestCase. To add a new file with tests, import unittest, create a class that inherits from unittest.TestCase, add a method to the class that starts with the word test, and add if name == '__main__': unittest.main() at the end of the file. If you would like to run a series of actions before and/or after each test runs, the TestCase class has a setUp method that will run before each test and a tearDown method that will run after each test completes.

  • Importing the unittest package: import unittest
  • Adding a new class of unit tests: class <testClassName>(unittest.TestCase):
  • Adding a new test to an existing class: def test_<test_name>(self):
  • Ensure the test runner runs all tests in the file: if name == '__main__': unittest.main()
  • Run a series of actions before each test starts: def setUp(self):
  • Run a series of actions after each test completes: def tearDown(self):

GUI tests

Unit tests for the qtgui package are housed within each subpackage and should be specific to that subpackage, e.g. the fitting GUI tests are held in the Perspectives/Fitting/UnitTesting directory. All unit tests are run using pytest. Pytest recursively searches for files as specified in pytest.ini, found in the top-most level of the sasview repository.

Running unit tests:

  • To run all unit tests: python -m pytest -v src\sas\qtgui\
  • To run a subset of tests: python -m pytest -v src\sas\qtgui\<path>\<to><subset>

pytests.ini information:

  • norecursedirs: A space-delimited list of directories that should not be traversed when searching for unit tests. Useful for skipping subdirectories.
  • addopts: Other command line options to always include when running pytest.
  • python_files: A space-delimited list of file name formats that should be included for testing if found during the recursive search. * is used for a catch-all.
  • python_classes: A space-delimited list of class name formats that should be included for testing if found during the recursive search. * is used for a catch-all.

Adding GUI tests

Test files should be stored within the UnitTesting package within the respective package the test is meant for. GUI tests use the pytest package and have a different paradigm than the tests for sascalc. To add a new test to an existing python file, simply add a new method starting with the word test in any class that matches any pattern in pytest.ini python_classes. To add a new file with tests, give the file a name that matches any pattern in pytest.ini python_files, import pytest in that file, create a class that matches any pattern in pytest.ini python_classes, and add a method to the class. If you would like to create a common variable used by many tests, add the @pytest.fixture(autouse=True) decorator to a method, create the common variable in the method, yield <variable>, and pass the method name as an argument to the tests you would like to use the variable in. Use the assert statement for testing and equalities. pytest.approx() can be used for floating point comparisons.

  • Importing the pytest package: import pytest
  • Adding a new class of unit tests: class <testClassName>:
  • Adding a new test to an existing class: def <test_name>(self):
  • Use the assert statement for testing for True statements and pytest.approx() when comparing floating point values to one-another
  • Example of an entire test runner:
class MyClassTest:

    @pytest.fixture(autouse=True)
    def variable(self):
        class MyDummyClass:
            def __init__(self, number=2):
                self._var = number
            def integer(self):
                return int(self._var)
            def floating_point(self):
                return float(self._var)
            def string(self):
                return str(self._var)
            def do_something(self):
                pass
        # create a variable and assign something to it
        variable = MyDummyClass()
        # Perform a series of class operations that would be common
        variable.do_something()
        # Return the variable
        yield variable
        # Destroy the variable
        variable.close()

    # Use variable in another method
    def some_random_test(self, variable):
        # variable will now be a MyClass instance and the do_something() method has already run
        assert variable.integer == 2
        assert pytest.approx(variable.floating_point, 5) == 2.0
        assert pytest.string == "2.0"

Decorators for failing tests

Many GUI tests currently have the decorator @pytest.mark.skip(reason="Actual reason"). When the tests were ported from unitest to pytest, they failed for some reason. When adding new tests to a file, please make an attempt to correct any tests currently skipped in the same file.

Clone this wiki locally