Testing with DATAFLOW Runtime

by Marco Wuelser

 

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:

  1. The C++ dialect is limited to the IAR Extended Embedded C++ dialect (no RTTI, no exceptions).
  2. Framework libraries do not allocate dynamic memory and do not use STL.
  3. Utility functions are reentrant and thread-safe.
  4. 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

Go back