Unit testing

Tests are setup using pythons default testing framework unittest. These tests are run each time new code is pushed to our Github repository using GitHub Actions. They can also be edited and run on your local machine as explained below.

In these explanations, we assume you have cloned a version of the qucat repository to your computer.

Running tests

Tests are located in the the tests directory. These can be run from the command line after navigating to the root directory of the qucat repository with the command

python -m unittest discover -v -s ./tests -p test_*.py

Each test file is dedicated to a specific part of the library, and is generally paired with a specific file of the source code, for example the file test/test_core.py tests the functionalities of the src/test_core.py file.

To run only run one of the test files, test_core.py for example, one can also run

python tests/test_core.py

Executing only a single test from a file is also possible by running for example

python tests/test_core.py SeriesRLC.test_frequency

Note

Some GUI tests have failed for a mysterious reason on certain operating systems, even thought the functionality they test works when tested manually.

Writing a basic test

Let us assume that a developer wishes to contribute a new module src/new.py which contains a function f which adds two numbers

...

def f(a, b):
    """
    Adds two numbers and returns the result.
    """
    return a + b

...

One should then create a test/test_new.py file which test if the function works as expected. This is one way of doing it

# Import python test framework
import unittest

import os
import sys

# Add the ./src/ folder to the system path
sys.path.append(os.path.join(os.path.join(os.path.dirname(os.path.dirname(__file__)),'src')))

# Import function to be tested
from new import f

class TestCase(unittest.TestCase):

    def test_f_function(self):
        # Check if f(1,2) is indeed equal to 3
        self.assertEqual(f(1,2),3)

if __name__ == '__main__':
    unittest.main()

For more details on the unittest framework, check this link.

Writing a test for a GUI functionality

Testing the GUI functionalities i.e. what happens if I click here? is also possible in an automatic way by simulating mouse motion, clicks or keystrokes events. These test are located in test/test_gui.py

Creating these tests is also done through the GUI, and we present here a short tutorial on how to do so.

Let’s assume we want to test moving a resistor by clicking, dragging then dropping.

The correct location to add this test is in the test/test_gui.py file, with the following code

class TestMovingComponentsAround(AutomaticTesting):

    def test_moving_resistor(self):
        self.launch_gui_testing(force_build=False,run_slower=False)

The first time we run this file, an empty GUI will open. Step 1: here we set the initial configuration for the test, in this example we create and place a resistor, then close the GUI. This circuit will be saved in the file tests/gui_testing_files/test_moving_resistor/initial_netlist.txt Step 2: a second GUI will open in the initial configuration, and we now perform the task we are testing. Our actions will be recorded in the file tests/gui_testing_files/test_moving_resistor/events.txt later repeated in an automatic way when running this test. In this example, we drag and drop the resistor to another location, then close the GUI. Upon closing the GUI, the final configuration of the circuit is recorded in the file tests/gui_testing_files/test_moving_resistor/final_netlist

The subsequent times we run this test no human intervention is necessary. A GUI will be opened in the initial configuration, a sequence of events will be triggered, and the final configuration of the circuit will be stored in the file tests/gui_testing_files/test_moving_resistor/final_after_events_netlist.txt and compared to the configuration created manually tests/gui_testing_files/test_moving_resistor/final_netlist. If the files are identical, the test passes.

The function AutomaticTesting.launch_gui_testing which implements these functionalities takes two arguments. By setting force_build = True, the test is run as if for the first time. By setting run_slower = True, the test is run very slowly so that one can observe what the automatically generated sequence of events is doing to the GUI. This enables easy debugging of the test.

Writing a test based on a tutorial

All tutorials featured on the website are jupyter notebooks located in the folder docs_src/source/tutorials and are also run as tests. These tests are located in test/test_tutorials.py.

If one adds a new notebook, for example new_tutorial.ipynb in the tutorials folder, one can test it by adding the following code to the test/test_tutorials.py script

class TestNewTutorial(TestTutorials):

    def test_if_it_runs(self):
        '''Runs the notebook new_tutorial.ipynb.
            If an error is encountered the test fails
        '''
        self.run_tutorial('new_tutorial.ipynb')

    def test_if_it_produces_the_right_answer(self):
        '''Runs the notebook new_tutorial.ipynb
            and stores all the variables defined in the notebook
            as a dictionary called variables.
            We then check if the variable called
            "name_of_variable" is equal to 10, 
            the test will fail if it isnt.
        '''
        variables = self.run_tutorial('new_tutorial.ipynb')
        x = variables['name_of_variable']
        expected_value_of_variable = 10
        self.assertEqual(x,expected_value_of_variable)