Testing with DATAFLOW Runtime
This Guide Article has been written for Version 2.1 of the DATAFLOW Software. For Previous Releases use the version selection in the navigation bar at the top of this page.
Testing
Runtime Testing Helper
Before the following chapters describes how to implement UnitTest with concrete examples, an overview is given about the provided testing helpers in DATAFLOW Runtime.
All the helpers are provided in Imt.Base.Dff.UnitTest.Helper project.
File Name |
Description |
Reference |
ActivePartHelper.h |
Is used to send protocols to ActivePart and check output as black box view. |
https://dffstudio.imt.ch/doc/2.7/classunit_test_helper_1_1_active_part_helper.html |
TestAssertActionHandler.h |
Is used for assertion checks in code. |
https://dffstudio.imt.ch/doc/2.7/classunit_test_helper_1_1_test_assert_action_handler.html |
TestUtil.h |
Provides utility methods to compare and assert complex types like arrays. |
https://dffstudio.imt.ch/doc/2.7/classunit_test_helper_1_1_test_util.html |
MS Unit Testing Framework
Setup VisualStudio 2015
This Tech Note assumes that unit tests are written using the Microsoft Unit Testing Framework in Visual Studio 2015. This means that the unit tests are written in managed C++, and the SUT (software under test) is built and linked as a static library.
Note The DATAFLOW Designer provides a code generator that can generate unit tests for all generated classes, such as protocols, active parts and active containers [2]. |
In addition to this or if no code generation will be used, the following chapters explain how the unit tests are written by hand.
To setup a UnitTest project in VisualStudio 2015 ensure the following “Preprocessor Definitions” are defined:
Visual Studio UnitTest project setup
Furthermore Imt.Base.Dff.Runtime.Mock, Imt.Base.Dff.UnitTest.Helper project and given HAL and HAL Mock project must be also included in the Visual Studio solution. When binary variation is used the test must be linked against <package_name>/lib/TestLib. See chapter 4.1 for detailed information about TestLib files.
Unit Test
Protocol
To unit test a protocol class, the following will be tested:
- Constructors
- Serialization and Deserialization
- Setters and Getters
Test Class Example:
// Imt.Base includes.
#include <Imt.Base.Core.Platform/Platform.h>
#include <Imt.Base.Dff.ActiveParts.Test/EventArgsSerializer.h>
#include <Imt.Base.Core.Diagnostics/Diagnostics.h>
// Class under test.
#include "Protocol/ButtonStateProtocol.h"
using namespace System;
using namespace System::Text;
using namespace System::Collections::Generic;
using namespace Microsoft::VisualStudio::TestTools::UnitTesting;
namespace unitTestDffGenerator {
constexpr bool defaultIsPressed{ false };
constexpr uint32_t defaultButtonId{ 0U };
constexpr bool expectedIsPressed{ true };
constexpr uint32_t expectedButtonId{ 1840700269U };
/**
* Tests the ButtonStateProtocol.
*/
[TestClass]
public ref class TestButtonStateProtocol {
public:
[TestInitialize()]
void setUp() {
m_pTestMessage = new ButtonStateProtocol();
m_pTestMessage->setIsPressed(expectedIsPressed);
m_pTestMessage->setButtonId(expectedButtonId);
}
[TestCleanup()]
void tearDown() {
delete m_pTestMessage;
m_pTestMessage = NULL;
}
// Test cases (see below)
private:
void assertIsAsExpected(const ButtonStateProtocol& message) {
// isPressed
Assert::AreEqual(expectedIsPressed, message.getIsPressed());
// buttonId
Assert::AreEqual(expectedButtonId, message.getButtonId());
}
void assertIsDefault(const ButtonStateProtocol& message) {
// isPressed
Assert::AreEqual(defaultIsPressed, message.getIsPressed());
// buttonId
Assert::AreEqual(defaultButtonId, message.getButtonId());
}
ButtonStateProtocol* m_pTestMessage;
};
} // namespace unitTestDffGenerator
Test Case Examples:
Check all fields when a message is created using the default constructor:
[TestMethod]
void testButtonStateProtocol_defaultConstructor() {
// Act
ButtonStateProtocol message;
// Assert
assertIsDefault(message);
}
Serialize and deserialize the message and compare to the initial message.
[TestMethod]
void testButtonStateProtocol_serialize_deserialize() {
// Arrange
ButtonStateProtocol inputMessage(*m_pTestMessage);
constexpr size_t bufferSize = sizeof(ButtonStateProtocol);
uint8_t buffer[bufferSize];
// Act
Serializer serializer(buffer, bufferSize);
inputMessage.serialize(serializer);
Deserializer deserializer(buffer, bufferSize);
ButtonStateProtocol deserializedMessage(deserializer);
// Assert
assertIsAsExpected(deserializedMessage);
}
Test getter and setter of a field.
[TestMethod]
void testButtonStateProtocol_field_isPressed() {
// Arrange
ButtonStateProtocol message1;
ButtonStateProtocol message2;
bool expected1 = false;
bool expected2 = true;
// Act
message1.setIsPressed(expected1);
message2.setIsPressed(expected2);
// Assert
Assert::AreEqual(expected1, message1.getIsPressed());
Assert::AreEqual(expected2, message2.getIsPressed());
}
Active Part
To unit test an active part class, the following will be tested:
- Behavior
- Constructor
- Priority
Note The TestClass should always be inherited from MockTestBase when TestObject interacts with runtime. MockTestBase enables MemoryLeak detection and initializes runtime mock.
See https://dffstudio.imt.ch/doc/2.7/classunit_test_helper_1_1_mock_test_base.html |
Test Class Example:
// Imt.Base includes.
#include <Imt.Base.Core.Platform/Platform.h>
#include <Imt.Base.Core.Diagnostics/Diagnostics.h>
#include <Imt.Base.Dff.ActiveParts.Test/ChannelMockIn.h>
#include <Imt.Base.Dff.ActiveParts.Test/ChannelMockOut.h>
#include <Imt.Base.Dff.ActiveParts.Test/ChannelConnectionVerifier.h>
// Helper includes.
#include <Imt.Base.Dff.UnitTest.Helper/ActivePartHelper.h>
#include <Imt.Base.Dff.UnitTest.Helper/MockTestBase.h>
// Class under test.
#include "ExampleMain/Output/Led/LedAP.h"
using namespace System;
using namespace System::Text;
using namespace System::Collections::Generic;
using namespace Microsoft::VisualStudio::TestTools::UnitTesting;
namespace unitTestDffGenerator {
/**
* Tests the LedAP.
*/
[TestClass]
public ref class TestLedAP : MockTestBase {
public:
[TestInitialize()]
void setUp() {
m_pTestObject = new LedAP();
m_pChannelIn = new ChannelMockIn();
// Initialize.
m_pTestObject->initialize();
// Connect ports.
m_pChannelIn->connectPortIn(m_pTestObject->PortIn);
// Start.
m_pTestObject->start();
}
[TestCleanup()]
void tearDown() {
delete m_pChannelIn;
m_pChannelIn = NULL;
delete m_pTestObject;
m_pTestObject = NULL;
}
// Test cases (see below)
private:
LedAP* m_pTestObject;
ChannelMockIn* m_pChannelIn;
};
} // namespace unitTestDffGenerator
Test Case Examples:
Test active part priority.
[TestMethod]
void testLedAP_Priority() {
Assert::IsTrue(RuntimePriority::Prio_18 == m_pTestObject->getPriority());
}
Schedule timer events (1ms ticks) and check if expected message has been sent.
[TestMethod]
void testVoltageMonitor_heartbeat() {
// Arrange
HeartbeatProtocol expectedMessage;
expectedMessage.setActivePartId(ActivePartIdentifierEnum::VOLTAGE_MONITOR);
// Act
RuntimeMock::getSingle().scheduleEvents(DeviceConstants::HEARTBEAT_TIMER);
// Assert
Assert::AreEqual<uint32_t>(1, m_pChannelOutHeartbeat->numberOfDataItemsReceived());
ActivePartHelper::assertMessage(m_pChannelOutHeartbeat, 0,
ProtocolIdentifier::HEARTBEAT, expectedMessage);
}
Send message to active part and check if expected message has been sent.
[TestMethod]
void testMonitor_persistencyRevieved_onSensorData() {
// Arrange
auto sensorData = SensorDataProtocol(1234, 4, 4321);
// Act
ActivePartHelper::sendMessage(m_pChannelIn, ProtocolIdentifier::SENSOR_DATA,
&sensorData);
// Assert
Assert::AreEqual<uint32_t>(1, m_pChannelOutData->numberOfDataItemsReceived());
ActivePartHelper::assertMessage(m_pChannelOutData, 0,
ProtocolIdentifier::SENSOR_DATA, expectedMessage);
}
Send message to active part and check if GPIO has been set to high.
[TestMethod]
void testBasicSafety_off_onBasicSafetyEventPump_basicSafety() {
// Arrange
Assert::AreEqual<uint32_t>(LOW, BasicSafety);
// Act
auto message = BasicSafetyProtocol(BasicSafetySourceEnum::PUMP, true);
ActivePartHelper::sendMessage(m_pChannelIn, ProtocolIdentifier::BASIC_SAFETY,
&message);
// Assert
Assert::AreEqual<uint32_t>(HIGH, BasicSafety);
}
Active Container
To unit test an active container class, the following will be tested:
- Constructor
- Initialization and startup of all components
- Connection of all channels
- Delegation of external ports to component ports
Note The TestClass should always be inherited from MockTestBase when TestObject interacts with runtime. MockTestBase enables MemoryLeak detection and initializes runtime mock.
See https://dffstudio.imt.ch/doc/2.7/classunit_test_helper_1_1_mock_test_base.html |
Test Class Example:
// Imt.Base includes.
#include <Imt.Base.Core.Platform/Platform.h>
#include <Imt.Base.Core.Diagnostics/Diagnostics.h>
#include <Imt.Base.Dff.ActiveParts.Test/ChannelMockIn.h>
#include <Imt.Base.Dff.ActiveParts.Test/ChannelMockOut.h>
#include <Imt.Base.Dff.ActiveParts.Test/ChannelConnectionVerifier.h>
// Helper includes.
#include <Imt.Base.Dff.UnitTest.Helper/ActivePartHelper.h>
#include <Imt.Base.Dff.UnitTest.Helper/MockTestBase.h>
// Class under test.
#include "ExampleMain/Output/OutputAPC.h"
using namespace System;
using namespace System::Text;
using namespace System::Collections::Generic;
using namespace Microsoft::VisualStudio::TestTools::UnitTesting;
namespace unitTestDffGenerator {
/**
* Test of the SW UNIT OutputAPC
*/
[TestClass]
public ref class TestOutputAPC : MockTestBase {
public:
[TestInitialize()]
void setUp() {
m_pTestObject = new OutputAPC();
m_pTestObject->initialize();
m_pTestObject->start();
}
[TestCleanup()]
void tearDown() {
delete m_pTestObject;
m_pTestObject = NULL;
}
private:
OutputAPC* m_pTestObject;
};
} // namespace unitTestDffGenerator
Example Test Cases:
Test that all child components are initialized.
[TestMethod]
void testOutputAPC_IsInitialized() {
// Check Activepart Container.
Assert::IsTrue(m_pTestObject->isInitialized());
// Check all included Activeparts.
Assert::IsTrue(m_pTestObject->Filter.isInitialized());
Assert::IsTrue(m_pTestObject->Led1.isInitialized());
Assert::IsTrue(m_pTestObject->Led2.isInitialized());
}
Test that all child components are started.
[TestMethod]
void testOutputAPC_IsStarted() {
// Check Activepart Container.
Assert::IsTrue(m_pTestObject->isStarted());
// Check all included Activeparts.
Assert::IsTrue(m_pTestObject->Filter.isStarted());
Assert::IsTrue(m_pTestObject->Led1.isStarted());
Assert::IsTrue(m_pTestObject->Led2.isStarted());
}
Test that all channels are connected to the correct ports.
[TestMethod]
void testOutputAPC_InternalConnections_Static() {
// Channel OneToAny ChanFilterLedToAny
Assert::IsTrue(ChannelConnectionVerifier::testChannelOneToAny<2>
(m_pTestObject->Filter.PortOutLed, m_pTestObject->Led1.PortIn));
Assert::IsTrue(ChannelConnectionVerifier::testChannelOneToAny<2>
(m_pTestObject->Filter.PortOutLed, m_pTestObject->Led2.PortIn));
}
Test that all external ports are set to correct child ports.
[TestMethod]
void testOutputAPC_ExternalPorts_Static() {
// InConnector PortInPortInLed
Assert::IsTrue(&m_pTestObject->PortInPortInLed == &m_pTestObject->Filter.PortIn);
}
Reference
This chapter contains additional information about the framework and the used notations.
Design Constraints
The whole DATAFLOW Framework is implemented under the following considerations:
- The C++ dialect is limited to the IAR Extended Embedded C++ dialect (no RTTI, no exceptions).
- Framework libraries do not allocate dynamic memory and do not use STL.
- Utility functions are reentrant and thread-safe.
- Framework and utility libraries return errors instead of using asserts.
Quote from the IAR C++ Development guide:
C++, a modern object-oriented programming language with a full-featured library that is well suited for modular programming. Any of these standards can be used:
- Standard C++: can be used with different levels of support for exceptions and runtime type information (RTTI).
- Embedded C++ (EC++): a subset of the C++ programming standard, which is intended for embedded systems programming. It is defined by an industry consortium, the Embedded C++ Technical committee.
- IAR Extended Embedded C++ (EEC++): EC++ with additional features such as full template support, multiple inheritance, namespace support, the new cast operators, as well as the Standard Template Library (STL).
DATAFLOW Notation
This chapter contains a notation for Active Parts, Containers and Channels. This is the notation used in DATAFLOW Designer. We suggest that this notation is also used in projects that are only using the runtime, but other notations such as UML can be used if the project or company requires it.
Software Components |
||
Ports |
||
Hardware Interfaces |
||
Other Items |
||
Channels |
Items
Item |
Type |
Responsibility |
Active Part |
Component |
Represents an Active Part |
Active Container |
Component |
A collection of Active Parts. It is not an Active Part itself; it only delegates the PortIn and PortOut of the containing Active Parts. |
Interrupt |
Component |
Special version of an Active Part where the PortIn is represented by an interrupt vector. (Interrupt Handler) |
PortIn |
Port |
Input Port where the data is received |
PortOut |
Port |
Output port where the data is sent |
Interrupt Handler |
Other |
Interrupt handler which is linked into the vector table |
Timer |
Other |
Recuring or single timer. |
Channel OneToOne |
Channel |
Connects 1 PortOut with 1 PortIn |
Channel OneToAny |
Channel |
Connects 1 PortOut with 2..n PortIn It behaves like a broadcast, where all connected PortIns receive the same data. |
Hardware Input |
Hardware Interface |
Many different types of Hardware elements |
Hardware Output |
Hardware Interface |
Many different types of Hardware elements |
Required Module: DATAFLOW Runtime
This Article has been written based on V2.1.1 of the DATAFLOW software.
Latest update 2023-05-31 by WUM.
Comments
0 comments
Please sign in to leave a comment.