DevOps: High-Speed Programming Unit Tests

WinCC Open Architecture (OA) has supported Unit Tests for quite a while now, however, when Siemens released version 3.16, one of the new features was High-Speed Programming.  This new feature set included a tool for project documentation and the ability to auto-generate templates for faster creation of code.  In this blog, we are going to look deeper at the new way to run and write Unit Tests inside of the graphical editor (GEDI). 

Step 1: Create a Script

The first thing we need to do is create a script, or class, that we would like to unit test.  I made a very simple script with two functions inside of it.  If we were using a Test-Driven Development (TDD) methodology writing the functions would come later.  Learn more about TDD

Step 2: Auto-generating Testing Script

Once you have the script created, all you will need to do is right-click that script and select the Open Unit Test option.  This will autogenerate a WinCC OA Test class which is used to run the unit tests.

Here you can see the created script and libs.

Step 3: Define your Tests

When you open the autogenerated script you will see a group of code already written.  The parts that will need to be added to are on line 28 and inside the startTestCase() function.  Inside the makeDynString on line 28 you will need to add in names of the tests you would like to run.  These tests should be small and only test a small unit of your code.  I’ve added “add” and “getDataPoint”.

Step 4: Write the Tests

Now that the tests are defined it’s time to actually write them.  Because we’ve added two entries above we need to add two cases to the switch statement.   Inside of the “add” case, I did a simple assertEqual(6,result); . This means I am testing that 6 equals the result of 2 plus 3.  This test will fail because this is not true.  I also wrote a simple test that reads a datapoint and asserts that the value is what I expect it to be.

Here is the entire script.

Step 5: Run the Test

Now that the tests are written we can now run them by right-clicking the test script and selecting Start Tests from the options.  This will cause the script and tests to run and display the results.

Step 6: View the Results

After we ran the test, from inside the GEDI we can see the results.  We have two fail and one success judging by the thumbs up or thumbs down.  The two that failed were the “add” and the “UnitTestDemo_”, which is a default autogenerated test that can be deleted.

For those that have been using Unit Tests inside of WinCC OA, none of this is to new because of the long support of Unit Tests in the platform but the new High-Speed Programming set of tools will make it easier for others to begin seeing the benefits of Unit Testing.

About The Author

3 thoughts on “DevOps: High-Speed Programming Unit Tests”

  1. Hi, Calvin

    Could it run the unit test in a script?
    I mean if it support to run all unit test .ctl files in a script at back-end instead of executing them manually in the GEDI

    1. Hi

      there are 2 ways.
      1. Use TestFramework
      + fully automated
      + useful in Jenkins or othe CI /CD tool
      – start overhead on writing json configs
      2. or make helper scrip with start all tests scripts recursively
      + easy to start
      – work only with existing project

      here snippet
      “`
      #uses “classes/oaTest/OaTestResultStatistic”
      #uses “classes/hsp/HspScript”
      #uses “classes/oaTest/OaTest”
      // $License: NOLICENSE

      //——————————————————————————–
      // used libraries (#uses)

      //——————————————————————————–
      // declare variables and constans

      class MyHspScript : HspScript
      {
      public OaTestResultStatistic stat;

      public int startUnitTest(string relPath = “”, string addOptions = “”)
      {
      if ( relPath == “” ) // use default relPath
      relPath = getUnitTestRelPath();

      const string covFilePath = getCoverageReportPath();
      const string covDir = dirName(PROJ_PATH + LOG_REL_PATH + covFilePath);
      if ( !isdir(covDir) )
      mkdir(covDir);

      fclose(fopen(PROJ_PATH + “quickResult.json”, “wb+”));
      fclose(fopen(PROJ_PATH + “fullResult.json”, “wb+”));
      if ( addOptions == “” ) // use default manager options
      addOptions = “-dumpCoverageOnExit -coveragereportfile ” + covFilePath;

      int rc = start(relPath, addOptions);
      string str;
      fileToString(PROJ_PATH + “quickResult.json”, str);
      string json;
      fileToString(PROJ_PATH + “quickResult.json”, json);

      stat.fromMapping(jsonDecode(json));
      remove(PROJ_PATH + “quickResult.json”);

      relPath = substr(relPath, strlen(“tests/libs/”));
      string resultPath = PROJ_PATH + DATA_REL_PATH + “oaTest/results/” + relPath + “.json”;
      string resultDir = dirName(resultPath);

      if ( !isdir(resultDir) )
      mkdir(resultDir);

      moveFile(PROJ_PATH + “fullResult.json”, resultPath);
      return rc;
      }
      };

      class TstRecursive : OaTest
      {

      public dyn_string getAllTestCaseIds()
      {
      return makeDynString(“recursive-check”);
      }

      protected OaTestResultStatistic stat;
      protected int testFilesCount;
      protected int allFilesCount;
      protected string path;

      protected int startTestCase(const string testCaseId)
      {
      _startTestRecursive(path + “/” + LIBS_REL_PATH);
      assertGreater(testFilesCount, 0, “check count of test scripts”);
      mapping map;
      stat.toMapping(map);
      oaUnitInfo(getCurrentTestCaseId(), “\n###########################\n” +
      “\n**** Unit test summary ****\n” +
      “\n###########################\n” +
      formatDebug(map));
      float f;
      if ( testFilesCount > 0 )
      {
      f = (float)allFilesCount / (float)testFilesCount;
      f = (float)100 / f;
      }
      oaUnitInfo(testCaseId, “Ration between all libs (” + allFilesCount + “) and tested libs (” + testFilesCount + “) = ” + (int)f);
      return 0;
      }

      private void _startTestRecursive(string dirPath)
      {
      if( !isdir(dirPath) )
      return;

      dyn_string libs = getFileNames(dirPath, “*.ctl”);
      for( int i = 1; i <= dynlen(libs); i++ )
      {
      _startTestForLib(dirPath + "/" + libs[ i ]);
      }

      dyn_string dirs = getFileNames(dirPath, "*", FILTER_DIRS);

      for( int i = 1; i <= dynlen(dirs); i++ )
      {
      const string dir = dirs[i];
      if( ( dir == "" ) || ( dir == "." ) || ( dir == ".." ) ||
      (dir == "tests") || (dir == "examples") )
      {
      continue;
      }
      _startTestRecursive(dirPath + "/" + dirs[ i ]);
      }
      }

      private void _startTestForLib(string filePath)
      {
      allFilesCount++;
      //
      // if( !isfile(filePath) )
      // return;

      MyHspScript script;
      script.setFilePath(filePath);

      if ( script.unitTestExist() )
      {
      oaUnitInfo(getCurrentTestCaseId(), "\n**** Start Unit test ****\n " + script.getFileRelPath());
      int rc = script.startUnitTest();
      assertGreater(script.stat.getAll(), 0, "check-count: " + script.getFileRelPath());
      oaUnitAbortNotEqual(getCurrentTestCaseId(), rc, 0,
      "check-rc: " + script.getFileRelPath());
      testFilesCount++;

      mapping map;
      script.stat.toMapping(map);
      oaUnitInfo(getCurrentTestCaseId(), formatDebug(map));
      stat.opPlus(script.stat);
      }
      else
      {
      // setKnownBug("missing unit test");
      oaUnitInfo(getCurrentTestCaseId(), "?!? Missing unit test for : " + script.getFileRelPath());
      }
      }

      };

      //——————————————————————————–
      /*!
      * @brief This is main rutine for …
      */
      main()
      {
      fclose(fopen(PROJ_PATH + "fullResult.json", "wb+"));

      TstRecursive test;;

      mkdir(PROJ_PATH + DATA_REL_PATH);
      mkdir(PROJ_PATH + DATA_REL_PATH + "oaTest");
      mkdir(PROJ_PATH + DATA_REL_PATH + "oaTest/results");

      test.startAll();

      moveFile(PROJ_PATH + "fullResult.json", PROJ_PATH + DATA_REL_PATH + "oaTest/results/crashCheck.json");
      exit(0);
      }

      “`

Leave a Reply to dawei liu Cancel Reply

Your email address will not be published. Required fields are marked *

Scroll to Top