{"id":12015,"date":"2025-06-05T11:37:53","date_gmt":"2025-06-05T09:37:53","guid":{"rendered":"https:\/\/www.basyskom.de\/?p=12015"},"modified":"2025-06-06T10:15:24","modified_gmt":"2025-06-06T08:15:24","slug":"how-to-do-black-box-testing-of-an-opc-ua-server-with-cucumber-js","status":"publish","type":"post","link":"https:\/\/www.basyskom.de\/en\/how-to-do-black-box-testing-of-an-opc-ua-server-with-cucumber-js\/","title":{"rendered":"How to do black box testing of an OPC UA server with Cucumber-JS"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"12015\" class=\"elementor elementor-12015\" data-elementor-post-type=\"post\">\n\t\t\t\t<div class=\"elementor-element elementor-element-09ebaee e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-equal-height-no e-con e-parent\" data-id=\"09ebaee\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-92c5e08 elementor-widget elementor-widget-heading\" data-id=\"92c5e08\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">Motivation<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3f27ba3 elementor-widget elementor-widget-text-editor\" data-id=\"3f27ba3\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The steadily growing importance of OPC UA has increased the demand for server applications to integrate existing or new devices into an OPC UA based ecosystem.\u00a0 An OPC UA server application usually includes business logic to handle method calls or to react to external inputs. If the server implements an existing\u00a0<a class=\"external-link\" href=\"https:\/\/opcfoundation.org\/about\/opc-technologies\/opc-ua\/ua-companion-specifications\/\" rel=\"nofollow noopener\" target=\"_blank\">companion specification<\/a>, the behavior of the server also has to conform strictly to the specification in order to ensure interoperability with applications from other manufacturers.<\/p>\n<p>\u00a0<\/p>\n<p>While a good unit test coverage of the server&#8217;s code is a helpful building block to achieve and maintain compatibility with the specification, running tests against the public interface the server exposes to its clients is equally important to ensure that modifications to the code don&#8217;t introduce unwanted behavioral changes. This article demonstrates the basics of implementing black box tests for an OPC UA server that implements the\u00a0<a class=\"external-link\" href=\"https:\/\/reference.opcfoundation.org\/AutoID\/v101\/docs\/\" rel=\"nofollow noopener\" target=\"_blank\">AutoId<\/a>\u00a0companion specification and exposes an\u00a0<a class=\"external-link\" href=\"https:\/\/reference.opcfoundation.org\/AutoID\/v101\/docs\/6.5\" rel=\"nofollow noopener\" target=\"_blank\">RfidReaderDeviceType<\/a> object.<\/p>\n<p>\u00a0<\/p>\n<p>We provide the full source code for the example project on <a href=\"https:\/\/github.com\/basysKom\/cucumber-js-opc-ua-demo\" target=\"_blank\" rel=\"noopener\">Github<\/a>.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1467c27 elementor-widget elementor-widget-heading\" data-id=\"1467c27\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">What Is Black Box Testing?<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e2520d8 elementor-widget elementor-widget-text-editor\" data-id=\"e2520d8\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Black box testing is a way of software testing where the application under test is regarded as an opaque box with interfaces to the outside world which are tested against a specification of expected behavior. This means that the tests must not require knowledge of the application&#8217;s inner workings and ideally would still pass if the entire application would be rewritten from scratch.<\/p><p>\u00a0<\/p><p>Depending on the architecture of the application, it might still be necessary to create some test fixture. For example, the application might depend on inputs from attached devices or external processes it communicates with. In such a case, a controllable mockup of the external system is required in order to trigger state changes or events in the application.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-56e3729 elementor-widget elementor-widget-heading\" data-id=\"56e3729\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">Cucumber and Gherkin<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-02b1851 elementor-widget elementor-widget-text-editor\" data-id=\"02b1851\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p><a class=\"external-link\" href=\"https:\/\/cucumber.io\/\" rel=\"nofollow noopener\" target=\"_blank\">Cucumber<\/a>\u00a0is a\u00a0<a class=\"external-link\" href=\"https:\/\/cucumber.io\/docs\/bdd\/\" rel=\"nofollow noopener\" target=\"_blank\">BDD<\/a>\u00a0tool that describes itself as a &#8220;tool for running automated acceptance tests, written in plain language&#8221;. It uses test definitions written in the\u00a0<a class=\"external-link\" href=\"https:\/\/cucumber.io\/docs\/gherkin\/\" rel=\"nofollow noopener\" target=\"_blank\">Gherkin<\/a>\u00a0language which consists of plain language held together by some keywords and a certain structure.<\/p><p>\u00a0<\/p><p>A feature is a collection of scenarios which each have a\u00a0 short description and consist of one or more steps starting with a keyword. Step definitions can contain placeholders to enable running the same check with different values to serve as inputs to the test setup or as expected output values.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8004c56 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"8004c56\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-bash'>Feature: State handling for running scans and reader online\/offline status must be checked\n  If the reader goes offline, the server state must change to Error and starting a scan must be forbidden.\n  While a scan is running, no new scan can be started.\n \n  Scenario: Attempt start while scan is running\n    Given a connected OPC UA client\n    And a running scan\n    Then I should see that the DeviceStatus is &quot;Scanning&quot;\n    When I call the ScanStart method with DataAvailable &quot;false&quot;\n    Then I should see that the method returned &quot;BadInvalidState&quot;\n    And I should see that the DeviceStatus is &quot;Scanning&quot; <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3e5250d elementor-widget elementor-widget-text-editor\" data-id=\"3e5250d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>A key idea of Cucumber is to separate the test description from the test implementation. By doing so, the tests become much more expressive as the intent of the test isn&#8217;t hidden in implementation details. The feature files can even be used to communicate\/brainstorm with less technical stake holders about how certain feature should work. Another way to think about Cucumber is &#8220;executable user stories&#8221;.<\/p><p>\u00a0<\/p><p>How do we implement these tests? When running the tests, they are fed into a Gherkin interpreter. This interpreter will look for an implementation of each step. There are Cucumber\/Gherkin implementations in around 24 languages. For our example, we chose to implement Typescript as it is a modern language with a mature Cucumber implementation as well as a good OPC UA client library.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1ab7959 elementor-widget elementor-widget-heading\" data-id=\"1ab7959\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">Cucumber-JS<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-568b583 elementor-widget elementor-widget-text-editor\" data-id=\"568b583\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The official JavaScript implementation of Cucumber is available as a\u00a0<a class=\"external-link\" href=\"https:\/\/www.npmjs.com\/package\/@cucumber\/cucumber\" rel=\"nofollow noopener\" target=\"_blank\">Node.js module<\/a>\u00a0on NPM and provides all necessary components to implement a test setup and to run tests written in Gherkin. It comes with built-in TypeScript declarations and\u00a0<a class=\"external-link\" href=\"https:\/\/github.com\/cucumber\/cucumber-js\/blob\/HEAD\/docs\/transpiling.md\" rel=\"nofollow noopener\" target=\"_blank\">transpilation support<\/a>, so test setups can be written entirely in TypeScript.<\/p><p>\u00a0<\/p><p>The core component of a TypeScript based test setup with Cucumber-JS is the <i>World<\/i> class which must be extended by the user to hold any state that is required between the steps that make up a scenario. A new World instance is created for each scenario. For our example, the <i>World<\/i> will contain the OPC UA client instance we use to interact with the application under test.<\/p><p>\u00a0<\/p><p>The step definitions are added as functions outside of any class using a helper function for each keyword. Cucumber-JS passes the <i>World<\/i> class instance as the first parameter so the step definitions can access and modify its data.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3b92727 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"3b92727\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-typescript'>Given(&#039;a connected OPC UA client&#039;, async function (this: OpcUaWorld) {\n  \/\/ Create a client and add it to the World class instance\n});\n \nWhen(\n  &#039;I call the ScanStart method with DataAvailable {string}&#039;,\n  async function (this: OpcUaWorld, dataAvailable: string) {\n    \/\/ Call the method using the client of the World class instance\n    \/\/ Store the result of the call in the World class instance\n  },\n);\n \nThen(\n  &#039;I should see that the method returned {string}&#039;,\n  async function (this: OpcUaWorld, expectedStatus: string) {\n    \/\/ Assert that the result stored in the World class instance matches the expected status\n  },\n); <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7a67389 elementor-widget elementor-widget-text-editor\" data-id=\"7a67389\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Step definition implementations can be spread over as many source files as the developer deems useful to achieve a logical grouping of related steps.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-078df6c elementor-widget elementor-widget-heading\" data-id=\"078df6c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">Our Test Setup<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-dd61695 elementor-widget elementor-widget-heading\" data-id=\"dd61695\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">The OPC UA Server\n<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1cedbb1 elementor-widget elementor-widget-text-editor\" data-id=\"1cedbb1\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>As our application under test, we implemented a basic OPC UA server using the\u00a0<a class=\"external-link\" href=\"https:\/\/www.npmjs.com\/package\/node-opcua\" rel=\"nofollow noopener\" target=\"_blank\">NodeOPCUA<\/a>\u00a0module. It loads the\u00a0<a class=\"external-link\" href=\"https:\/\/reference.opcfoundation.org\/DI\/v104\/docs\/\" rel=\"nofollow noopener\" target=\"_blank\">DI<\/a>\u00a0and\u00a0<a class=\"external-link\" href=\"https:\/\/reference.opcfoundation.org\/AutoID\/v101\/docs\/\" rel=\"nofollow noopener\" target=\"_blank\">AutoId<\/a>\u00a0companion specifications at runtime, instantiates an object node of type\u00a0<a class=\"external-link\" href=\"https:\/\/reference.opcfoundation.org\/AutoID\/v101\/docs\/6.5\" rel=\"nofollow noopener\" target=\"_blank\">RfidReaderDeviceType<\/a>\u00a0and\u00a0 writes the identification properties. A business logic implementation handles calls to the ScanStart and ScanStop methods and updates the DeviceStatus variable.<\/p><p>\u00a0<\/p><p>To keep the implementation simple, our server project uses a TCP interface to communicate with a simulated RFID reader (in a real world application, this would most likely be a serial port). If the simulated reader sends information about a recognized RFID tag, an OPC UA event of type\u00a0<a class=\"external-link\" href=\"https:\/\/reference.opcfoundation.org\/AutoID\/v101\/docs\/7.6\" rel=\"nofollow noopener\" target=\"_blank\">RfidScanEventType<\/a>\u00a0is generated if the current DeviceStatus is Scanning. If the DataAvailable parameter for the ScanStart method is true, DeviceStatus changes back to Idle after the first tag has been received from the reader.<\/p><p>\u00a0<\/p><p>The simulated reader is implemented as a TypeScript class with a public method to inject recognized tags for test purposes.<\/p><p>Note that our use of NodeOPCUA was purely for convenience. Even though we are testing using Cucumber.js, the server can be written in any language as we are purely testing via its OPC UA service interface.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5a86955 elementor-widget elementor-widget-heading\" data-id=\"5a86955\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Features And Scenarios<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4bbdd43 elementor-widget elementor-widget-text-editor\" data-id=\"4bbdd43\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Our tests are split into three features which each reside in their own file<\/p><ul><li>opcua_autoid_basics.feature contains scenarios to check if the RfidReaderDeviceType instance exists in the server&#8217;s address space and if the identification properties look as expected<\/li><li>opcua_autoid_scanning.feature checks if a single shot scan and a scan with manual stop work as expected based on defined input from the simulated reader<\/li><li>opcua_autoid_statehandling.feature makes sure the state handling works as expected. For example, it must not be possible to start a scan while a scan is running.<\/li><\/ul><p>See <a href=\"https:\/\/github.com\/basysKom\/cucumber-js-opc-ua-demo\/tree\/main\/features\" target=\"_blank\" rel=\"noopener\">Github<\/a> for full details.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-a623c03 elementor-widget elementor-widget-heading\" data-id=\"a623c03\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">The World Class<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e16246a elementor-widget elementor-widget-text-editor\" data-id=\"e16246a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Our <i>OpcUaWorld<\/i> class extends the Cucumber-JS <i>World<\/i> class and adds an NodeOPCUA client used to interact with the OPC UA server. Several helper functions to find the necessary nodes in the server, to call the ScanStart and ScanStop methods and to read the DeviceStatus variable are also implemented to keep the step definition implementations as compact as possible.<\/p><p>\u00a0<\/p><p>The necessary variables to store information gathered in\u00a0<span class=\"inline-comment-marker valid\" data-ref=\"061c3430-2d60-485f-9d74-0dfc7f422edf\"><i>When<\/i> steps<\/span> to have them available in the related <i>Then<\/i> steps are added as public member variables so the step definitions can access them.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-38dcef0 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"38dcef0\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-typescript'>export class OpcUaWorld&lt;ParametersType = any&gt; extends World&lt;ParametersType&gt; {\n  uaClient: OPCUAClient | null = null; \/\/ A client connected to the server\n  session: ClientSession | null = null; \/\/ A session on the server\n  readerSimulator: RfidReaderSimulator;\n  server: AutoIdServer;\n \n  \/\/ Other member variables required to hold state between steps\n  receivedEvents: Variant[][] = [];\n  methodCallStatus: StatusCode | null = null;\n \n  constructor(options: IWorldOptions&lt;ParametersType&gt;) {\n    super(options);\n \n    \/\/ Get bind ports from the environment\n    const readerPort =\n      parseInt(process.env.READER_SIMULATOR_PORT || &#039;&#039;) || 5678;\n    const opcuaPort = parseInt(process.env.OPCUA_BIND_PORT || &#039;&#039;) || 4840;\n \n    \/\/ Instantiate a server and a simulated reader\n    this.readerSimulator = new RfidReaderSimulator(readerPort);\n    this.server = new AutoIdServer(opcuaPort, readerPort);\n  }\n \n  async initialize() {\n    this.readerSimulator.initialize();\n    await this.server.initialize();\n  }\n \n  async shutdown() {\n    await this.disconnectFromServer();\n    await this.server.stop();\n    await this.readerSimulator.shutdown();\n  }\n \n  \/\/ Methods to create a client, to connect to the server and to create a session\n  \/\/ Methods to find the necessary nodes to interact with the RfidReaderDeviceType\n  \/\/ Methods to call ScanStart and ScanStop on the server\n  \/\/ Methods to read variable values\n}\n \n\/\/ Register the world constructor\nsetWorldConstructor(OpcUaWorld); <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f8ef35f elementor-widget elementor-widget-text-editor\" data-id=\"f8ef35f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Our <i>World<\/i> also contains the necessary member variables and methods to instantiate the OPC UA server and the simulated reader. This is a detail of our example implementation. When testing a &#8220;real&#8221; OPC UA server, it might be brought up differently.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-512051c elementor-widget elementor-widget-heading\" data-id=\"512051c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Step Definitions<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2793ef3 elementor-widget elementor-widget-text-editor\" data-id=\"2793ef3\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The necessary step definitions for our scenarios are spread over multiple TypeScript source files grouped by topic, for example:<\/p><ul><li>stepdefs_common.ts for basics like creating and connecting the OPA UA client, to control the simulated reader and the hooks to initialize and to shut down the World class instance<\/li><li>stepdefs_server_events.ts handles the subscription and monitored item part as well as the checks performed on the received RfidScanEventType events<\/li><li>stepdef_server_methods.ts implements the steps for calling the ScanStart and ScanStop methods and verifying their return status and output arguments.<\/li><\/ul><p>See <a href=\"https:\/\/github.com\/basysKom\/cucumber-js-opc-ua-demo\/tree\/main\/features\/step_definitions\" target=\"_blank\" rel=\"noopener\">Github<\/a> for full details.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-a5c1c0b elementor-widget elementor-widget-heading\" data-id=\"a5c1c0b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Running The Tests<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-bde2c16 elementor-widget elementor-widget-text-editor\" data-id=\"bde2c16\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>While Cucumber-JS can be entirely configured using command line arguments, it also supports reading a&nbsp;<a class=\"external-link\" href=\"https:\/\/github.com\/cucumber\/cucumber-js\/blob\/main\/docs\/configuration.md\" rel=\"nofollow noopener\" target=\"_blank\">configuration file<\/a>. We chose this approach to keep our test run scripts in the package.json as a simple as possible. Our file instructs Cucumber-JS to create a HTML report as well as pretty printed console output and tells it where to find the TypeScript source files containing our environment setup, <i>World<\/i> class and step definitions.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d4b1d49 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"d4b1d49\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-json'>{\n  &quot;default&quot;: {\n    &quot;formatOptions&quot;: {\n      &quot;snippetInterface&quot;: &quot;synchronous&quot;\n    },\n    &quot;format&quot;: [\n      [&quot;html&quot;, &quot;reports\/report.html&quot;],\n      &quot;summary&quot;,\n      &quot;@cucumber\/pretty-formatter&quot;,\n      &quot;cucumber-console-formatter&quot;\n    ],\n    &quot;requireModule&quot;: [&quot;ts-node\/register&quot;],\n    &quot;require&quot;: [&quot;env\/*.ts&quot;, &quot;world\/*.ts&quot;, &quot;features\/step_definitions\/*.ts&quot;]\n  }\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-9dc1b64 elementor-widget elementor-widget-text-editor\" data-id=\"9dc1b64\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The tests are added as scripts in the package.json file. The\u00a0<em>test<\/em>\u00a0script runs the tests while\u00a0<em>check<\/em> executes a dry run without really running any test steps to check if there are any missing step definitions.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-15a0700 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"15a0700\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"elementor-syntax-highlighter.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<pre><code class='language-json'>&quot;scripts&quot;: {\n  &quot;test&quot;: &quot;npx cucumber-js&quot;,\n  &quot;check&quot;: &quot;npx cucumber-js --dry-run&quot;\n} <\/code><\/pre><script>\nif (!document.getElementById('syntaxed-prism')) {\n\tvar my_awesome_script = document.createElement('script');\n\tmy_awesome_script.setAttribute('src','https:\/\/www.basyskom.de\/wp-content\/plugins\/syntax-highlighter-for-elementor\/assets\/prism2.js');\n\tmy_awesome_script.setAttribute('id','syntaxed-prism');\n\tdocument.body.appendChild(my_awesome_script);\n} else {\n\twindow.Prism && Prism.highlightAll();\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-6fdac66 elementor-widget elementor-widget-text-editor\" data-id=\"6fdac66\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The following images shows an excerpt of the HTML report we generated.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3d979f8 elementor-widget elementor-widget-image\" data-id=\"3d979f8\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img fetchpriority=\"high\" decoding=\"async\" width=\"800\" height=\"668\" src=\"https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/06\/cucumber-output.png\" class=\"attachment-large size-large wp-image-12029\" alt=\"basysKom, HMI Dienstleistung, Qt, Cloud, Azure\" srcset=\"https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/06\/cucumber-output.png 931w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/06\/cucumber-output-300x250.png 300w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/06\/cucumber-output-768x641.png 768w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/06\/cucumber-output-14x12.png 14w, https:\/\/www.basyskom.de\/wp-content\/uploads\/2025\/06\/cucumber-output-560x467.png 560w\" sizes=\"(max-width: 800px) 100vw, 800px\">\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b0cacb7 elementor-widget elementor-widget-heading\" data-id=\"b0cacb7\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Conclusion<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0b92e46 elementor-widget elementor-widget-text-editor\" data-id=\"0b92e46\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<div id=\"content\" class=\"page view\"><article><div id=\"main-content\" class=\"wiki-content\"><p><span class=\"inline-comment-marker\" data-ref=\"07c0f537-6d8a-4dae-91df-8549b9bd3a54\">With some initial investment of time and resources in agreeing on the domain language, extracting the necessary test steps and implementing a <i>World<\/i> class with its associated step definitions, we have built a powerful test setup where the test coverage can be extended without the requirement for programming skills.<\/span><\/p><p><span class=\"inline-comment-marker\" data-ref=\"07c0f537-6d8a-4dae-91df-8549b9bd3a54\">\u00a0<\/span><\/p><p><span class=\"inline-comment-marker\" data-ref=\"07c0f537-6d8a-4dae-91df-8549b9bd3a54\">Separating the test description from the test implementation leads to more expressive tests which are easier to maintain and understand in the long run. It also enables closer collaboration with stake holders.<\/span><\/p><\/div><\/article><\/div>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>This article shows how to implement black box tests for an OPC UA server using Cucumber. Cucumber is a BDD (\u201cBehavior Driven Development\u201d)  tool typically associated with business\/enterprise class software. We demonstrate that it can also be used to create very expressive tests for embedded software.<\/p>","protected":false},"author":4,"featured_media":12054,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1,2,230],"tags":[],"class_list":["post-12015","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-allgemein","category-blog","category-opc-ua"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/12015","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/comments?post=12015"}],"version-history":[{"count":46,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/12015\/revisions"}],"predecessor-version":[{"id":12091,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/12015\/revisions\/12091"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/media\/12054"}],"wp:attachment":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/media?parent=12015"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/categories?post=12015"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/tags?post=12015"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}