{"id":12064,"date":"2025-06-13T11:11:03","date_gmt":"2025-06-13T09:11:03","guid":{"rendered":"https:\/\/www.basyskom.de\/?p=12064"},"modified":"2025-06-23T09:38:48","modified_gmt":"2025-06-23T07:38:48","slug":"improved-black-box-testing-with-cucumber-cpp","status":"publish","type":"post","link":"https:\/\/www.basyskom.de\/en\/improved-black-box-testing-with-cucumber-cpp\/","title":{"rendered":"Improved Black Box Testing with Cucumber-CPP"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"12064\" class=\"elementor elementor-12064\" data-elementor-post-type=\"post\">\n\t\t\t\t<div class=\"elementor-element elementor-element-b57e6d2 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=\"b57e6d2\" 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-ce9a8d0 elementor-widget elementor-widget-heading\" data-id=\"ce9a8d0\" 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\">Introduction<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4c0a20c elementor-widget elementor-widget-text-editor\" data-id=\"4c0a20c\" 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 href=\"https:\/\/doc.qt.io\/qt-6\/qtopcua-index.html\" rel=\"nofollow noopener\" target=\"_blank\">Qt OPC UA <\/a>is a library we have started in 2015 and maintained ever since. It is covered by multiple extensive test suites written in C++ using the <a href=\"https:\/\/doc.qt.io\/qt-6\/qtest-overview.html\" rel=\"nofollow noopener\" target=\"_blank\">Qt Test<\/a>\u00a0framework. The existing test suites bring up a custom built server application based on the open62541 library and then test the API of QOpcUaClient and QOpcUaNode against that server.<\/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-375568c elementor-widget elementor-widget-text-editor\" data-id=\"375568c\" 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>Conceptually, these tests are black box tests since they are written against the public headers of Qt OPC UA. The test suites have served us well over the years, but reading the test source isn&#8217;t pleasant. Often there are a lots of implementation details which make it hard to figure out the intent of the test. After our <a href=\"https:\/\/www.basyskom.de\/how-to-do-black-box-testing-of-an-opc-ua-server-with-cucumber-js\/\">successful evaluation<\/a> of Cucumber-JS for black box tests of an OPC UA server, we decided to have a look at\u00a0<a href=\"https:\/\/github.com\/cucumber\/cucumber-cpp\" rel=\"nofollow noopener\" target=\"_blank\">Cucumber-CPP<\/a> and what using it to build tests for the Qt OPC UA C++ API would feel like.<\/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-96db91a elementor-widget elementor-widget-text-editor\" data-id=\"96db91a\" 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>We provide the full source code for the case-study project on <a href=\"https:\/\/github.com\/basysKom\/cucumber-cpp-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-6482d1d elementor-widget elementor-widget-heading\" data-id=\"6482d1d\" 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\">Cucumber-CPP<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e771be0 elementor-widget elementor-widget-text-editor\" data-id=\"e771be0\" 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>Cucumber-CPP is currently the only solution to run tests written in Gherkin using step definitions written in C++. Our first impression was that it is much less comfortable compared to other language specific implementations like Cucumber-JS because it doesn&#8217;t provide a way to parse the .feature files and execute the test scenarios itself. Instead, the intended way of using it is to build an executable which opens a TCP server with support for the JSON based cucumber wire protocol on localhost.<\/p><p>\u00a0<\/p><p>The official Ruby based Cucumber implementation with the\u00a0<a class=\"external-link\" href=\"https:\/\/github.com\/cucumber\/cucumber-ruby-wire\" rel=\"nofollow noopener\" target=\"_blank\">cucumber-ruby-wire plugin<\/a>\u00a0must be executed manually after starting that executable. It parses the .feature files, communicates with the server using the cucumber wire protocol, requests the execution of the step definitions and gathers the results.<\/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-df045b4 elementor-widget elementor-widget-heading\" data-id=\"df045b4\" 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\">Test Setup<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-a131e7b elementor-widget elementor-widget-text-editor\" data-id=\"a131e7b\" 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>We decided to implement just a few basic scenarios to test the water and not to attempt to recreate the full test coverage of the original test suites. The\u00a0<a class=\"external-link\" href=\"https:\/\/github.com\/cucumber\/cucumber-cpp\/tree\/main\/examples\" rel=\"nofollow noopener\" target=\"_blank\">examples<\/a>\u00a0from the repository served us as a good first reference on how to structure the code and which of the libraries built by the Cucumber-CPP project to link against.<\/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-7ad1f39 elementor-widget elementor-widget-heading\" data-id=\"7ad1f39\" 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-c413e35 elementor-widget elementor-widget-text-editor\" data-id=\"c413e35\" 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 main focus of the test cases was to cover the basic functionalities (read\/write\/browsing\/monitored items\/method calls) superficially and to find out if common test steps could be extracted.<\/p><p>\u00a0<\/p><p>We end<span class=\"inline-comment-marker valid\" data-ref=\"654f168d-2a80-4309-8e01-d6455c89a994\">ed up with six scenarios that share at least some common steps.<\/span><\/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-c87d0df elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"c87d0df\" 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: Qt OPC UA Basics\n  Test basic functionality of QOpcUaClient and QOpcUaNode\n \n  Scenario: Write and read back (string)\n    Given a connected client\n    When I write &quot;foo&quot; to &quot;ns=2;s=Demo.Static.Scalar.String&quot;\n    And I read the value of &quot;ns=2;s=Demo.Static.Scalar.String&quot;\n    Then the value should be &quot;foo&quot;\n \n  Scenario: Write and read back (int64)\n    Given a connected client\n    When I write 42 to &quot;ns=2;s=Demo.Static.Scalar.Int64&quot;\n    And I read the value of &quot;ns=2;s=Demo.Static.Scalar.Int64&quot;\n    Then the value should be 42\n \n  Scenario: Browse children\n    Given a connected client\n    When I browse the children of &quot;ns=3;s=TestFolder&quot;\n    Then I should see that the browse result contains 86 children\n    And I should see that the browse result contains a child named &quot;2:DateTimeArrayTest&quot;\n \n  Scenario: Value monitoring\n    Given a connected client\n    When I write &quot;foo&quot; to &quot;ns=2;s=Demo.Static.Scalar.String&quot;\n    And I enable value monitoring for &quot;ns=2;s=Demo.Static.Scalar.String&quot;\n    And I wait 200 ms\n    And I write &quot;bar&quot; to &quot;ns=2;s=Demo.Static.Scalar.String&quot;\n    And I wait 200 ms\n    And I write &quot;baz&quot; to &quot;ns=2;s=Demo.Static.Scalar.String&quot;\n    And I wait 200 ms\n    Then I should have received 3 value attribute updates\n    And value update #0 should be &quot;foo&quot;\n    And value update #1 should be &quot;bar&quot;\n    And value update #2 should be &quot;baz&quot;\n \n  Scenario: Event monitoring\n    Given a connected client\n    When I enable event monitoring for &quot;i=2253&quot;\n    And I trigger an event with severity 23\n    And I trigger an event with severity 42\n    And I trigger an event with severity 5\n    Then I should have received an event with severity 23\n    And I should have received an event with severity 42\n    And I should have received an event with severity 5\n \n  Scenario Outline: Method calls\n    Given a connected client\n    When I call the multiply method with input arguments &lt;InputA&gt; and &lt;InputB&gt;\n    Then the method call should have returned Good\n    And output argument #0 should be &lt;Product&gt;\n \n    Examples:\n      | InputA | InputB | Product |\n      | 21     | 2      | 42      |\n      | 8      | 8      | 64      | <\/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-aeaeffa elementor-widget elementor-widget-text-editor\" data-id=\"aeaeffa\" 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>To give an impression of the improved readability, the following snippet shows a part of the basic setup of the event monitoring unit test written in C++.<\/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-b35850f elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"b35850f\" 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-cpp'>QFETCH(QOpcUaClient *, opcuaClient);\nOpcuaConnector connector(opcuaClient, m_endpoint);\n\nQScopedPointer&lt;QOpcUaNode&gt; serverNode(opcuaClient-&gt;node(QOpcUa::namespace0Id(QOpcUa::NodeIds::Namespace0::Server)));\nQVERIFY(serverNode != nullptr);\n\nQScopedPointer&lt;QOpcUaNode&gt; testFolderNode(opcuaClient-&gt;node(&quot;ns=3;s=TestFolder&quot;));\nQVERIFY(testFolderNode != nullptr);\n\nQSignalSpy enabledSpy(serverNode.data(), &amp;QOpcUaNode::enableMonitoringFinished);\nQSignalSpy eventSpy(serverNode.data(), &amp;QOpcUaNode::eventOccurred);\n\nQOpcUaMonitoringParameters::EventFilter filter;\nfilter &lt;&lt; QOpcUaSimpleAttributeOperand(&quot;Severity&quot;);\nfilter &lt;&lt; QOpcUaSimpleAttributeOperand(&quot;Message&quot;);\n\nQOpcUaMonitoringParameters p(0);\np.setQueueSize(10); \/\/ Without setting the queue size, we get a queue overflow event after triggering both events without waiting\np.setFilter(filter);\n\nserverNode-&gt;enableMonitoring(QOpcUa::NodeAttribute::EventNotifier, p);\nenabledSpy.wait();\nQCOMPARE(enabledSpy.size(), 1);\nQCOMPARE(enabledSpy.at(0).at(0).value&lt;QOpcUa::NodeAttribute&gt;(), QOpcUa::NodeAttribute::EventNotifier);\nQCOMPARE(enabledSpy.at(0).at(1).value&lt;QOpcUa::UaStatusCode&gt;(), QOpcUa::UaStatusCode::Good);\n\nQCOMPARE(serverNode-&gt;monitoringStatus(QOpcUa::NodeAttribute::EventNotifier).filter().value&lt;QOpcUaMonitoringParameters::EventFilter&gt;(),\n         filter); <\/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-53e9417 elementor-widget elementor-widget-heading\" data-id=\"53e9417\" 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 CMake Project<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8a04ced elementor-widget elementor-widget-text-editor\" data-id=\"8a04ced\" 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 we are building a test setup for a Qt based library, we decided to go with the Qt Test driver that comes with Cucumber-CPP. The latest v0.7.0 tag from December 2023 explicitly invokes find_package() for the Qt 5 libraries, so we went with the current main branch, where Qt 6 support is available and can be enabled using the CUKE_ENABLE_QT_6 option in CMake.<\/p><p>\u00a0<\/p><p>To keep the external dependencies of the project small, we decided to add Cucumber-CPP as a git submodule and to build it with the project using add_subdirectory(). This also ensures that it is built against the same version of Qt 6 as the Qt OPC UA module under test.<\/p><p>\u00a0<\/p><p>The full CMakeLists.txt is available on <a href=\"https:\/\/github.com\/basysKom\/cucumber-cpp-opc-ua-demo\/blob\/main\/CMakeLists.txt\" 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-180012f elementor-widget elementor-widget-heading\" data-id=\"180012f\" 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\">Context Class And 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-403c3cb elementor-widget elementor-widget-text-editor\" data-id=\"403c3cb\" 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 context class (equivalent to the World class in Cucumber-JS) and the step definitions are implemented in a single file named CucumberCppQtOpcUaTestsSteps.cpp which is built and linked against the Qt6::Test, Qt6::OpcUa and cucumber-cpp libraries. For a more extensive test setup, spreading the definitions to multiple files might be a good idea.<\/p><p>\u00a0<\/p><p>It is mandatory to include the QTest header before the Cucumber-CPP header to allow Cucumber-CPP to identify the test framework in use.<\/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-a5efb75 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"a5efb75\" 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-cpp'>#include &lt;QTest&gt;\n#include &quot;cucumber-cpp\/autodetect.hpp&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-de6e666 elementor-widget elementor-widget-text-editor\" data-id=\"de6e666\" 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>Due to the asynchronous nature of the Qt OPC UA API,\u00a0<a class=\"external-link\" href=\"https:\/\/doc.qt.io\/qt-6\/qsignalspy.html\" rel=\"nofollow noopener\" target=\"_blank\">QSignalSpy<\/a>\u00a0is the test implementer&#8217;s best friend because it provides an easy way to record received signals and to wait until a signal is received or a timeout occurs, whatever happens first. While there are some step definitions where the signal carrying the result can be checked directly in the step definition, other cases like monitored items or method calls can be implemented much easier if the corresponding QSignalSpy is part of the context and available to all the following steps in a scenario.<\/p><p>\u00a0<\/p><p>When we attempted to run the first step, we were puzzled because the test steps did never return from the first call to\u00a0<a class=\"external-link\" href=\"https:\/\/doc.qt.io\/qt-6\/qsignalspy.html#wait\" rel=\"nofollow noopener\" target=\"_blank\">QSignalSpy::wait()<\/a>. It turned out that the Qt Test driver in Cucumber-CPP\u00a0<a class=\"external-link\" href=\"https:\/\/github.com\/cucumber\/cucumber-cpp\/issues\/294\" rel=\"nofollow noopener\" target=\"_blank\">does not use an event loop<\/a>, so we had to bring our own by instantiating a static QCoreApplication in our code. With this additional step, waiting for QSignalSpy worked as intended and the expected signals were recorded.<\/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-4a963c2 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"4a963c2\" 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-cpp'>#include &lt;QCoreApplication&gt;\n#include &lt;QObject&gt;\n#include &lt;QProcess&gt;\n#include &lt;QSignalSpy&gt;\n \n#include &lt;QtOpcUa\/QOpcUaClient&gt;\n#include &lt;QtOpcUa\/QOpcUaNode&gt;\n#include &lt;QtOpcUa\/QOpcUaProvider&gt;\n \nclass QtOpcUaTestCtx\n{\npublic:\n    QtOpcUaTestCtx() { }\n \n    ~QtOpcUaTestCtx() { }\n \n    QScopedPointer&lt;QOpcUaClient&gt; m_client;\n \n    QScopedPointer&lt;QOpcUaNode&gt; m_browseNode;\n    QList&lt;QOpcUaReferenceDescription&gt; m_references;\n \n    QScopedPointer&lt;QOpcUaNode&gt; m_monitoringNode;\n    QScopedPointer&lt;QSignalSpy&gt; m_attributeUpdatedSpy;\n    QScopedPointer&lt;QSignalSpy&gt; m_eventSpy;\n \n    QScopedPointer&lt;QOpcUaNode&gt; m_methodCallObjectNode;\n    QScopedPointer&lt;QSignalSpy&gt; m_methodCallSpy;\n \n    QScopedPointer&lt;QSignalSpy&gt; m_readSpy;\n \n    \/\/ Global state\n    static QProcess m_serverProcess;\n    static const QUrl m_serverUrl;\n    \/\/ A QCoreApplication is required to use QSignalSpy::wait()\n    static QCoreApplication m_app;\n \n    \/\/ Constants\n    static const QString PluginName;\n    static const QString TestFolderId;\n    static const QString MultiplyMethodId;\n    static const QString SeverityName;\n    static const QString TriggerEventId;\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-697dd39 elementor-widget elementor-widget-text-editor\" data-id=\"697dd39\" 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>Using QString values for placeholders in the step definitions requires manually adding a stream operator for QString and std::istream:<\/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-5a6a983 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"5a6a983\" 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-cpp'>std::istream &amp;operator&gt;&gt;(std::istream &amp;in, QString &amp;val)\n{\n    std::string s;\n    in &gt;&gt; s;\n    val = QString::fromStdString(s);\n    return in;\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-ffaf945 elementor-widget elementor-widget-text-editor\" data-id=\"ffaf945\" 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>We use the BeforeAll and AfterAll hooks provided by Cucumber-CPP to bring up the open62541 based test server before running the first scenario and to shut it down after the last scenario has finished.<\/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-8b4e18d elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"8b4e18d\" 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-cpp'>\/\/ Bring up the test server and make sure it is responsive\nBEFORE_ALL()\n{\n    const auto serverPath = QString::fromUtf8(qgetenv(&quot;TEST_SERVER_PATH&quot;));\n    QVERIFY(!serverPath.isEmpty());\n \n    QTcpSocket socket;\n    socket.connectToHost(QtOpcUaTestCtx::m_serverUrl.host(), QtOpcUaTestCtx::m_serverUrl.port());\n    QVERIFY2(socket.waitForConnected(1500) == false, &quot;Server is already running&quot;);\n \n    QtOpcUaTestCtx::m_serverProcess.setProgram(serverPath);\n    QtOpcUaTestCtx::m_serverProcess.start();\n    QCOMPARE(QtOpcUaTestCtx::m_serverProcess.waitForStarted(), true);\n \n    QTest::qWait(1500);\n \n    socket.connectToHost(QtOpcUaTestCtx::m_serverUrl.host(), QtOpcUaTestCtx::m_serverUrl.port());\n    QVERIFY(socket.waitForConnected(1500));\n}\n \n\/\/ Tear down the test server after all scenarios are done\nAFTER_ALL()\n{\n    if (QtOpcUaTestCtx::m_serverProcess.state() == QProcess::ProcessState::Running) {\n        QtOpcUaTestCtx::m_serverProcess.kill();\n        QtOpcUaTestCtx::m_serverProcess.waitForFinished();\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-5416902 elementor-widget elementor-widget-text-editor\" data-id=\"5416902\" 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 step definitions are implemented using macros, the string describing the step and its placeholders is passed as a regular expression. Using different regular expressions to distinguish strings or numeric values in placeholders allow to &#8220;overload&#8221; step definitions to have separate implementations for different input types to the same step (for example, see the two implementations each for &#8220;I write xxx to yyy&#8221; and &#8220;the value should be xxx&#8221;).<\/p><p>\u00a0<\/p><p>The cucumber::ScenarioScope class template is used to fetch the current context object to add data or to retrieve data left by previous steps.<\/p><p>The REGEX_PARAM macro allows typed retrieving of captured parameters from the regular expression describing the test step. The streaming operator we defined before lets us get string parameters directly as QString so we don&#8217;t have to convert them manually in our code before passing them to any Qt API.<\/p><p>\u00a0<\/p><p>The rest of the step definition can be written like normal Qt Test based test code and use its assertion functions like QVERIFY() and QCOMPARE().<\/p><p>\u00a0<\/p><p>The code snippet below only contains a few step definitions, the full source code is available on <a href=\"https:\/\/github.com\/basysKom\/cucumber-cpp-opc-ua-demo\/blob\/main\/features\/step_definitions\/CucumberCppQtOpcUaTestsSteps.cpp#L74\" 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-1d3e181 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"1d3e181\" 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-cpp'>WHEN(&quot;^I write \\&quot;(.*)\\&quot; to \\&quot;(.*)\\&quot;$&quot;)\n{\n    REGEX_PARAM(QString, value);\n    REGEX_PARAM(QString, nodeId);\n    cucumber::ScenarioScope&lt;QtOpcUaTestCtx&gt; ctx;\n \n    QVERIFY(ctx-&gt;m_client);\n \n    QSignalSpy writeSpy(ctx-&gt;m_client.get(), &amp;QOpcUaClient::writeNodeAttributesFinished);\n \n    ctx-&gt;m_client-&gt;writeNodeAttributes({ { nodeId, QOpcUa::NodeAttribute::Value, value } });\n \n    QVERIFY(writeSpy.wait());\n    QCOMPARE(writeSpy.size(), 1);\n    QCOMPARE(writeSpy.at(0).at(1), QOpcUa::UaStatusCode::Good);\n    const auto writeResults = writeSpy.at(0).at(0).value&lt;QList&lt;QOpcUaWriteResult&gt;&gt;();\n    QCOMPARE(writeResults.size(), 1);\n    QCOMPARE(writeResults.at(0).statusCode(), QOpcUa::UaStatusCode::Good);\n}\n \nWHEN(&quot;^I write ([^\\&quot; ]*) to \\&quot;(.*)\\&quot;$&quot;)\n{\n    REGEX_PARAM(qint64, value);\n    REGEX_PARAM(QString, nodeId);\n    cucumber::ScenarioScope&lt;QtOpcUaTestCtx&gt; ctx;\n \n    QVERIFY(ctx-&gt;m_client);\n \n    QSignalSpy writeSpy(ctx-&gt;m_client.get(), &amp;QOpcUaClient::writeNodeAttributesFinished);\n \n    ctx-&gt;m_client-&gt;writeNodeAttributes({ { nodeId, QOpcUa::NodeAttribute::Value, value } });\n \n    QVERIFY(writeSpy.wait());\n    QCOMPARE(writeSpy.size(), 1);\n    QCOMPARE(writeSpy.at(0).at(1), QOpcUa::UaStatusCode::Good);\n    const auto writeResults = writeSpy.at(0).at(0).value&lt;QList&lt;QOpcUaWriteResult&gt;&gt;();\n    QCOMPARE(writeResults.size(), 1);\n    QCOMPARE(writeResults.at(0).statusCode(), QOpcUa::UaStatusCode::Good);\n}\n \nWHEN(&quot;^I read the value of \\&quot;(.*)\\&quot;$&quot;)\n{\n    REGEX_PARAM(QString, nodeId);\n    cucumber::ScenarioScope&lt;QtOpcUaTestCtx&gt; ctx;\n \n    QVERIFY(ctx-&gt;m_client);\n \n    ctx-&gt;m_readSpy.reset(\n            new QSignalSpy(ctx-&gt;m_client.get(), &amp;QOpcUaClient::readNodeAttributesFinished));\n    QVERIFY(ctx-&gt;m_client-&gt;readNodeAttributes({ nodeId }));\n}\n \nTHEN(&quot;^the value should be \\&quot;(.*)\\&quot;$&quot;)\n{\n    REGEX_PARAM(QString, value);\n \n    cucumber::ScenarioScope&lt;QtOpcUaTestCtx&gt; ctx;\n    QVERIFY(ctx-&gt;m_readSpy);\n    if (ctx-&gt;m_readSpy-&gt;empty())\n        QVERIFY(ctx-&gt;m_readSpy-&gt;wait());\n    QCOMPARE(ctx-&gt;m_readSpy-&gt;size(), 1);\n \n    QCOMPARE(ctx-&gt;m_readSpy-&gt;at(0).at(1), QOpcUa::UaStatusCode::Good);\n    const auto results = ctx-&gt;m_readSpy-&gt;at(0).at(0).value&lt;QList&lt;QOpcUaReadResult&gt;&gt;();\n    QCOMPARE(results.size(), 1);\n    QCOMPARE(results.at(0).statusCode(), QOpcUa::UaStatusCode::Good);\n    QCOMPARE(results.at(0).value(), value);\n}\n \nTHEN(&quot;^the value should be ([^\\&quot; ]*)$&quot;)\n{\n    REGEX_PARAM(qint64, value);\n \n    cucumber::ScenarioScope&lt;QtOpcUaTestCtx&gt; ctx;\n    QVERIFY(ctx-&gt;m_readSpy);\n    if (ctx-&gt;m_readSpy-&gt;empty())\n        QVERIFY(ctx-&gt;m_readSpy-&gt;wait());\n    QCOMPARE(ctx-&gt;m_readSpy-&gt;size(), 1);\n \n    QCOMPARE(ctx-&gt;m_readSpy-&gt;at(0).at(1), QOpcUa::UaStatusCode::Good);\n    const auto results = ctx-&gt;m_readSpy-&gt;at(0).at(0).value&lt;QList&lt;QOpcUaReadResult&gt;&gt;();\n    QCOMPARE(results.size(), 1);\n    QCOMPARE(results.at(0).statusCode(), QOpcUa::UaStatusCode::Good);\n    QCOMPARE(results.at(0).value(), value);\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-1c24395 elementor-widget elementor-widget-heading\" data-id=\"1c24395\" 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\">Building And 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-2042aaa elementor-widget elementor-widget-text-editor\" data-id=\"2042aaa\" 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>Building the project requires an installation of Qt 6.x (we used Qt 6.8, other versions might require a change to the expected number of children in the &#8220;Browse children&#8221; scenario) with the matching version of the Qt OPC UA module installed and the\u00a0<a class=\"external-link\" href=\"https:\/\/github.com\/cucumber\/cucumber-cpp?tab=readme-ov-file#dependencies\" rel=\"nofollow noopener\" target=\"_blank\">dependencies<\/a>\u00a0Cucumber-CPP mentions in its README. If Qt OPC UA is built with the CMake option QT_BUILD_TESTS=ON, the open62541 based test server is available in the bin directory of the module&#8217;s installation location.<\/p><p>\u00a0<\/p><p>After building the C++ test project, it must be run in an environment where the TEST_SERVER_PATH environment variable contains the path to the Qt OPC UA test server. Depending on the development setup, it might be necessary to point the LD_LIBRARY_PATH environment variable to the lib directory of the Qt installation to make the necessary runtime dependencies available to the test server binary.<\/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-8f8c6c9 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"8f8c6c9\" 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'>$ export TEST_SERVER_PATH=\/path\/to\/open62541-testserver\n$ export LD_LIBRARY_PATH=\/path\/to\/Qt\/6.x.x\/gcc_64\/lib # Optional, if the server requires it\n$ .\/CucumberCppQtOpcUaTests &amp;\n$ cucumber <\/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-7cbd5cb elementor-widget elementor-widget-text-editor\" data-id=\"7cbd5cb\" 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>After cucumber has finished and disconnected, the C++ executable will exit.<\/p><p>\u00a0<\/p><p>If everything went well, cucumber will print out all scenarios with their steps marked in green and conclude its output with a summary:<\/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-70a7031 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"70a7031\" 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'>7 scenarios (7 passed)\n40 steps (40 passed)\n0m4.698s <\/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-acba812 elementor-widget elementor-widget-text-editor\" data-id=\"acba812\" 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>To see a test fail, we modify the expected number of children in the &#8220;Browse Children&#8221; scenario and run the test setup again:<\/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-29999b0 elementor-widget elementor-widget-elementor-syntax-highlighter\" data-id=\"29999b0\" 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'>Scenario: Browse children                                                              # features\/opcua.feature:16\n  Given a connected client                                                             # CucumberCppQtOpcUaTestsSteps.cpp:103\n  When I browse the children of &quot;ns=3;s=TestFolder&quot;                                    # CucumberCppQtOpcUaTestsSteps.cpp:190\n  Then I should see that the browse result contains 86 children                        # CucumberCppQtOpcUaTestsSteps.cpp:208\n    TAP version 13\n    # cucumber::internal::QtTestObject\n    ok 1 - initTestCase()\n    not ok 2 - test()\n      ---\n      type: QCOMPARE\n      message: Compared values are not the same\n      wanted: 87 (numExpected)\n      found: 86 (ctx-&gt;m_references.size())\n      expected: 87 (numExpected)\n      actual: 86 (ctx-&gt;m_references.size())\n      at: cucumber::internal::QtTestObject::test() (\/path\/to\/cucumber-cpp-opcua-demo\/features\/step_definitions\/CucumberCppQtOpcUaTestsSteps.cpp:214)\n      file: \/path\/to\/cucumber-cpp-opcua-demo\/features\/step_definitions\/CucumberCppQtOpcUaTestsSteps.cpp\n      line: 214\n      ...\n    ok 3 - cleanupTestCase()\n    1..3\n    # tests 3\n    # pass 2\n    # fail 1\n     (Cucumber::Wire::Exception)\n    features\/opcua.feature:19:in `Then I should see that the browse result contains 86 children&#039;\n  And I should see that the browse result contains a child named &quot;2:DateTimeArrayTest&quot; # CucumberCppQtOpcUaTestsSteps.cpp:217 <\/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-132cc33 elementor-widget elementor-widget-text-editor\" data-id=\"132cc33\" 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 we can see, Cucumber-CPP transmits a comprehensive error output to cucumber, so it is easy to find out why and where the test step failed.<\/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-722c22e elementor-widget elementor-widget-heading\" data-id=\"722c22e\" 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\">Lessons Learned<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5ac1434 elementor-widget elementor-widget-text-editor\" data-id=\"5ac1434\" 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>When comparing our existing black box tests written using QtTest with the feature files we created for this case-study we clearly see the advantage of separating test description and test implementation. While Cucumber-CPP is not as comfortable to use as Cucumber-JS, which provides a self sufficient environment to implement and execute tests, building a test setup with it is still quite straightforward. If starting the server application and running the cucumber executable are bundled in a script\/CMake or CI pipeline, this aspect will be much less relevant to the developer experience.<\/p><\/div><\/article><\/div>\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-ea158fb elementor-widget elementor-widget-text-editor\" data-id=\"ea158fb\" 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 selected test scenarios achieved a good degree of reusable test step definitions. But it is improbable that this would hold up for the rest of the tests we currently have in Qt OPC UA, because there are lots of tests checking for expected values in complex structured types with nested members.\u00a0 These tests involve for example writing crafted values of a certain type to nodes in the server both as scalars and arrays, reading them back and then checking each member of the structured types for the expected value. Find a solution for that requires further research.<\/p>\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>Learn how to use Cucumber-CPP and Gherkin to implement better black box tests for a C++ library. We developed a case-study based on Qt OPC UA.<\/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,8,847],"tags":[124,15,123],"class_list":["post-12064","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-allgemein","category-blog","category-opc-ua","category-qt","category-software-testing","tag-opc-ua","tag-qt","tag-qt-opc-ua"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/12064","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=12064"}],"version-history":[{"count":43,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/12064\/revisions"}],"predecessor-version":[{"id":12145,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/posts\/12064\/revisions\/12145"}],"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=12064"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/categories?post=12064"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.basyskom.de\/en\/wp-json\/wp\/v2\/tags?post=12064"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}