Main Content

Write Independent and Repeatable Tests

Unit tests must run independently, without unintentionally affecting one another. In addition, unit tests must be repeatable, which means that a running test must not affect subsequent reruns of the same test. When you create class-based tests by subclassing the matlab.unittest.TestCase class, use the following best practices for test independence and repeatability.

Specify Symmetric Setup and Teardown Actions

If your test class includes code for setting up the test environment, it must also include code to restore the environment to its original state by performing teardown actions symmetrically in the reverse order of their corresponding setup actions:

  • For setup code specified in a TestClassSetup methods block, specify the corresponding teardown code by using the addTeardown method in the same block or by specifying a TestClassTeardown methods block.

  • For setup code specified in a TestMethodSetup methods block, specify the corresponding teardown code by using the addTeardown method in the same block or by specifying a TestMethodTeardown methods block.

  • For setup code specified using a shared test fixture, do not add separate teardown code to your test class. The fixture automatically restores the environment when the testing framework tears it down. Shared test fixtures are specified using the SharedTestFixtures attribute of TestCase subclasses.

For example, in the AsymmetryExampleTest class, the setFormat method runs a single time before the tests because it is defined in a TestClassSetup methods block. However, the restoreFormat method runs after each test in the test class because it is defined in a TestMethodTeardown methods block. If you set the line spacing format to its default value (format loose) and then run the AsymmetryExampleTest class, one of the tests in the test class fails because the restoreFormat method restores the format to its original state after running the first test. In other words, the teardown code runs earlier than expected. In contrast, the tests in the SymmetryExampleTest class pass because the addTeardown method call runs the teardown code only after both the tests in the class run. Therefore, the format remains the same during testing.

Not RecommendedRecommended
classdef AsymmetryExampleTest < matlab.unittest.TestCase
    properties
        OriginalFormat
    end

    methods (TestClassSetup)
        function setFormat(testCase)
            testCase.OriginalFormat = format;
            format("compact")
        end
    end

    methods (TestMethodTeardown)
        function restoreFormat(testCase)
            format(testCase.OriginalFormat)
        end
    end

    methods (Test)
        function formatTest1(testCase)
            testCase.verifyEqual(format().LineSpacing,"compact")
        end

        function formatTest2(testCase)
            testCase.verifyEqual(format().LineSpacing,"compact")
        end
    end
end
classdef SymmetryExampleTest < matlab.unittest.TestCase
    methods (TestClassSetup)
        function setFormat(testCase)
            originalFormat = format;
            testCase.addTeardown(@format,originalFormat)
            format("compact")
        end
    end

    methods (Test)
        function formatTest1(testCase)
            testCase.verifyEqual(format().LineSpacing,"compact")
        end

        function formatTest2(testCase)
            testCase.verifyEqual(format().LineSpacing,"compact")
        end
    end
end

It is recommended that you perform all teardown actions from within the TestMethodSetup and TestClassSetup methods blocks using the addTeardown method instead of implementing corresponding teardown methods in the TestMethodTeardown and TestClassTeardown methods blocks. Call addTeardown immediately before or after the original state change, without any other code in between that can throw an exception. Using addTeardown allows the testing framework to execute the teardown code in the reverse order of the setup code and also creates exception-safe test content.

For more information about setup and teardown code in test classes, see Write Setup and Teardown Code Using Classes and Write Tests Using Shared Fixtures.

Access Global State Using Methods

To access a global state, such as the MATLAB® search path or output display format, use a method instead of a property. Specifying the state as a default property value is not recommended because accessing the property value at different points might not reflect changes to the state. MATLAB evaluates default property values a single time, when parsing the class.

For example, in the PropertyExampleTest class, the OriginalFormat property is set to the display format at class parse time, and its value remains the same in different runs of the test class. If you change the line spacing format after the first run, the property still points to the original line spacing format from class parse time. As a result, a second run of the test class corrupts the display format because the addTeardown method call always restores the format to its value at class parse time and not the value prior to the test run. In contrast, in the MethodExampleTest class, the current display format is correctly captured because the formatTest method runs every time the test class runs. Therefore, the addTeardown method call in the MethodExampleTest class can restore the display format to its value prior to the test run, and the test does not corrupt the display format.

Not RecommendedRecommended
classdef PropertyExampleTest < matlab.unittest.TestCase
    properties
        OriginalFormat = format
    end

    methods (Test)
        function formatTest(testCase)
            testCase.addTeardown(@format,testCase.OriginalFormat)
            format("compact")
            % Test code
        end
    end
end
classdef MethodExampleTest < matlab.unittest.TestCase
    methods (Test)
        function formatTest(testCase)
            originalFormat = format;
            testCase.addTeardown(@format,originalFormat)
            format("compact")
            % Test code
        end
    end
end

Use Value Objects as Test Parameter Values

Initialize your parameterization properties with value objects. Using handle objects, such as MATLAB graphics objects, as parameter values is not recommended. If you need to test handle objects in your parameterized test, consider constructing them indirectly by using function handles as parameter values and invoking those function handles in tests.

For example, the first time you run the HandleExampleTest class, the parameterized tests successfully run using the figures created in the properties block. After testing, each test closes the figure passed to it as a parameter value using a call to the addTeardown method. If you run the test class again, the tests fail because MATLAB evaluates default property values a single time, when parsing the class. In other words, there are no figures to test with in subsequent runs of the HandleExampleTest class. In contrast, the tests in the ValueExampleTest class are repeatable because each parameterized test creates its own figure by invoking a function handle and then closes the figure after testing.

Not RecommendedRecommended
classdef HandleExampleTest < matlab.unittest.TestCase
    properties (TestParameter)
        fig = {figure,uifigure}
    end

    methods (Test)
        function figureTest(testCase,fig)
            testCase.addTeardown(@close,fig)
            cp = fig.CurrentPoint;
            testCase.verifyEqual(cp,[0 0])
        end
    end
end
classdef ValueExampleTest < matlab.unittest.TestCase
    properties (TestParameter)
        fig = {@figure,@uifigure}
    end

    methods (Test)
        function figureTest(testCase,fig)
            f = fig();
            testCase.addTeardown(@close,f)
            cp = f.CurrentPoint;
            testCase.verifyEqual(cp,[0 0])
        end
    end
end

For more information about test parameters, see Use Parameters in Class-Based Tests.

See Also

Classes

Related Topics