Unit Testing
Introduction
In the tutorials so far we have introduced many features of Flightkit which you can use to produce spacecraft flight software, but we haven’t yet discussed mechanisms you can use to ensure the code you build using Flightkit is correct.
When making use of reusable software in space systems (where the consequences of failure can be painful!), it’s important that you, and others, can have confidence that the software will work as intended.
To help with this, the Flightkit Tooling has Unit Testing support built into it.
This tutorial will show you how to use this Unit Testing support when developing your own Component Types.
|
In this tutorial, you will:
|
Before You Begin
Guidance on the styles and conventions used in these tutorials is available here.
Unless otherwise directed, all commands in this tutorial must be run in the developing_components/UnitTesting workspace.
You must have Flightkit installed as described in the Getting Started Guide.
Navigate to the developing_components/UnitTesting workspace in a terminal and run hfk workspace prepare to ensure it is ready to use.
Unity and CMock
Flightkit uses two third-party tools to assist with developing Unit Tests:
Unity provides helper functions and a set of assertions for composing Unit Tests.
CMock is used to generate mocks. These are functions used to fulfil the requirements of the code being tested in place of the real functions, which are often unavailable when Unit Testing.
For example, when working on code which commands deployment of a solar array, it would be preferable to mock the function responsible for handling that command. CMock can be used to check that the function is called correctly without requiring the code, or the attached solar array hardware, to be present. With the correct selection of mocks, code which is dependent on embedded hardware can be tested in a low risk environment - like on your Linux development machine.
|
There is a wealth of information about Unit Testing available online. In this tutorial we assume a basic familiarity with the concepts involved, and focus on how to write and run tests when working with Flightkit. We will cover various Unity and CMock features we often use, and where relevant we will link to the documentation provided by those projects. |
Unit Tests in Flightkit
In Flightkit Component Types are the principle unit of reuse. This means that it is reasonable to expect a given Component Type to work correctly when instantiated within any Deployment Type.
To try and ensure that a Component Type will work correctly in new contexts, we typically write a set of tests which attempt to cover all of the interfaces of a Component Type.
This usually means:
-
For Service Providers, writing tests which make sure they implement each Operation of that Service correctly.
-
For Component Types with Actions and Parameters, writing tests which invoke each Action with various Arguments, and get and set each Parameter with various values, to make sure each case is handled correctly.
-
For Service Consumers, mocking the Service Operations which the Component Type issues. This gives us opportunity to make sure the correct Service Operations are issued at the correct times.
For complex Component Types there are more interfaces to consider, but they follow the same principles: exercise your inputs, and mock your requirements.
Testing SimpleImager
|
In the following sections you will create, implement and run a Unit Test to
demonstrate the If you haven’t yet completed that tutorial, we suggest you do so now. |
Creating the Test File
Generating the test files for a Component Type is very simple. Whenever a
Component Type is generated using the generate command, test files are
automatically created.
A complete copy of the SimpleImager Component Type is provided in this
tutorial’s library. This is the Component Type you will write the Unit Tests
against.
Generate a test file for the provided SimpleImager Component Type by
generating it.
hfk component-type generate SimpleImager
This creates a test file for the SimpleImager Component Type in the
following location:
library/component_types/SimpleImager/test/SimpleImager_Test.c
Open SimpleImager_Test.c.
As you can see, SimpleImager_Test.c already contains various data structures
and functions required to write some Unit Tests. There are TODO comments
indicating where you will need to make changes. There are also, at the bottom,
a number of autogenerated Unit Tests which the Flightkit Tooling has created.
From top to bottom, the file includes:
-
A set of structures which are used instead of the usual Component management mechanisms of a Deployment. Importantly,
gt_InitDatais a structure which can be used to provide different Initialisation Data values toSimpleImager, andgpt_SimpleImageris the global pointer to theSimpleImagerComponent Instance which must be passed when calling its public functions. -
Next is an empty section for placing helper functions. If you need to provide stubs to CMock, you can define them here.
-
Below that are the
setUp()andtearDown()functions. These functions are called by the Unity test framework before and after every test, respectively. By default these are generated such that they call the Component Type's local initialisation and finalisation functions. Any other required set up and tear down steps can be added here. -
The remaining functions are the tests themselves. The test functions for
SimpleImagerall begin withSimpleImager_Test_. Unity calls each of the functions with this prefix in order, from top to bottom. Flightkit Tooling generates some test functions for you automatically based on the Component Type'scomponentType.xml. The first one,SimpleImager_Test_initAndFini(), verifies the Component Type's initialisation and finalisation functions perform as expected. The rest verify that the Component Type's Actions and Parameter accessors perform as expected in certain scenarios.
|
Notice that the autogenerated tests are only partially complete. If you wish to
make use of them, you will have to complete their implementation according to
their It is also worth noting that they are not a comprehensive list. If your aim is to achieve full test coverage, you will have to supplement them with additional tests covering the scenarios the Flightkit Tooling cannot infer. The following sections of the tutorial demonstrate this process through the design and implementation of four new tests. |
Designing the Tests
As is the case with any software, it makes sense to think about how you’re going to approach writing your Unit Tests before you begin.
Tests should usually cover the broad functional jobs which a Component Type
fulfils. In the case of SimpleImager, we are mostly interested in testing the
code which uses FSS which you added in the
previous tutorial.
To test the settings reading and parsing functionality, you will implement unit tests which exercise the following specific cases:
-
Trying to capture an image before any settings are loaded.
-
Trying to read settings from a file which doesn’t exist.
-
Trying to read settings from a file which contains malformed settings. There are many possible ways the settings data can be malformed, but you will just write a single test covering one kind of error.
-
Reading settings from a well formed file, and checking that the settings are correctly presented in the
SimpleImagerParameters.
|
We will work through this list in order in the remaining sections of the tutorial, referring back to it as required. |
Initialisation Data
The first TODO in SimpleImager_Test.c suggests you set
Initialisation Data values for the Component Instance under test. However,
as you can see in its main header file -
library/component_types/SimpleImager/include/SimpleImager.h - SimpleImager
does not contain any Initialisation Data.
Update the comment to indicate that no Initialisation Data is required.
Show the updated TODO
/** The initialisation data for the component */
static SimpleImager_Init_t gt_InitData =
{
- /* TODO: Set initial values for initialisation data */
+ /* SimpleImager does not have any initialisation data */
};
When testing other Component Types you may wish to put invalid values in
gt_InitData in order to test validation. You may also want to provide
different Initialisation Data for different tests. This is why gt_InitData
is not const by default.
Test 1: Capture Without Settings
The first test outlined above needs to check that invoking captureImage before settings are loaded is handled correctly.
Rather than creating this test from scratch, you can instead use the
autogenerated SimpleImager_Test_captureImageSuccessful() function as a
starting point since it is not required for this tutorial.
The first step in implementing the new test is to select a name for it. It is important to ensure that test functions are named in a way which clearly describes the test being carried out. This allows readers to quickly understand the purpose of each test, whether they are reading the test functions themselves, or the test output produced when the tests are run. This test function will test capturing an image without loading settings, so its name should reflect that.
Rename the SimpleImager_Test_captureImageSuccessful() test function to have a
more appropriate name for the test you are implementing, and update the
description comment accordingly.
Make sure to preserve the SimpleImager_Test_ prefix in the name so that Unity
can run the test.
Show the updated test function name
-/** Test successfully invoking the captureImage action */
-void SimpleImager_Test_captureImageSuccessful(void)
+/** Test that captureImage fails if invoked before settings are loaded */
+void SimpleImager_Test_captureImageFailureWithoutSettings(void)
{
status_t t_Status; /* The current status */
/* TODO: Set up any expected external function calls for this action */
t_Status = SimpleImager_captureImage(gpt_SimpleImager);
TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
}
|
We’ll show you the code needed for this first test, but you might like to try and work it out on your own first. If so, look at how the code already generated in |
A good approach to writing Unit Tests is to first establish pre-conditions for the test, then carry out some steps which may or may not fail, and then examine the results of those steps.
The pre-condition for this test is that no settings are currently loaded into
the Component Instance. You can check whether this is the case using the
settingsLoaded parameter.
In the SimpleImager_Test_captureImageFailureWithoutSettings() function, get
the settingsLoaded Parameter and confirm it is false.
Show the added Parameter get and test assertion
/** Test that captureImage fails if invoked before settings are loaded */
void SimpleImager_Test_captureImageFailureWithoutSettings(void)
{
status_t t_Status; /* The current status */
+ bool b_SettingsLoaded; /* Whether the settings are loaded */
- /* TODO: Set up any expected external function calls for this action */
+ t_Status = SimpleImager_getSettingsLoaded(gpt_SimpleImager, &b_SettingsLoaded);
+ TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
+ TEST_ASSERT_FALSE(b_SettingsLoaded);
t_Status = SimpleImager_captureImage(gpt_SimpleImager);
TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
}
Finally, make sure that invoking the capture Action fails.
To do this, change the autogenerated assertion so it requires t_Status to
not equal STATUS_SUCCESS.
Show the updated t_Status assertion.
/** Test that captureImage fails if invoked before settings are loaded */
void SimpleImager_Test_captureImageFailureWithoutSettings(void)
{
status_t t_Status; /* The current status */
bool b_SettingsLoaded; /* Whether the settings are loaded */
t_Status = SimpleImager_getSettingsLoaded(gpt_SimpleImager, &b_SettingsLoaded);
TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
TEST_ASSERT_FALSE(b_SettingsLoaded);
t_Status = SimpleImager_captureImage(gpt_SimpleImager);
- TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
+ TEST_ASSERT_NOT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
}
Running Tests
Building and running tests is built into the Flightkit Tooling.
Build and run the SimpleImager Unit Tests by running the following command:
hfk component-type test SimpleImager
You should see output for each test executed, and a summary of how many failed.
Confirm that you see SimpleImager_Test_captureImageFailureWithoutSettings:PASS
in the output.
You should also see error output from SimpleImager reporting that
loadSettings must be invoked before captureImage. This indicates that the
SimpleImager_Test_captureImageFailureWithoutSettings() test function is
testing what you expect, and SimpleImager is behaving as it should.
Don’t worry about the other tests failing. In the following sections you will add the remainder of the tests outlined above.
Tests Using Mocks
We’ll now guide you through writing tests which mock the FSS functions which
SimpleImager calls. By mocking these functions, you do not need to connect an
FSS Provider to your Component Instance. This ensures that as little
effort as possible is expended testing or debugging code which is not part of
the Component Type under test.
Generating Mocks
CMock generates mocking macros and functions for use in your Unit Test. The macros can validate input arguments to mocked functions, populate output arguments, and generate specific return values. If these macros are not powerful enough, CMock allows you to provide callbacks to use in place of the mocked functions at runtime.
The Flightkit Tooling controls what mocks are generated for a particular Component Type under test. Specifically:
-
Functions which the Component Type uses to access required Services are mocked.
-
Functions defined by required Component Types are mocked.
-
Functions which the Component Type uses to store configuration are mocked.
In this tutorial you will only need to use mocks for the first group of functions: those which access required Services.
|
Component Types may require Modules to access additional functionality. These are buildable artefacts which can contain types and functions, amongst other things. We discuss Modules in more detail in a later tutorial. It is worth noting at this stage, however, that functions defined by required Modules are not mocked. |
Having built and run the unit tests earlier, the mocks for the SimpleImager
Component Type have already been generated.
Check that MockSimpleImager_FSS_package.h has been generated at the following
location:
generated/library/component_types/SimpleImager/test/mocks/container/MockSimpleImager_FSS_package.h
|
This file is under the |
CMock prefixes mocked file names with Mock. This is so that, at build time,
the mocked functions can be distinguished from the real functions. It also
means that in the test file you must use the correct file name in order to
access the generated mocks.
Check that container/MockSimpleImager_FSS_package.h has been included at the
top of SimpleImager_Test.c:
#include "container/MockSimpleImager_FSS_package.h"
Open the MockSimpleImager_FSS_package.h mock header.
This file contains a lot of content generated by CMock, and we won’t discuss it in detail. It is, however, worth looking at an example of the macros which are available.
Find the SimpleImager_FSS_openFileFs_ExpectAndReturn() macro.
This macro instructs CMock to:
-
Expect a call to
SimpleImager_FSS_openFileFs(), -
Check the input Arguments, and
-
Return with the given status.
SimpleImager_FSS_openFileFs() is the function which SimpleImager calls when
it needs to open a file. By mocking it, you can check that SimpleImager is
opening the correct file in the correct way.
|
We will not go further into the specifics of what CMock generates. In the following sections you will see how some of these mocks work by example. If you would like to read about them in more detail, documentation is available in the CMock Github repository. |
Test 2: Settings File Not Found
As outlined above, the next test to implement should check that loading settings from a file which doesn’t exist fails gracefully. For this, you will have to make use of mocks.
The loadSettings Action issues an openFile FSS Operation to its fs
service requirement by calling SimpleImager_FSS_openFileFs(). As mentioned
earlier, the Flightkit Tooling generates mock functions for all service requirements,
so you will need to tell CMock to expect this call within your test using
SimpleImager_FSS_openFileFs_ExpectAndReturn().
When setting up the expected call, you will need to either validate or ignore
each of the input Arguments depending on what you expect to be passed into it.
For this test, pt_Timeout and pt_SelectionID are not required, so can be
ignored. z_FileName can also be ignored as you require the function call to
report that the file was not found regardless of which file SimpleImager
tries to open.
As with before, rather than writing this test from scratch, you can just modify
the existing SimpleImager_Test_loadSettingsSuccessfulWithArg() test since that
test is not required for this tutorial.
Modify the SimpleImager_Test_loadSettingsSuccessfulWithArg() test’s
name and description so that it describes the test you are implementing. Again,
make sure to preserve the SimpleImager_Test_ prefix.
Show the updated test function name
-/** Test successfully invoking the loadSettings action with an argument */
-void SimpleImager_Test_loadSettingsSuccessfulWithArg(void)
+/** Test that loadSettings fails when opening a non-existent file */
+void SimpleImager_Test_loadSettingsFailureFileNotFound(void)
{
status_t t_Status; /* The current status */
uint8_t ru8_SettingsPath[SIMPLEIMAGER_LOAD_SETTINGS_MAX_ARG_SIZE] = { 0 };
/* TODO Set the ru8_SettingsPath argument */
/* TODO: Set up any expected external function calls for this action */
t_Status = SimpleImager_loadSettings(
gpt_SimpleImager,
&ru8_SettingsPath[0],
SIMPLEIMAGER_LOAD_SETTINGS_MAX_ARG_SIZE);
TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
}
With the test function renamed, you can now resolve the TODO comments such
that it performs as described above.
The first TODO can simply be deleted as the mock will ignore the file path
regardless of its value.
The second TODO requires you to use the
SimpleImager_FSS_openFileFs_ExpectAndReturn() function to indicate to CMock
that you expect the SimpleImager_FSS_openFileFs() mock to be called. You
will need to set the status returned by this function to
FSS_STATUS_FILE_NOT_FOUND since you want the mock to simulate the file not
being found.
Update the test to expect a call to SimpleImager_FSS_openFileFs().
Show the updated test.
/** Test that loadSettings fails when opening a non-existent file */
void SimpleImager_Test_loadSettingsFailureFileNotFound(void)
{
status_t t_Status; /* The current status */
uint8_t ru8_SettingsPath[SIMPLEIMAGER_LOAD_SETTINGS_MAX_ARG_SIZE] = { 0 };
- /* TODO Set the ru8_SettingsPath argument */
-
- /* TODO: Set up any expected external function calls for this action */
+ SimpleImager_FSS_openFileFs_ExpectAndReturn(
+ gpt_SimpleImager,
+ NULL,
+ NULL,
+ false,
+ false,
+ FSS_ACCESSMODE_READ_ONLY,
+ NULL,
+ FSS_STATUS_FILE_NOT_FOUND);
+ /* Ignore the values of pt_Timeout, z_FileName and pt_SelectionID */
+ SimpleImager_FSS_openFileFs_IgnoreArg_pt_Timeout();
+ SimpleImager_FSS_openFileFs_IgnoreArg_z_Filename();
+ SimpleImager_FSS_openFileFs_IgnoreArg_pt_SelectionID();
t_Status = SimpleImager_loadSettings(
gpt_SimpleImager,
&ru8_SettingsPath[0],
SIMPLEIMAGER_LOAD_SETTINGS_MAX_ARG_SIZE);
TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
}
With the TODO`s complete, the last step is to update the
`SimpleImager_loadSettings() status assertion to verify that
FSS_STATUS_FILE_NOT_FOUND is returned.
Update the SimpleImager_loadSettings() status assertion to validate against
the correct return status.
Show updated t_Status assertion.
/** Test that loadSettings fails when opening a non-existent file */
void SimpleImager_Test_loadSettingsFailureFileNotFound(void)
{
status_t t_Status; /* The current status */
uint8_t ru8_SettingsPath[SIMPLEIMAGER_LOAD_SETTINGS_MAX_ARG_SIZE] = { 0 };
SimpleImager_FSS_openFileFs_ExpectAndReturn(
gpt_SimpleImager,
NULL,
NULL,
false,
false,
FSS_ACCESSMODE_READ_ONLY,
NULL,
FSS_STATUS_FILE_NOT_FOUND);
/* Ignore the values of pt_Timeout, z_FileName and pt_SelectionID */
SimpleImager_FSS_openFileFs_IgnoreArg_pt_Timeout();
SimpleImager_FSS_openFileFs_IgnoreArg_z_Filename();
SimpleImager_FSS_openFileFs_IgnoreArg_pt_SelectionID();
t_Status = SimpleImager_loadSettings(
gpt_SimpleImager,
&ru8_SettingsPath[0],
SIMPLEIMAGER_LOAD_SETTINGS_MAX_ARG_SIZE);
- TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
+ TEST_ASSERT_EQUAL_UINT(FSS_STATUS_FILE_NOT_FOUND, t_Status);
}
|
Note that all mocks need to be configured before the Component Instance's code is called. That is why the word "expect" is used - you are telling CMock what it should expect to happen next. |
With the test complete, you can now build and run all tests again to check if the new test passes.
Build and run the SimpleImager tests and confirm that
SimpleImager_Test_loadSettingsFailureFileNotFound() passes.
As before, run the tests with the following command:
hfk component-type test SimpleImager
Test 3: Malformed Settings Data
The next test listed above is a little more complicated
to implement. For this test, you need to check that SimpleImager correctly
handles files which contain badly formatted settings.
To do this you will need to use CMock to report success from the openFile and
readFile FSS Operations, whilst returning data from readFile which is
malformed. You will also need to expect a call to closeFile once
SimpleImager is done reading the file.
Rather than modifying an existing test to implement this new test as you have done previously, you can instead write it from scratch.
The following steps will walk you through implementing this test from start to finish.
Add a new SimpleImager_Test_loadSettingsFailureMalformedData() test directly
below the SimpleImager_Test_loadSettingsFailureFileNotFound() test.
Use SimpleImager_FSS_openFileFs_ExpectAndReturn() to expect the
openFile FSS Operation.
Set the returned status to STATUS_SUCCESS since you want the mock to indicate
that the file was opened successfully.
Although the value of pt_SelectionID can be ignored, you will still need to
configure the mock to return a file handle to SimpleImager via this argument
so that SimpleImager can later use it to read from the file. You will need to
create a local Service_SelectionID_t variable to implement this behaviour.
Show the new test.
+/** Test that loadSettings fails when reading a malformed settings file */
+void SimpleImager_Test_loadSettingsFailureMalformedData(void)
+{
+ /* A dummy selection ID to use as a file handle */
+ Service_SelectionID_t t_File = (Service_SelectionID_t)99;
+
+ SimpleImager_FSS_openFileFs_ExpectAndReturn(
+ gpt_SimpleImager,
+ NULL,
+ NULL,
+ false,
+ false,
+ FSS_ACCESSMODE_READ_ONLY,
+ NULL,
+ STATUS_SUCCESS);
+ /* Ignore the values of pt_Timeout, z_FileName and pt_SelectionID */
+ SimpleImager_FSS_openFileFs_IgnoreArg_pt_Timeout();
+ SimpleImager_FSS_openFileFs_IgnoreArg_z_Filename();
+ SimpleImager_FSS_openFileFs_IgnoreArg_pt_SelectionID();
+ /* Return a dummy test value to SimpleImager through pt_SelectionID */
+ SimpleImager_FSS_openFileFs_ReturnThruPtr_pt_SelectionID(&t_File);
+}
Next, you need to expect SimpleImager to read from the file it has "opened".
This is achieved using the SimpleImager_FSS_readFileFs_ExpectAndReturn()
CMock macro.
Use SimpleImager_FSS_readFileFs_ExpectAndReturn() to expect the readFile
FSS Operation.
Set the t_SelectionID argument to the same value that you returned via the
pt_SelectionID argument in the SimpleImager_FSS_openFileFs_ExpectAndReturn
mock in the previous step. This will validate that SimpleImager is trying to
read from the same file it opened.
Use SimpleImager_FSS_readFileFs_ReturnMemThruPtr_pu8_Data() and
SimpleImager_FSS_readFileFs_ReturnThruPtr_pu32_DataLength() to return some
malformed data, and the length of that data.
Show the second mock setup added to the new test.
/** Test that loadSettings fails when reading a malformed settings file */
void SimpleImager_Test_loadSettingsFailureMalformedData(void)
{
/* A dummy selection ID to use as a file handle */
Service_SelectionID_t t_File = (Service_SelectionID_t)99;
+
+ /* Some malformed data */
+ uint8_t ru8_MalformedData[] = "Malformed Data";
+ uint32_t u32_MalformedDataLength = ARRAY_COUNT(ru8_MalformedData);
SimpleImager_FSS_openFileFs_ExpectAndReturn(
gpt_SimpleImager,
NULL,
NULL,
false,
false,
FSS_ACCESSMODE_READ_ONLY,
NULL,
STATUS_SUCCESS);
/* Ignore the values of pt_Timeout, z_FileName and pt_SelectionID */
SimpleImager_FSS_openFileFs_IgnoreArg_pt_Timeout();
SimpleImager_FSS_openFileFs_IgnoreArg_z_Filename();
SimpleImager_FSS_openFileFs_IgnoreArg_pt_SelectionID();
/* Return a dummy test value to SimpleImager through pt_SelectionID */
SimpleImager_FSS_openFileFs_ReturnThruPtr_pt_SelectionID(&t_File);
+
+ /* Expect SimpleImager to read from the opened file */
+ SimpleImager_FSS_readFileFs_ExpectAndReturn(
+ gpt_SimpleImager,
+ NULL,
+ t_File,
+ NULL,
+ NULL,
+ 0,
+ STATUS_SUCCESS);
+ /* Ignore the values of pt_Timeout, pu8_Data, pu32_DataLength and
+ * u32_ReadLength */
+ SimpleImager_FSS_readFileFs_IgnoreArg_pt_Timeout();
+ SimpleImager_FSS_readFileFs_IgnoreArg_pu8_Data();
+ SimpleImager_FSS_readFileFs_IgnoreArg_pu32_DataLength();
+ SimpleImager_FSS_readFileFs_IgnoreArg_u32_ReadLength();
+
+ /* Return the malformed data and its length through the right pointers */
+ SimpleImager_FSS_readFileFs_ReturnMemThruPtr_pu8_Data(
+ &ru8_MalformedData[0], u32_MalformedDataLength);
+ SimpleImager_FSS_readFileFs_ReturnThruPtr_pu32_DataLength(
+ &u32_MalformedDataLength);
}
|
The above additions ignore the length arguments passed to the To catch issues like this a callback would need to be written and provided to
CMock using the |
After SimpleImager reads the settings file, it must close it. You can tell
CMock to expect this call by using
SimpleImager_FSS_closeFileFs_ExpectAndReturn().
Expect SimpleImager_FSS_closeFileFs_ExpectAndReturn() to be called, making
sure SimpleImager closes the same file it opened.
Show the added closeFile mock setup.
/* Return the malformed data and its length through the right pointers */
SimpleImager_FSS_readFileFs_ReturnMemThruPtr_pu8_Data(
&ru8_MalformedData[0], u32_MalformedDataLength);
SimpleImager_FSS_readFileFs_ReturnThruPtr_pu32_DataLength(
&u32_MalformedDataLength);
+
+ /* Expect SimpleImager to then close the file */
+ SimpleImager_FSS_closeFileFs_ExpectAndReturn(
+ gpt_SimpleImager, NULL, t_File, STATUS_SUCCESS);
+ SimpleImager_FSS_closeFileFs_IgnoreArg_pt_Timeout();
}
You’ve now set up all the expected FSS Operations so all that’s left to do
in this test is to invoke the loadSettings Action and validate the returned
status.
Add a call to SimpleImager_loadSettings() and validate the returned status.
Show the added t_Status and z_FileName variables
/** Test that loadSettings fails when reading a malformed settings file */
void SimpleImager_Test_loadSettingsFailureMalformedData(void)
{
/* A dummy selection ID to use as a file handle */
Service_SelectionID_t t_File = (Service_SelectionID_t)99;
/* Some malformed data */
uint8_t ru8_MalformedData[] = "Malformed Data";
uint32_t u32_MalformedDataLength = ARRAY_COUNT(ru8_MalformedData);
+
+ status_t t_Status;
+ cstring_t z_FileName = "/this/file/is/malformed";
SimpleImager_FSS_openFileFs_ExpectAndReturn(
gpt_SimpleImager,
NULL,
Show the added Action invocation and t_Status assertion.
/* Expect SimpleImager to then close the file */
SimpleImager_FSS_closeFileFs_ExpectAndReturn(
gpt_SimpleImager, NULL, t_File, STATUS_SUCCESS);
SimpleImager_FSS_closeFileFs_IgnoreArg_pt_Timeout();
+
+ t_Status = SimpleImager_loadSettings(
+ gpt_SimpleImager, (const uint8_t *)z_FileName, strlen(z_FileName));
+ TEST_ASSERT_NOT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
}
The above code snippet uses the strlen() function, so you’ll need to include
the <string.h> header before building the tests.
Add <string.h> to the headers included at the top of SimpleImager_Test.c.
Show the added #include directive.
#include "container/MockSimpleImager_FSS_package.h"
#include "util/Util_Log.h"
+#include <string.h>
/*---------------------------------------------------------------------------*
* Global variables
*---------------------------------------------------------------------------*/
Build and run the SimpleImager tests and confirm that
SimpleImager_Test_loadSettingsFailureMalformedData() passes.
|
In the next section you’ll write the final test discussed
above. In doing so you’ll reuse the code in
|
Helper Functions
Up to this point the Unit Tests you have written for SimpleImager are each
fairly independent. There isn’t very much overlap in the contents of each test
function. This is good - it makes the tests maintainable. If something changes
in SimpleImager, you typically wouldn’t need to make the same changes in lots
of tests.
The final test you’re going to implement, however, is very similar to
SimpleImager_Test_loadSettingsFailureMalformedData(). Rather than giving
SimpleImager malformed settings data, you’ll give it valid settings data,
then check the parsed Parameter values.
To do this in a maintainable way, you will define a helper function for testing
the loadSettings Action with an arbitrary string.
Define loadSettingsFromString()
Under the Helper functions banner, define a loadSettingsFromString()
function using the body of the SimpleImager_Test_loadSettingsFailureMalformedData()
test.
Use the return value from the SimpleImager_loadSettings() function call as
the return value from loadSettingsFromString().
This allows tests which call loadSettingsFromString() to check the returned
status code themselves, depending on what they expect.
Rather than using local variables to define the settings data, give the
loadSettingsFromString() function a z_SettingsData argument.
Derive the length of the settings data to pass to the mocked FSS read using
strlen(z_SettingsData).
Make sure any comments and strings copied from
SimpleImager_Test_loadSettingsFailureMalformedData() are updated in the more
generic loadSettingsFromString() function.
These refactoring steps should result in a function similar to that given
below. If you’ve run into problems defining loadSettingsFromString() you can
paste the following function into SimpleImager_Test.c under the Helper
functions banner.
Show the definition of the loadSettingsFromString() helper function.
/**
* Test the loadSettings action using the given settings data
* @param z_SettingsData The settings data to load
* @return The status returned from the loadSettings action
*/
static status_t loadSettingsFromString
(
cstring_t z_SettingsData
)
{
/* A dummy selection ID to use as a file handle */
Service_SelectionID_t t_File = (Service_SelectionID_t)99;
/* Get the length of the settings string */
uint32_t u32_SettingsDataLength = strlen(z_SettingsData);
cstring_t z_FileName = "/dummy/file/path";
SimpleImager_FSS_openFileFs_ExpectAndReturn(
gpt_SimpleImager,
NULL,
NULL,
false,
false,
FSS_ACCESSMODE_READ_ONLY,
NULL,
STATUS_SUCCESS);
/* Ignore the values of pt_Timeout, z_FileName and pt_SelectionID */
SimpleImager_FSS_openFileFs_IgnoreArg_pt_Timeout();
SimpleImager_FSS_openFileFs_IgnoreArg_z_Filename();
SimpleImager_FSS_openFileFs_IgnoreArg_pt_SelectionID();
/* Return a dummy test value to SimpleImager through pt_SelectionID */
SimpleImager_FSS_openFileFs_ReturnThruPtr_pt_SelectionID(&t_File);
/* Expect SimpleImager to read from the opened file */
SimpleImager_FSS_readFileFs_ExpectAndReturn(
gpt_SimpleImager,
NULL,
t_File,
NULL,
NULL,
0,
STATUS_SUCCESS);
/* Ignore the values of pt_Timeout, pu8_Data, pu32_DataLength and
* u32_ReadLength */
SimpleImager_FSS_readFileFs_IgnoreArg_pt_Timeout();
SimpleImager_FSS_readFileFs_IgnoreArg_pu8_Data();
SimpleImager_FSS_readFileFs_IgnoreArg_pu32_DataLength();
SimpleImager_FSS_readFileFs_IgnoreArg_u32_ReadLength();
/* Return the data and its length through the right pointers */
SimpleImager_FSS_readFileFs_ReturnMemThruPtr_pu8_Data(
(const uint8_t *)z_SettingsData, u32_SettingsDataLength);
SimpleImager_FSS_readFileFs_ReturnThruPtr_pu32_DataLength(
&u32_SettingsDataLength);
/* Expect SimpleImager to then close the file */
SimpleImager_FSS_closeFileFs_ExpectAndReturn(
gpt_SimpleImager, NULL, t_File, STATUS_SUCCESS);
SimpleImager_FSS_closeFileFs_IgnoreArg_pt_Timeout();
return SimpleImager_loadSettings(
gpt_SimpleImager, (const uint8_t *)z_FileName, strlen(z_FileName));
}
Now use loadSettingsFromString() to implement the
SimpleImager_Test_loadSettingsFailureMalformedData() test.
Show the use of loadSettingsFromString() in SimpleImager_Test_loadSettingsFailureMalformedData().
/** Test that loadSettings fails when reading a malformed settings file */
void SimpleImager_Test_loadSettingsFailureMalformedData(void)
{
- /* A dummy selection ID to use as a file handle */
- Service_SelectionID_t t_File = (Service_SelectionID_t)99;
-
- /* Some malformed data */
- uint8_t ru8_MalformedData[] = "Malformed Data";
- uint32_t u32_MalformedDataLength = ARRAY_COUNT(ru8_MalformedData);
-
- status_t t_Status;
- cstring_t z_FileName = "/this/file/is/malformed";
-
- SimpleImager_FSS_openFileFs_ExpectAndReturn(
- gpt_SimpleImager,
- NULL,
- NULL,
- false,
- false,
- FSS_ACCESSMODE_READ_ONLY,
- NULL,
- STATUS_SUCCESS);
- /* Ignore the values of pt_Timeout, z_FileName and pt_SelectionID */
- SimpleImager_FSS_openFileFs_IgnoreArg_pt_Timeout();
- SimpleImager_FSS_openFileFs_IgnoreArg_z_Filename();
- SimpleImager_FSS_openFileFs_IgnoreArg_pt_SelectionID();
- /* Return a dummy test value to SimpleImager through pt_SelectionID */
- SimpleImager_FSS_openFileFs_ReturnThruPtr_pt_SelectionID(&t_File);
-
- /* Expect SimpleImager to read from the opened file */
- SimpleImager_FSS_readFileFs_ExpectAndReturn(
- gpt_SimpleImager,
- NULL,
- t_File,
- NULL,
- NULL,
- 0,
- STATUS_SUCCESS);
- /* Ignore the values of pt_Timeout, pu8_Data, pu32_DataLength and
- * u32_ReadLength */
- SimpleImager_FSS_readFileFs_IgnoreArg_pt_Timeout();
- SimpleImager_FSS_readFileFs_IgnoreArg_pu8_Data();
- SimpleImager_FSS_readFileFs_IgnoreArg_pu32_DataLength();
- SimpleImager_FSS_readFileFs_IgnoreArg_u32_ReadLength();
-
- /* Return the malformed data and its length through the right pointers */
- SimpleImager_FSS_readFileFs_ReturnMemThruPtr_pu8_Data(
- &ru8_MalformedData[0], u32_MalformedDataLength);
- SimpleImager_FSS_readFileFs_ReturnThruPtr_pu32_DataLength(
- &u32_MalformedDataLength);
-
- /* Expect SimpleImager to then close the file */
- SimpleImager_FSS_closeFileFs_ExpectAndReturn(
- gpt_SimpleImager, NULL, t_File, STATUS_SUCCESS);
- SimpleImager_FSS_closeFileFs_IgnoreArg_pt_Timeout();
-
- t_Status = SimpleImager_loadSettings(
- gpt_SimpleImager, (const uint8_t *)z_FileName, strlen(z_FileName));
- TEST_ASSERT_NOT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
+ /* Call the loadSettings action with malformed data */
+ status_t t_Status = loadSettingsFromString("Malformed data");
+ TEST_ASSERT_NOT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
}
At this stage it would be wise to build and run the revised
SimpleImager_Test_loadSettingsFailureMalformedData() test and make sure
nothing is broken.
Build and run the SimpleImager tests again and confirm that
SimpleImager_Test_loadSettingsFailureMalformedData() still passes.
|
Note that if, for some reason, the way in which You can now very easily use You’ll do exactly this in the next section. |
Test 4: loadSettings Successful
The final test listed above needs to make sure that,
when given valid settings data, SimpleImager parses and presents the values
correctly. You’ll make use of the loadSettingsFromString() function you just
added to do this.
Modify the autogenerated SimpleImager_Test_loadSettingsSuccessfulNoArg() test
to implement this new test, since it is not required for this tutorial.
Rename SimpleImager_Test_loadSettingsSuccessfulNoArg() to an appropriate
name for the test you are implementing, and update its description accordingly.
Update the body of the function to make use of the loadSettingsFromString()
helper function to invoke the loadSettings action with valid settings data.
Recall from the previous tutorial
that valid settings are of the form T=0;H=0;W=0;, where T, H and W can
be any 16-bit unsigned integer.
Show the updated test.
-/** Test successfully invoking the loadSettings action without an argument */
-void SimpleImager_Test_loadSettingsSuccessfulNoArg(void)
+/** Test that loadSettings succeeds when reading a valid settings file */
+void SimpleImager_Test_loadSettingsSuccessfulValidData(void)
{
- status_t t_Status; /* The current status */
-
- /* TODO: Set up any expected external function calls for this action */
-
- t_Status = SimpleImager_loadSettings(
- gpt_SimpleImager, NULL, 0);
+ status_t t_Status = loadSettingsFromString("T=145;H=480;W=640;");
TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
}
Build and run the SimpleImager tests and confirm that
SimpleImager_Test_loadSettingsSuccessfulValidData() currently passes.
To complete this test you still need to check that the correct values were
parsed by SimpleImager. To do this, you can check the values of the
exposureTime, imageHeight and imageWidth Parameters.
Add calls to SimpleImager_getExposureTime(), SimpleImager_getImageHeight(),
and SimpleImager_getImageWidth(). Use Unity assertions to check their
returned statuses and Parameter values.
Show the Parameter checks added to SimpleImager_Test_loadSettingsSuccessfulValidData().
/** Test that loadSettings succeeds when reading a valid settings file */
void SimpleImager_Test_loadSettingsSuccessfulValidData(void)
{
+ uint16_t u16_ExposureTime, u16_ImageHeight, u16_ImageWidth;
status_t t_Status = loadSettingsFromString("T=145;H=480;W=640;");
TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
+
+ t_Status = SimpleImager_getExposureTime(
+ gpt_SimpleImager, &u16_ExposureTime);
+ TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
+ TEST_ASSERT_EQUAL_UINT16(145, u16_ExposureTime);
+
+ t_Status = SimpleImager_getImageHeight(
+ gpt_SimpleImager, &u16_ImageHeight);
+ TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
+ TEST_ASSERT_EQUAL_UINT16(480, u16_ImageHeight);
+
+ t_Status = SimpleImager_getImageWidth(
+ gpt_SimpleImager, &u16_ImageWidth);
+ TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
+ TEST_ASSERT_EQUAL_UINT16(640, u16_ImageWidth);
}
Build and run the SimpleImager tests again and confirm that the completed
SimpleImager_Test_loadSettingsSuccessfulValidData() still passes.
This completes the set of tests outlined above.
Notice that some of the remaining autogenerated tests are still failing. This is
due to their implementation being incomplete as indicated by their TODO
comments. Completing their implementation is not within the scope of this
tutorial, however, if you wish to reinforce what you have learned in this
tutorial, feel free to give it a go. The solution can be seen below.
Show the updated implementation for the remaining tests.
/** Test successfully getting the SettingsLoaded parameter */
void SimpleImager_Test_getSettingsLoadedSuccessful(void)
{
status_t t_Status; /* The return status */
/* The value to test with */
bool b_Value = false;
- /* TODO: Set up any expected external function calls for this parameter */
-
t_Status = SimpleImager_getSettingsLoaded(
gpt_SimpleImager, &b_Value);
TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
+ TEST_ASSERT_FALSE(b_Value);
-
- /* TODO: test that the retrieved values match what is expected */
}
/** Test successfully getting the ExposureTime parameter */
void SimpleImager_Test_getExposureTimeSuccessful(void)
{
status_t t_Status; /* The return status */
/* The value to test with */
uint16_t u16_Value = 0;
+ uint16_t u16_ExpectedValue = 145;
- /* TODO: Set up any expected external function calls for this parameter */
+ t_Status = loadSettingsFromString("T=145;H=480;W=640;");
+ TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
t_Status = SimpleImager_getExposureTime(
gpt_SimpleImager, &u16_Value);
TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
+ TEST_ASSERT_EQUAL_UINT16(u16_ExpectedValue, u16_Value);
-
- /* TODO: test that the retrieved values match what is expected */
}
/** Test successfully getting the ImageHeight parameter */
void SimpleImager_Test_getImageHeightSuccessful(void)
{
status_t t_Status; /* The return status */
/* The value to test with */
uint16_t u16_Value = 0;
+ uint16_t u16_ExpectedValue = 480;
- /* TODO: Set up any expected external function calls for this parameter */
+ t_Status = loadSettingsFromString("T=145;H=480;W=640;");
+ TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
t_Status = SimpleImager_getImageHeight(
gpt_SimpleImager, &u16_Value);
TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
+ TEST_ASSERT_EQUAL_UINT16(u16_ExpectedValue, u16_Value);
-
- /* TODO: test that the retrieved values match what is expected */
}
/** Test successfully getting the ImageWidth parameter */
void SimpleImager_Test_getImageWidthSuccessful(void)
{
status_t t_Status; /* The return status */
/* The value to test with */
uint16_t u16_Value = 0;
+ uint16_t u16_ExpectedValue = 640;
- /* TODO: Set up any expected external function calls for this parameter */
+ t_Status = loadSettingsFromString("T=145;H=480;W=640;");
+ TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
t_Status = SimpleImager_getImageWidth(
gpt_SimpleImager, &u16_Value);
TEST_ASSERT_EQUAL_UINT(STATUS_SUCCESS, t_Status);
+ TEST_ASSERT_EQUAL_UINT16(u16_ExpectedValue, u16_Value);
-
- /* TODO: test that the retrieved values match what is expected */
}
|
Using the techniques you’ve learned in this tutorial you could write more tests
to further exercise Perhaps also consider how you might write Unit Tests for the |
Wrap Up
In this tutorial you have:
-
Learned about how Unit Testing is supported by Flightkit.
-
Learned how to use Flightkit Tooling to generate, build and run Unit Tests.
-
Implemented and run Unit Tests for the
SimpleImagerComponent Type, making use of mocking to fulfil its FSS requirement.
In the next tutorial, you’ll learn how to use Services to let your Component Types interact with hardware.
Click here to move on to that now.