Recommended Component Test Frameworks
The following component test frameworks are recommended but not mandatory.
GoogleTest
Used for example in the Control Software stream, e.g. for Next Generation control development. The format is XML because there is an own-developed test framework on top of the GoogleTest framework that processes an XML-format file as input.
Example of a component test:
<TestCases>
<!-- Each TestCase needs a unique Id number -->
<TestCase Id="1"> <!-- Description if the test case. -->
<Documentation>
<Description>
Configure an empty Ac800m configuration service and the AlarmAndEvent service lib with condition
files at startup, then do a reconfiguration. Alarm conditions are activated and deactivated
cyclically from code.
</Description>
<Setup>Correct configuration files are used to configure Ac800m and AlarmAndEvent.</Setup>
<ExpectedResult>
Ac800m and AlarmAndEvent register and unregister the alarm conditions correctly. The alarm conditions
are toggled between activated and deactivated.
</ExpectedResult>
</Documentation>
<!-- START OF CONFIGURATION -->
<!-- Operation: AddControlService: add service to start list -->
<TestStep Operation="AddControlService"> <!-- Add the service for Execution service -->
<Parameter Name="InstanceName" Value="AC800MExecService" />
<Parameter Name="DefaultConfig" Value="0001_AEConditions/AEConditions_ConfigCollection.xml" />
</TestStep>
<!-- Operation: Start: start the platform with the given start list. -->
<TestStep Operation="Start" />
<!-- START OF SUCCESSFUL RECONFIGURATION -->
<TestStep Operation="HighlevelConfig">
<Parameter Name="Operation" Value="Connect" />
<Parameter Name="OPCUAurl" Value="[ConfigMgrDefaultUrl]" />
</TestStep>
<TestStep Operation="HighlevelConfig">
<Parameter Name="Operation" Value="OpenConfigCollection" />
<Parameter Name="ClientNumber" Value="0" />
<Parameter Name="ReceiverID" Value="AC800MExecService" />
</TestStep>
<TestStep Operation="HighlevelConfig">
<Parameter Name="Operation" Value="TransferCollectionFile" />
<Parameter Name="ClientNumber" Value="0" />
<Parameter Name="CollectionNumber" Value="1" />
<Parameter Name="ManifestPath" Value="0001_AEConditions/AEConditions_ConfigCollection.xml" />
<Parameter Name="FilePath" Value="0001_AEConditions/AEConditions.signals.xml" />
<Parameter Name="FilePath" Value="0001_AEConditions/AEConditions.conditions.xml" />
<Parameter Name="FilePath" Value="0001_AEConditions/AEConditions.ncac.xml" />
<Parameter Name="FilePath" Value="0001_AEConditions/AEConditions.ncos.xml" />
<Parameter Name="FilePath" Value="0001_AEConditions/AEConditions.task.xml" />
</TestStep>
<TestStep Operation="HighlevelConfig">
<Parameter Name="Operation" Value="PrepareConfigCollection" />
<Parameter Name="ClientNumber" Value="0" />
<Parameter Name="CollectionNumber" Value="1" />
</TestStep>
<TestStep Operation="HighlevelConfig">
<Parameter Name="Operation" Value="CommitAndCloseConfigCollection" />
<Parameter Name="ClientNumber" Value="0" />
<Parameter Name="CollectionNumber" Value="1" />
</TestStep>
<TestStep Operation="HighlevelConfig">
<Parameter Name="Operation" Value="Disconnect" />
<Parameter Name="ClientNumber" Value="0" />
</TestStep>
<!-- END OF SUCCESSFUL RECONFIGURATION -->
<!-- Operation: VerifyLog: inspect log files. -->
<TestStep Operation="VerifyLog">
<!-- Find text (with regex) on any row in log file -->
<Parameter Name="LogFolder" Value="AC800MExecService" />
<!-- DbgPrint Id=5 v1=1 v2=0 -->
<Parameter Name="Text" Value="AEConditionManager::RegisterCondition: size=0, and inputSize=2." />
<Parameter Name="Text" Value="AEConfigurableComponent::EndCommit completed." />
<Parameter Name="Text" Value="AEConditionManager::RegisterCondition: size=2, and inputSize=2." />
<Parameter Name="Text" Value="AEConfigurableComponent::EndCommit completed." />
<Parameter Name="Text" Value="CEN: SN:alarm1_SrcName / CN:alarm1_CondName / Active:true" />
<Parameter Name="Text" Value="CEN: SN:alarm1_SrcName / CN:alarm1_CondName / Acknowledged:true" />
<Parameter Name="Text" Value="CEN: SN:alarm1_SrcName / CN:alarm1_CondName / Retain:true" />
<Parameter Name="Text" Value="CEN: SN:alarm1_SrcName / CN:alarm1_CondName / ConditionId:\'-AEConditions.Program1.alarm1Obj\'" />
<Parameter Name="Text" Value="CEN: SN:alarm2_SrcName / CN:alarm2_CondName / Active:true" />
<Parameter Name="Text" Value="CEN: SN:alarm1_SrcName / CN:alarm1_CondName / Acknowledged:true" />
<Parameter Name="Text" Value="CEN: SN:alarm2_SrcName / CN:alarm2_CondName / Retain:true" />
<Parameter Name="Text" Value="CEN: SN:alarm1_SrcName / CN:alarm1_CondName / Active:false" />
<Parameter Name="Text" Value="CEN: SN:alarm1_SrcName / CN:alarm1_CondName / Acknowledged:true" />
<Parameter Name="Text" Value="CEN: SN:alarm1_SrcName / CN:alarm1_CondName / Retain:false" />
<Parameter Name="Text" Value="CEN: SN:alarm2_SrcName / CN:alarm2_CondName / Active:false" />
<Parameter Name="Text" Value="CEN: SN:alarm1_SrcName / CN:alarm1_CondName / Acknowledged:true" />
<Parameter Name="Text" Value="CEN: SN:alarm1_SrcName / CN:alarm1_CondName / Retain:false" />
<Parameter Name="Text" Value="AEConditionManager::UnregisterCondition: size=4 and inputSize=2." />
<Parameter Name="Text" Value="AEConfigurableComponent::CleanupConfigInternal completed." />
</TestStep>
<!-- Operation: Stop: halt platform and ApplicationServices -->
<TestStep Operation="Stop" />
</TestCase>
</TestCases>
MSTest
Used for example in the Operations stream for Symphony Plus, e.g. in backend services written with C++ and C#.
Example of a C#/C++ test to write and read a tag:
[TestMethod]
public void ItemsReadWrite(int newValue)
{
// Assert.
Assert.IsTrue(CReadWriteField::TestSetAndGet(newValue));
Console.WriteLine("Test finished.");
}
bool CReadWriteField::TestSetAndGet(int newValue)
{
CString strLogMessage;
// Prepare to Set new info
TAGINFOEX tInfo;
SetTagInfo(tInfo, _tVt, newValue);
DWORD dwSubParam = SetSubParam(_tType);
// Notice: tInfo is not preserved by all settings. need to have a copy
TAGINFOEX eInfo = tInfo;
bool bRet = _si->SetInfo(_tType, &tInfo.info, 0, dwSubParam);
if (!bRet)
{
if (_paramConfig)
{
ApiTestUtil::Message(L"%s.%s - Set Info failed\n", _si->GetName().c_str(), _paramName);
return false;
}
}
else
{
if (!_paramConfig)
{
ApiTestUtil::Message(L"%s.%s - Set Info suceeded but not flagged as config\n", _si->GetName().c_str(), _paramName);
}
}
// Wait for Data Processing (if any is requested)
if (_tVt == vt_TVX)
{
Sleep(1000);
}
// Read back and compare
PwInfo rInfo = _si->GetInfo(_tType, dwSubParam);
if (rInfo.GetVarDataType() == vt_UNDEF)
{
ApiTestUtil::Message(L"%s.%s - read back failed\n", _si->GetName().c_str(), _paramName);
return false;
}
TAGINFOEX rrInfo(*rInfo.GetTaginfo(), rInfo.GetVarDataType());
if (!CompareInfo(_tType, rrInfo, eInfo))
{
ApiTestUtil::Message(L"%s.%s - comparison failed. Is %s - expected %s\n",
_si->GetName().c_str(), _paramName, rInfo.ToString().c_str(), eInfo.ToString().c_str());
return false;
}
return true;
}
Manual tests
Manual test cases are rare in software unit and component tests.
In cases they are needed, they should be defined in Test Case work items and be part of a Test suite in Azure DevOps. When the manual test cases are being performed, execution of the Test suite should be triggered so that the test result is registered directly in Azure DevOps together with the result of the automated tests.
To do this, manual tests and recording of the test results for each test step can be done using Microsoft Test Runner (which will be started automatically when you trigger the execution of test points in your test suite defined in Azure).
You can run tests for both web applications and desktop apps. For more details, check Microsoft's guides: Run manual tests