Write Independent and Repeatable Tests - MATLAB & Simulink (original) (raw)
Main Content
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 thematlab.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 aTestClassTeardown
methods
block. - For setup code specified in a
TestMethodSetup
methods
block, specify the corresponding teardown code by using theaddTeardown
method in the same block or by specifying aTestMethodTeardown
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 ofTestCase
subclasses.
For example, in the AsymmetryExampleTest
class, thesetFormat
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 aTestMethodTeardown
methods
block. If you set the line spacing format to its default value (format loose
) and then run theAsymmetryExampleTest
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 theSymmetryExampleTest
class pass because theaddTeardown
method call runs the teardown code only after both the tests in the class run. Therefore, the format remains the same during testing.
Not Recommended | Recommended |
---|---|
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
andTestClassTeardown
methods
blocks. Call addTeardown
immediately before or after the original state change, without any other code in between that can throw an exception. UsingaddTeardown
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, theOriginalFormat
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 theaddTeardown
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 theMethodExampleTest
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 theMethodExampleTest
class can restore the display format to its value prior to the test run, and the test does not corrupt the display format.
Not Recommended | Recommended |
---|---|
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 theproperties
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 theHandleExampleTest
class. In contrast, the tests in theValueExampleTest
class are repeatable because each parameterized test creates its own figure by invoking a function handle and then closes the figure after testing.
Not Recommended | Recommended |
---|---|
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.