<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Artur Neumann</title>
    <description>The latest articles on DEV Community by Artur Neumann (@individualit).</description>
    <link>https://dev.to/individualit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F256025%2F7881cedc-a7b9-453c-a535-7d6bd9366b81.png</url>
      <title>DEV Community: Artur Neumann</title>
      <link>https://dev.to/individualit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/individualit"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Artur Neumann</dc:creator>
      <pubDate>Wed, 01 Apr 2026 07:47:30 +0000</pubDate>
      <link>https://dev.to/individualit/-4iae</link>
      <guid>https://dev.to/individualit/-4iae</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/jankaritech/kubernetes-a-beginners-guide-to-container-orchestration-5fkh" class="crayons-story__hidden-navigation-link"&gt;Kubernetes - A Beginner's Guide to Container Orchestration&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/jankaritech"&gt;
            &lt;img alt="JankariTech logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1403%2Fc3f3670b-3a33-47c8-828b-3dd0bf573ce8.png" class="crayons-logo__image" width="413" height="413"&gt;
          &lt;/a&gt;

          &lt;a href="/ashim-stha" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3584060%2F236fac02-acca-42cc-9cea-a65ad4f670c7.png" alt="ashim-stha profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/ashim-stha" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ashim Shrestha
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ashim Shrestha
                
              
              &lt;div id="story-author-preview-content-3439220" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/ashim-stha" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3584060%2F236fac02-acca-42cc-9cea-a65ad4f670c7.png" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ashim Shrestha&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/jankaritech" class="crayons-story__secondary fw-medium"&gt;JankariTech&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/jankaritech/kubernetes-a-beginners-guide-to-container-orchestration-5fkh" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 1&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/jankaritech/kubernetes-a-beginners-guide-to-container-orchestration-5fkh" id="article-link-3439220"&gt;
          Kubernetes - A Beginner's Guide to Container Orchestration
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/kubernetes"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;kubernetes&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/containers"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;containers&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/jankaritech/kubernetes-a-beginners-guide-to-container-orchestration-5fkh#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            10 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Configure Joplin to work with ownCloud Infinite Scale (oCIS)</title>
      <dc:creator>Artur Neumann</dc:creator>
      <pubDate>Thu, 08 Jun 2023 06:02:37 +0000</pubDate>
      <link>https://dev.to/jankaritech/configure-joplin-to-work-with-owncloud-infinite-scale-ocis-19ih</link>
      <guid>https://dev.to/jankaritech/configure-joplin-to-work-with-owncloud-infinite-scale-ocis-19ih</guid>
      <description>&lt;p&gt;I love to use &lt;a href="//joplinapp.org"&gt;Joplin&lt;/a&gt; to organize my notes. To synchronize the notes between different devices I have so far used the WebDAV sync option together with &lt;a href="//github.com/owncloud/core/"&gt;ownCloud 10&lt;/a&gt;. Now &lt;a href="https://owncloud.com/infinite-scale/" rel="noopener noreferrer"&gt;oCIS (ownCloud Infinite Scale)&lt;/a&gt; is the new cool kid in the cloud storage space and I would like to use it for syncing of Joplin.&lt;/p&gt;

&lt;p&gt;Similar to ownCloud 10, oCIS offers a WebDAV API, but it has &lt;a href="https://doc.owncloud.com/ocis/next/deployment/services/s-list/auth-basic.html#introduction" rel="noopener noreferrer"&gt;disabled basic-auth by default and the docs discourage using it in production&lt;/a&gt;. Instead, oCIS implements the OIDC workflow for authentication. I tried to get the OIDC authentication into Joplin, but sadly my &lt;a href="https://github.com/laurent22/joplin/pull/7400" rel="noopener noreferrer"&gt;PR&lt;/a&gt; for that was refused.&lt;/p&gt;

&lt;p&gt;The workaround is the "Share via link" function of oCIS. The generated links support basic-auth by default. (Don't fear your notes will still be very private).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;create a folder in oCIS (I've called it &lt;code&gt;joplin&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;click on the "Add people" button of that folder, that will bring you to the sharing functions&lt;/li&gt;
&lt;li&gt;you should see the "Share via link" function&lt;/li&gt;
&lt;li&gt;create a new link&lt;/li&gt;
&lt;li&gt;change the permissions to "Anyone with the link can edit"&lt;/li&gt;
&lt;li&gt;additionally set a password &lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xyid4e5ciholma4bq7r.png" alt="password" width="417" height="294"&gt;
&lt;/li&gt;
&lt;li&gt;to configure Joplin, open its synchronisation settings and:

&lt;ol&gt;
&lt;li&gt;select "WebDAV" as "Synchronisation target"&lt;/li&gt;
&lt;li&gt;copy the last bit of the link URL (everything after the last &lt;code&gt;/&lt;/code&gt;), this is your "public-link-token"
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3f694gvskm39aavg45a.png" alt="public-link-token" width="417" height="69"&gt;
&lt;/li&gt;
&lt;li&gt;compose the "WebDAV URL" like &lt;code&gt;&amp;lt;ocis-base-URL&amp;gt;/remote.php/dav/public-files/&amp;lt;public-link-token&amp;gt;&lt;/code&gt;
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmk5sjybohrn3i9gjilip.png" alt="joplin settings" width="418" height="430"&gt;
&lt;/li&gt;
&lt;li&gt;use the "public-link-token" as "WebDAV username"&lt;/li&gt;
&lt;li&gt;add your password into the "WebDAV password" field&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;DONE!&lt;/p&gt;

&lt;p&gt;To access your notes, one would need to know the random link AND your self selected password - so it should be pretty safe.&lt;br&gt;
You could even have a different link, with a different password for every device you are using, so if you lose one device you would only have to delete that link to stop further syncing.&lt;/p&gt;

</description>
      <category>joplin</category>
      <category>cloudstorage</category>
      <category>integration</category>
      <category>owncloud</category>
    </item>
    <item>
      <title>Automated Black-Box Tests for System Settings of a Micro-service Application</title>
      <dc:creator>Artur Neumann</dc:creator>
      <pubDate>Mon, 22 May 2023 05:19:50 +0000</pubDate>
      <link>https://dev.to/jankaritech/automated-black-box-tests-for-system-settings-of-a-micro-service-application-2h61</link>
      <guid>https://dev.to/jankaritech/automated-black-box-tests-for-system-settings-of-a-micro-service-application-2h61</guid>
      <description>&lt;p&gt;oCIS is the new generation open source file-sync and sharing platform, built by &lt;a href="https://www.owncloud.com"&gt;ownCloud GmbH&lt;/a&gt;, who also created the ownCloud 10 server based on the LAMP stack. The team of &lt;a href="https://www.jankaritech.com"&gt;JankariTech&lt;/a&gt; is part of that development and mainly writes and maintains the automated tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge 🚀
&lt;/h2&gt;

&lt;p&gt;Because a lot of APIs of oCIS still have to be compatible with ownCloud 10, we have used the existing API tests and (after some adjustments) ran them on oCIS to ensure backward compatibility. I have written about that process in &lt;a href="https://dev.to/jankaritech/bdd-on-software-rewrite-6md"&gt;BDD on Software Rewrite&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This worked well for a lot of test cases, and we could quickly reach good coverage. But there were some cases where the old tests could not be reused. One of those cases is specific system-wide settings. In ownCloud 10 those settings are often set by the UI, an API call, or the designated CLI tool. Also, the changes can be done during runtime and are immediately effective. This is great for an API test: change the setting =&amp;gt; check the expected behaviour! Easy!&lt;/p&gt;

&lt;p&gt;Because of the different architecture of oCIS, system-wide settings are set through YML files or &lt;a href="https://doc.owncloud.com/ocis/next/deployment/services/env-var-note.html"&gt;environment variables&lt;/a&gt;. This is great if you run the service in a container environment, but it makes automated testing harder. Logically, the settings are only read once during the start of the service and for any change to take effect, the service needs to be restarted. How to do that as an external black-box API test?&lt;br&gt;
Not to test those settings or to rely only on manual tests was never an option.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Iteration 😵‍💫
&lt;/h2&gt;

&lt;p&gt;We first started by creating different pipelines in CI that start oCIS with defined settings and ran specific tests against that instance. That gave quick results but soon became confusing because every combination of settings needs its own CI pipeline and a designated set of tests. Additionally, that system makes it hard for developers to run the tests in their environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second Iteration 🥳
&lt;/h2&gt;

&lt;p&gt;The next iteration was to write a small wrapper for the server (of course, it's written in Go, the same as oCIS).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bYCzpb9w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fs0r9rtbp74i23yg5njm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bYCzpb9w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fs0r9rtbp74i23yg5njm.jpg" alt="Whiteboard" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This wrapper starts the oCIS server, monitors its state, and provides its own API that accepts lists of settings.&lt;br&gt;
Whenever a test wants to change a system-wide setting of oCIS it sends the wrapper the desired environment variables. The wrapper then exposes those to oCIS, restarts oCIS and reports back the success of the reconfiguration. The test can then go on and contact oCIS through its APIs and check if the new behavior is as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Nbu68pVt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ld9nw9bt7twea1rz1396.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Nbu68pVt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ld9nw9bt7twea1rz1396.png" alt="Diagram" width="800" height="749"&gt;&lt;/a&gt;&lt;br&gt;
To decouple test-cases from each other, the wrapper also provides a special endpoint to completely reset oCIS to its default state.&lt;/p&gt;

&lt;p&gt;All the kudos for this work goes to &lt;a href="https://github.com/saw-jan"&gt;Sajan Gurung&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Challenges 🧑‍💻
&lt;/h2&gt;

&lt;p&gt;As we move forward with extending the test-coverage to new APIs and various different use-cases of oCIS we face further challenges. For example, oCIS can be connected with different storage providers (POSIX filesystem, EOS, S3, ownCloud 10). We are currently exploring what would be the best way and layer to test the functionality of the storage drivers. Unit tests, Contract tests, E2E API tests, something else?&lt;/p&gt;

&lt;p&gt;I will keep posting about interesting developments in this test automation area.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>api</category>
      <category>microservices</category>
      <category>owncloud</category>
    </item>
    <item>
      <title>BDD (Behavior-driven development) mit Go</title>
      <dc:creator>Artur Neumann</dc:creator>
      <pubDate>Tue, 18 May 2021 09:34:14 +0000</pubDate>
      <link>https://dev.to/jankaritech/bdd-behavior-driven-development-mit-go-4lla</link>
      <guid>https://dev.to/jankaritech/bdd-behavior-driven-development-mit-go-4lla</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/jankaritech/einstieg-in-bdd-behavior-driven-development-1m8h"&gt;Einstieg in BDD (Behavior-driven development)&lt;/a&gt; habe ich die Grundzüge von BDD erklärt und ihren Einsatz, um die Funktionen einer Anwendung zu beschreiben. Im Grunde genommen ist BDD dazu gedacht, alle Beteiligten zusammenzubringen und klar zu beschreiben, wie sich die "Features" einer Anwendung in verschiedenen Situationen zu verhalten haben.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatische Tests
&lt;/h2&gt;

&lt;p&gt;Die Kommunikation und damit die Erfolgschancen der Entwicklung einer Anwendung zu verbessern, ist das Wichtigste bei BDD. Aber wir können noch einen Schritt weiter gehen und die entstandenen Feature-Beschreibungen nutzen, um die Anwendung automatisch zu testen.&lt;/p&gt;

&lt;p&gt;(Wie schon im ersten Artikel werde ich den Quellcode und die Feature-Dateien nicht übersetzen, sondern so zeigen, wie sie in &lt;a href="https://github.com/JankariTech/bsDateServer"&gt;GitHub&lt;/a&gt; abgespeichert sind.)&lt;/p&gt;

&lt;p&gt;Nach &lt;a href="https://dev.to/jankaritech/einstieg-in-bdd-behavior-driven-development-1m8h"&gt;"Einstieg in BDD (Behavior-driven development)"&lt;/a&gt; haben wir ein Feature-File, das so aussieht:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Feature: convert dates from BS to AD using an API
  As an app-developer in Nepal
  I want to be able to send BS dates to an API endpoint and receive the corresponding AD dates
  So that I have a simple way to convert BS to AD dates, that can be used in different apps

  Scenario: converting a valid BS date
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01"
    Then the HTTP-response code should be "200"
    And the response content should be "2003-07-17"

  Scenario: converting an invalid BS date
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01"
    Then the HTTP-response code should be "400"
    And the response content should be "not a valid date"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um aus den Feature-Files automatische Tests zu machen, brauchen wir zunächst einen Interpreter, der die Gherkin Sprache versteht und die entsprechenden Tests ausführt.&lt;/p&gt;

&lt;p&gt;Solche Interpreter gibt es für die verschiedensten Programmiersprachen. In diesem Artikel demonstriere ich, wie es mit &lt;a href="https://github.com/cucumber/godog"&gt;godog package&lt;/a&gt; für Go funktioniert.&lt;/p&gt;

&lt;p&gt;Um godog zu &lt;a href="https://github.com/cucumber/godog#install"&gt;installieren&lt;/a&gt;, müssen wir zunächst eine einfache &lt;code&gt;go.mod&lt;/code&gt; Datei anlegen&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module github.com/JankariTech/bsDateServer

go 1.19
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;und dann &lt;code&gt;go get github.com/cucumber/godog@v0.12.6&lt;/code&gt; ausführen.&lt;/p&gt;

&lt;p&gt;(Die Versionsnummer &lt;code&gt;@v0.12.6&lt;/code&gt; ist optional; ohne sie wird die neueste vorhandene Version installiert. Damit dieser Artikel aber länger verwendbar bleibt und ich ihn nicht ständig anpassen muss, gebe ich hier eine Versionsnummer an.)&lt;/p&gt;

&lt;p&gt;Wir brauchen auch das godog Kommandozeilenwerkzeug, um das zu installieren muss&lt;br&gt;
&lt;code&gt;go install github.com/cucumber/godog/cmd/godog@v0.12.6&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;ausgeführt werden&lt;/p&gt;

&lt;p&gt;Jetzt können wir godog mit &lt;code&gt;$GOPATH/bin/godog *.feature&lt;/code&gt; ausführen. Die Ausgabe sollte in etwa so aussehen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Feature: convert dates from BS to AD using an API
  As an app-developer in Nepal
  I want to be able to send BS dates to an API endpoint and receive the corresponding AD dates
  So that I have a simple way to convert BS to AD dates, that can be used in different apps

  Scenario: converting a valid BS date                                    # bs-to-ad-conversion.feature:6
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01"
    Then the HTTP-response code should be "200"
    And the response content should be "2003-07-17"

  Scenario: converting an invalid BS date                               # bs-to-ad-conversion.feature:11
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01"
    Then the HTTP-response code should be "400"
    And the response content should be "not a valid date"

2 scenarios (2 undefined)
6 steps (6 undefined)
441.226µs

You can implement step definitions for undefined steps with these snippets:

func aRequestIsSentToTheEndpoint(arg1, arg2 string) error {
    return godog.ErrPending
}

func theHTTPresponseCodeShouldBe(arg1 string) error {
    return godog.ErrPending
}

func theResponseContentShouldBe(arg1 string) error {
    return godog.ErrPending
}

func InitializeScenario(ctx *godog.ScenarioContext) {
    ctx.Step(`^a "([^"]*)" request is sent to the endpoint "([^"]*)"$`, aRequestIsSentToTheEndpoint)
    ctx.Step(`^the HTTP-response code should be "([^"]*)"$`, theHTTPresponseCodeShouldBe)
    ctx.Step(`^the response content should be "([^"]*)"$`, theResponseContentShouldBe)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Godog listet alle Szenarien, die wir ausführen wollen, und sagt uns, dass es keine Ahnung hat, was es machen soll. Das ist keine Überraschung - schließlich haben wir noch keine Test-Schritte implementiert. Um das zu tun, erstellen wir eine Datei mit dem Namen &lt;code&gt;bsdateServer_test.go&lt;/code&gt; und dem Inhalt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "github.com/cucumber/godog"
)

func aRequestIsSentToTheEndpoint(arg1, arg2 string) error {
    return godog.ErrPending
}

func theHTTPresponseCodeShouldBe(arg1 string) error {
    return godog.ErrPending
}

func theResponseContentShouldBe(arg1 string) error {
    return godog.ErrPending
}

func InitializeScenario(ctx *godog.ScenarioContext) {
    ctx.Step(`^a "([^"]*)" request is sent to the endpoint "([^"]*)"$`, aRequestIsSentToTheEndpoint)
    ctx.Step(`^the HTTP-response code should be "([^"]*)"$`, theHTTPresponseCodeShouldBe)
    ctx.Step(`^the response content should be "([^"]*)"$`, theResponseContentShouldBe)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Die &lt;code&gt;InitializeScenario&lt;/code&gt; Funktion ist die Verbindung zwischen der menschenlesbaren Gherkin Sprache und dem Code, den der Computer ausführen soll. Mithilfe von RegularExpressions werden Teile der Sätze aus der Gherkin Zeile extrahiert und als Argumente an die jeweilige Funktion gesendet.&lt;br&gt;
Aus &lt;code&gt;When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01"&lt;/code&gt; wird der Funktionsaufruf: &lt;code&gt;aRequestIsSentToTheEndpoint("GET", "/ad-from-bs/2060-04-01")&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Wenn wir wieder &lt;code&gt;$GOPATH/bin/godog *.feature&lt;/code&gt; ausführen, sieht die Ausgabe schon anders aus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Feature: convert dates from BS to AD using an API
  As an app-developer in Nepal
  I want to be able to send BS dates to an API endpoint and receive the corresponding AD dates
  So that I have a simple way to convert BS to AD dates, that can be used in different apps

  Scenario: converting a valid BS date                                    # bs-to-ad-conversion.feature:6
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01" # bsdateServer_test.go:8 -&amp;gt; aRequestIsSentToTheEndpoint
      TODO: write pending definition
    Then the HTTP-response code should be "200"                           # bsdateServer_test.go:12 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "2003-07-17"                       # bsdateServer_test.go:16 -&amp;gt; theResponseContentShouldBe

  Scenario: converting an invalid BS date                               # bs-to-ad-conversion.feature:11
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01" # bsdateServer_test.go:8 -&amp;gt; aRequestIsSentToTheEndpoint
      TODO: write pending definition
    Then the HTTP-response code should be "400"                         # bsdateServer_test.go:12 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "not a valid date"               # bsdateServer_test.go:16 -&amp;gt; theResponseContentShouldBe

2 scenarios (2 pending)
6 steps (2 pending, 4 skipped)
576.1µs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Godog hat jetzt die Funktionen gefunden, die mit den jeweiligen Schritten korrespondieren, aber diese Funktionen tun, außer Fehler anzuzeigen, noch nichts.&lt;/p&gt;

&lt;p&gt;Also implementieren wir die erste Funktion, die die HTTP Anfrage and unsere (noch nicht vorhandene) API sendet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;index c8b0144..f7ee56d 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/bsdateServer_test.go
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/bsdateServer_test.go
&lt;/span&gt;&lt;span class="p"&gt;@@ -1,11 +1,26 @@&lt;/span&gt;
 package main
&lt;span class="err"&gt;
&lt;/span&gt; import (
&lt;span class="gi"&gt;+    "fmt"
&lt;/span&gt;     "github.com/cucumber/godog"
&lt;span class="gi"&gt;+    "net/http"
+    "strings"
&lt;/span&gt; )
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-func aRequestIsSentToTheEndpoint(arg1, arg2 string) error {
-    return godog.ErrPending
&lt;/span&gt;&lt;span class="gi"&gt;+var host = "http://localhost:10000"
+var res *http.Response
+
+func aRequestIsSentToTheEndpoint(method, endpoint string) error {
+    var reader = strings.NewReader("")
+    var request, err = http.NewRequest(method, host+endpoint, reader)
+    if err != nil {
+        return fmt.Errorf("could not create request %s", err.Error())
+    }
+    res, err = http.DefaultClient.Do(request)
+    if err != nil {
+        return fmt.Errorf("could not send request %s", err.Error())
+    }
+    return nil
&lt;/span&gt; }
&lt;span class="err"&gt;
&lt;/span&gt; func theHTTPresponseCodeShouldBe(arg1 string) error {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wir benutzen das &lt;code&gt;net/http&lt;/code&gt; Go packet, um eine einfache HTTP Anfrage zu versenden. Der Trick bei godog ist, &lt;code&gt;nil&lt;/code&gt; zurückzugeben, wenn kein Fehler aufgetreten ist. Das führt dazu, dass godog den Schritt als erfolgreich bewertet. Auf der anderen Seite wird ein Schritt als gescheitert markiert, wenn die Funktion irgendein Objekt zurückgibt, das die &lt;code&gt;error&lt;/code&gt; Schnittstelle (Interface) implementiert.&lt;/p&gt;

&lt;p&gt;Randbemerkung: die &lt;code&gt;res&lt;/code&gt; Variable ist außerhalb der Funktion definiert, weil wir auf sie noch von anderen Funktionen zugreifen müssen.&lt;/p&gt;

&lt;p&gt;Die Ausgabe von &lt;code&gt;$GOPATH/bin/godog *.feature&lt;/code&gt; ist jetzt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
  Scenario: converting a valid BS date                                    # bs-to-ad-conversion.feature:6
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01" # bsdateServer_test.go:13 -&amp;gt; aRequestIsSentToTheEndpoint
    could not send request Get "http://localhost:10000/ad-from-bs/2060-04-01": dial tcp 127.0.0.1:10000: connect: connection refused
    Then the HTTP-response code should be "200"                           # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "2003-07-17"                       # bsdateServer_test.go:31 -&amp;gt; theResponseContentShouldBe

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Die HTTP Anfrage, die der Test sendet, schlägt fehl, weil nichts auf dem entsprechenden Port lauscht. Ganz Ähnlich wie bei TDD (Test Driven Development) haben wir erst den Test gebaut (oder einen Teil davon), bevor die Software implementiert wurde.&lt;/p&gt;

&lt;p&gt;Deswegen implementieren wir jetzt einen minimal-Server, der praktisch nur den Port &lt;code&gt;10000&lt;/code&gt; öffnet. Dafür kommt der folgende code in die Datei &lt;code&gt;main.go&lt;/code&gt; und dann wird der Server mit &lt;code&gt;go run main.go&lt;/code&gt; gestartet&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
)

func homePage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Bikram Sambat Server")
}

func handleRequests() {
    myRouter := mux.NewRouter().StrictSlash(true)
    myRouter.HandleFunc("/", homePage)
    log.Fatal(http.ListenAndServe(":10000", myRouter))
}

func main() {
    handleRequests()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wenn wir jetzt die Tests laufen lassen, während der Server läuft, sieht man, dass wir einen Schritt weiter gekommen sind:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Scenario: converting a valid BS date                                    # bs-to-ad-conversion.feature:6
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01" # bsdateServer_test.go:13 -&amp;gt; aRequestIsSentToTheEndpoint
    Then the HTTP-response code should be "200"                           # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
      TODO: write pending definition
    And the response content should be "2003-07-17"                       # bsdateServer_test.go:31 -&amp;gt; theResponseContentShouldBe

  Scenario: converting an invalid BS date                               # bs-to-ad-conversion.feature:11
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01" # bsdateServer_test.go:13 -&amp;gt; aRequestIsSentToTheEndpoint
    Then the HTTP-response code should be "400"                         # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
      TODO: write pending definition
    And the response content should be "not a valid date"               # bsdateServer_test.go:31 -&amp;gt; theResponseContentShouldBe

2 scenarios (2 pending)
6 steps (2 passed, 2 pending, 2 skipped)
1.849695ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Die &lt;code&gt;When&lt;/code&gt; Schritte funktionieren jetzt wie gewünscht. Als Nächstes müssen die &lt;code&gt;Then&lt;/code&gt; Schritte implementiert werden:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;--- a/bsdateServer_test.go
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/bsdateServer_test.go
&lt;/span&gt;&lt;span class="p"&gt;@@ -3,6 +3,7 @@&lt;/span&gt; package main
 import (
     "fmt"
     "github.com/cucumber/godog"
&lt;span class="gi"&gt;+    "io/ioutil"
&lt;/span&gt;     "net/http"
     "strings"
 )
&lt;span class="p"&gt;@@ -23,16 +24,23 @@&lt;/span&gt; func aRequestIsSentToTheEndpoint(method, endpoint string) error {
     return nil
 }
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-func theHTTPresponseCodeShouldBe(arg1 string) error {
-    return godog.ErrPending
&lt;/span&gt;&lt;span class="gi"&gt;+func theHTTPresponseCodeShouldBe(expectedCode int) error {
+    if expectedCode != res.StatusCode {
+        return fmt.Errorf("status code not as expected! Expected '%d', got '%d'", expectedCode, res.StatusCode)
+    }
+    return nil
&lt;/span&gt; }
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-func theResponseContentShouldBe(arg1 string) error {
-    return godog.ErrPending
&lt;/span&gt;&lt;span class="gi"&gt;+func theResponseContentShouldBe(expectedContent string) error {
+    body, _ := ioutil.ReadAll(res.Body)
+    if expectedContent != string(body) {
+        return fmt.Errorf("status code not as expected! Expected '%s', got '%s'", expectedContent, string(body))
+    }
+    return nil
&lt;/span&gt; }
&lt;span class="err"&gt;
&lt;/span&gt; func InitializeScenario(ctx *godog.ScenarioContext) {
     ctx.Step(`^a "([^"]*)" request is sent to the endpoint "([^"]*)"$`, aRequestIsSentToTheEndpoint)
&lt;span class="gd"&gt;-    ctx.Step(`^the HTTP-response code should be "([^"]*)"$`, theHTTPresponseCodeShouldBe)
&lt;/span&gt;&lt;span class="gi"&gt;+    ctx.Step(`^the HTTP-response code should be "(\d+)"$`, theHTTPresponseCodeShouldBe)
&lt;/span&gt;     ctx.Step(`^the response content should be "([^"]*)"$`, theResponseContentShouldBe)
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hier lesen wir den HTTP Status Code und den Inhalt aus der HTTP Antwort und vergleichen die Werte mit den Erwartungen. Sollten die Resultate nicht mit den Erwartungen übereinstimmen, wird ein Fehler zurückgegeben.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Randnotiz: Es ist wichtig, gute Fehlermeldungen auszugeben. Das Ziel einer Fehlermeldung ist es, dem Entwickler die Fehlersuche zu erleichtern. Die Ausgabe des Tests muss den Entwickler zum Fehler führen. Diese Tests sollen schließlich nicht nur in der Entstehungsphase des Projekts benutzt werden, sondern auch später, um Regressionen zu vermeiden.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Die kleine Änderung in der Regular-Expression in &lt;code&gt;InitializeScenario&lt;/code&gt; stellt sicher, dass nur Zahlen als HTTP Status Code akzeptiert werden.&lt;/p&gt;

&lt;p&gt;Die Ausgabe der Tests ist jetzt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
  Scenario: converting a valid BS date # bs-to-ad-conversion.feature:6
    Then the HTTP-response code should be "200" # bs-to-ad-conversion.feature:8
      Error: status code not as expected! Expected '200', got '404'

  Scenario: converting an invalid BS date # bs-to-ad-conversion.feature:11
    Then the HTTP-response code should be "400" # bs-to-ad-conversion.feature:13
      Error: status code not as expected! Expected '400', got '404'


2 scenarios (2 failed)
6 steps (2 passed, 2 failed, 2 skipped)
1.766438ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Das war zu erwarten; &lt;code&gt;/ad-from-bs/&lt;/code&gt; existiert noch nicht. Es ist an der Zeit, die API an sich zu implementieren.&lt;/p&gt;

&lt;p&gt;Hier die Änderungen in &lt;code&gt;main.go&lt;/code&gt; für eine einfache Konvertierung eines Bikram Sambat Datums in ein gregorianisches Datum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;index ae01ed0..06299b0 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/main.go
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/main.go
&lt;/span&gt;&lt;span class="p"&gt;@@ -2,18 +2,34 @@&lt;/span&gt; package main
&lt;span class="err"&gt;
&lt;/span&gt; import (
        "fmt"
&lt;span class="gi"&gt;+       "github.com/JankariTech/GoBikramSambat"
&lt;/span&gt;        "github.com/gorilla/mux"
        "log"
        "net/http"
&lt;span class="gi"&gt;+       "strconv"
+       "strings"
&lt;/span&gt; )
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+func getAdFromBs(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       dateString := vars["date"]
+       var splitedDate = strings.Split(dateString, "-")
+       day, _ := strconv.Atoi(splitedDate[2])
+       month, _ := strconv.Atoi(splitedDate[1])
+       year, _ := strconv.Atoi(splitedDate[0])
+       date, _ := bsdate.New(day, month, year)
+       gregorianDate, _ := date.GetGregorianDate()
+       fmt.Fprintf(w, gregorianDate.Format("2006-01-02"))
+}
+
&lt;/span&gt; func handleRequests() {
        myRouter := mux.NewRouter().StrictSlash(true)
        myRouter.HandleFunc("/", homePage)
&lt;span class="gi"&gt;+       myRouter.HandleFunc("/ad-from-bs/{date}", getAdFromBs)
&lt;/span&gt;        log.Fatal(http.ListenAndServe(":10000", myRouter))
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Die Änderung ist eigentlich recht simpel: das BS Datum in Tag, Monat und Jahr aufspalten und es an die fertige &lt;code&gt;GoBikramSambat&lt;/code&gt; Bibliothek übergeben. (Die Bibliothek wird mit &lt;code&gt;go get github.com/JankariTech/GoBikramSambat&lt;/code&gt; installiert)&lt;/p&gt;

&lt;p&gt;Und damit funktioniert schon das erste Szenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
  Scenario: converting a valid BS date                                    # bs-to-ad-conversion.feature:6
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01" # bsdateServer_test.go:14 -&amp;gt; aRequestIsSentToTheEndpoint
    Then the HTTP-response code should be "200"                           # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "2003-07-17"                       # bsdateServer_test.go:34 -&amp;gt; theResponseContentShouldBe

  Scenario: converting an invalid BS date                               # bs-to-ad-conversion.feature:11
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01" # bsdateServer_test.go:14 -&amp;gt; aRequestIsSentToTheEndpoint
    could not send request Get "http://localhost:10000/ad-from-bs/60-13-01": EOF
    Then the HTTP-response code should be "400"                         # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "not a valid date"               # bsdateServer_test.go:34 -&amp;gt; theResponseContentShouldBe

--- Failed steps:

  Scenario: converting an invalid BS date # bs-to-ad-conversion.feature:11
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01" # bs-to-ad-conversion.feature:12
      Error: could not send request Get "http://localhost:10000/ad-from-bs/60-13-01": EOF


2 scenarios (1 passed, 1 failed)
6 steps (3 passed, 1 failed, 2 skipped)
2.035601ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mit ein paar kleinen Änderungen zur Behandlung von Fehlern sollte das zweite Scenario auch funktionieren:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;index 8243aef..2850678 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/main.go
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/main.go
&lt;/span&gt;&lt;span class="p"&gt;@@ -21,7 +21,11 @@&lt;/span&gt; func getAdFromBs(w http.ResponseWriter, r *http.Request) {
     day, _ := strconv.Atoi(splitedDate[2])
     month, _ := strconv.Atoi(splitedDate[1])
     year, _ := strconv.Atoi(splitedDate[0])
&lt;span class="gd"&gt;-    date, _ := bsdate.New(day, month, year)
&lt;/span&gt;&lt;span class="gi"&gt;+    date, err := bsdate.New(day, month, year)
+    if err != nil {
+        http.Error(w, err.Error(), http.StatusBadRequest)
+        return
+    }
&lt;/span&gt;     gregorianDate, _ := date.GetGregorianDate()
     fmt.Fprintf(w, gregorianDate.Format("2006-01-02"))
 }
&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="gh"&gt;index b731d6d..9871219 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/bsdateServer_test.go
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/bsdateServer_test.go
&lt;/span&gt;&lt;span class="p"&gt;@@ -33,7 +33,7 @@&lt;/span&gt; func theHTTPresponseCodeShouldBe(expectedCode int) error {
&lt;span class="err"&gt;
&lt;/span&gt; func theResponseContentShouldBe(expectedContent string) error {
     body, _ := ioutil.ReadAll(res.Body)
&lt;span class="gd"&gt;-    if expectedContent != string(body) {
&lt;/span&gt;&lt;span class="gi"&gt;+    if expectedContent != strings.TrimSpace(string(body)) {
&lt;/span&gt;         return fmt.Errorf("status code not as expected! Expected '%s', got '%s'", expectedContent, string(body))
     }
     return nil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sollte die Konvertierung nicht funktionieren, wird jetzt in &lt;code&gt;main.go&lt;/code&gt; ein Fehler ausgegeben. In den Tests benutzen wir &lt;code&gt;TrimSpace&lt;/code&gt;, weil &lt;code&gt;http.Error&lt;/code&gt; ein &lt;code&gt;\n&lt;/code&gt; an den Fehlertext hängt.&lt;/p&gt;

&lt;p&gt;Nun sollten beide Szenarien grün sein:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Feature: convert dates from BS to AD using an API
  As an app-developer in Nepal
  I want to be able to send BS dates to an API endpoint and receive the corresponding AD dates
  So that I have a simple way to convert BS to AD dates, that can be used in different apps

  Scenario: converting a valid BS date                                    # bs-to-ad-conversion.feature:6
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01" # bsdateServer_test.go:14 -&amp;gt; aRequestIsSentToTheEndpoint
    Then the HTTP-response code should be "200"                           # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "2003-07-17"                       # bsdateServer_test.go:34 -&amp;gt; theResponseContentShouldBe

  Scenario: converting an invalid BS date                               # bs-to-ad-conversion.feature:11
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01" # bsdateServer_test.go:14 -&amp;gt; aRequestIsSentToTheEndpoint
    Then the HTTP-response code should be "400"                         # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "not a valid date"               # bsdateServer_test.go:34 -&amp;gt; theResponseContentShouldBe

2 scenarios (2 passed)
6 steps (6 passed)
1.343415ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Beispiel-Tabellen (Examples)
&lt;/h2&gt;

&lt;p&gt;Um sicherzustellen, dass die Konvertierung richtig funktioniert, sollten wir noch mehr verschiedene Daten testen. Grundsätzlich sind beim Testen oft diese Dinge interessant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;höchste und niedrigste möglichen Werte - da die &lt;a href="https://dev.to/jankaritech/demonstrating-tdd-test-driven-development-in-go-27b0"&gt;Umrechnung zwischen BS und AD auf Tabellen beruht&lt;/a&gt;, haben wir ein erstes Datum, das wir konvertieren können, und ein letztes; darüber hinaus ist keine Umrechnung möglich &lt;/li&gt;
&lt;li&gt;Übergänge - zwischen Monaten und Jahren&lt;/li&gt;
&lt;li&gt;andere besondere Fälle - Schaltjahre&lt;/li&gt;
&lt;li&gt;falsche Eingaben - dreizehnter Monat, 32ter Tag, usw.&lt;/li&gt;
&lt;li&gt;falsches Format - in unserem Fall z.b. &lt;code&gt;2012.12.03&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wir könnten für jeden Fall ein eigenes Szenario schreiben, aber das würde zu vielen Wiederholungen führen und die Datei schnell unübersichtlich machen. Besser ist es, mit dem &lt;code&gt;Examples&lt;/code&gt; Schlüsselwort Beispiel-Tabellen anzulegen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;index 33f5d6c..9003cff 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/bs-to-ad-conversion.feature
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/bs-to-ad-conversion.feature
&lt;/span&gt;&lt;span class="p"&gt;@@ -3,10 +3,15 @@&lt;/span&gt; Feature: convert dates from BS to AD using an API
   I want to be able to send BS dates to an API endpoint and receive the corresponding AD dates
   So that I have a simple way to convert BS to AD dates, that can be used in different apps
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-  Scenario: converting a valid BS date
-    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01"
&lt;/span&gt;&lt;span class="gi"&gt;+  Scenario Outline: converting a valid BS date
+    When a "GET" request is sent to the endpoint "/ad-from-bs/&amp;lt;bs-date&amp;gt;"
&lt;/span&gt;     Then the HTTP-response code should be "200"
&lt;span class="gd"&gt;-    And the response content should be "2003-07-17"
&lt;/span&gt;&lt;span class="gi"&gt;+    And the response content should be "&amp;lt;ad-date&amp;gt;"
+    Examples:
+      | bs-date    | ad-date    |
+      | 2060-04-01 | 2003-07-17 |
+      | 2040-01-01 | 1983-04-14 |
+      | 2040-12-30 | 1984-04-12 |
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anstatt &lt;code&gt;Scenario&lt;/code&gt; benutzen wir hier &lt;code&gt;Scenario Outline&lt;/code&gt; als Schlüsselwort und am Ende des Szenarios ist eine Tabelle angefügt. Die Überschriften der Spalten werden als Variablennamen benutzt und in den Test-Schritten, in denen die Namen vorkommen, werden diese durch die Werte aus der Tabelle ersetzt.&lt;br&gt;
Godog erstellt damit aus jeder Tabellenzeile ein separates Szenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zusammenfassung
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Die gewünschten Erwartungen an die Software in Gherkin Syntax niederzuschreiben, kann die Kommunikation zwischen allen Beteiligten verbessern und damit die Chancen auf den Erfolg des Projekts drastisch verbessern.&lt;/li&gt;
&lt;li&gt;Die Beschreibungen der Funktionen werden zur Dokumentation.&lt;/li&gt;
&lt;li&gt;Zusätzlich können die gleichen Beschreibungen benutzt werden, um die Software automatisch zu testen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wir helfen gerne bei der Umstellung auf BDD und der Erstellung von automatischen Tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.jankaritech.com/"&gt;https://www.jankaritech.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/company/jankaritech/"&gt;https://www.linkedin.com/company/jankaritech/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>go</category>
      <category>bdd</category>
      <category>test</category>
    </item>
    <item>
      <title>Einstieg in BDD (Behavior-driven development)</title>
      <dc:creator>Artur Neumann</dc:creator>
      <pubDate>Wed, 31 Mar 2021 11:05:39 +0000</pubDate>
      <link>https://dev.to/jankaritech/einstieg-in-bdd-behavior-driven-development-1m8h</link>
      <guid>https://dev.to/jankaritech/einstieg-in-bdd-behavior-driven-development-1m8h</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/jankaritech/demonstrating-tdd-test-driven-development-in-go-27b0"&gt;Demonstrating TDD (Test-driven development) in Go&lt;/a&gt; habe ich über TDD geschrieben und jetzt möchte ich zeigen wie BDD (Behavior-driven development) mit Go funktionieren kann.&lt;br&gt;
In diesem Artikel gehe ich auf die Grundlagen von BDD ein und darauf, wie die Funktionen einer Applikation beschrieben werden. Der nächste Artikel wird dann technischer sein und sich darum drehen, wie man BDD Tests automatisch in einer Golang Umgebung laufen lässt.&lt;/p&gt;

&lt;p&gt;Beide Artikel wurden bereits auf Englisch veröffentlicht &lt;a href="https://dev.to/jankaritech/demonstrating-bdd-behavior-driven-development-in-go-1eci"&gt;Demonstrating BDD (Behavior-driven development) in Go&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anstatt BDD in Theorie zu beschreiben, werde ich praktische Beispiele benutzen, um die Prinzipien von BDD zu erklären.&lt;br&gt;
Hier ein paar weiterführende Links zu BDD:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Behavior-driven_development"&gt;Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://inviqa.com/blog/bdd-guide"&gt;"The beginner's guide to BDD (behaviour-driven development)", By Konstantin Kudryashov, Alistair Stead, Dan North&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cucumber.io/docs/bdd/"&gt;Behaviour-Driven Development&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wenn du weitere gute Ressourcen kennst, poste sie bitte in den Kommentaren.&lt;/p&gt;
&lt;h2&gt;
  
  
  Die Idee
&lt;/h2&gt;

&lt;p&gt;Ich bin ein großer Fan davon, Dinge an anschaulichen Beispielen zu erklären. Daher habe ich in &lt;a href="https://dev.to/jankaritech/demonstrating-tdd-test-driven-development-in-go-27b0"&gt;Demonstrating TDD (Test-driven development) in Go&lt;/a&gt; eine kleine Bibliothek gebaut, um Daten aus dem in Nepal benutzten Kalender (Bikram Sambat (BS), auch als Vikram Samvat bekannt) in gregorianische Daten umzuwandeln.&lt;br&gt;
Diese Bibliothek verwende ich jetzt und baue eine API drumherum. (Das Projekt ist bei &lt;a href="https://github.com/JankariTech/bsDateServer"&gt;github&lt;/a&gt; zu finden)&lt;/p&gt;

&lt;p&gt;Man könnte diese "Anforderung" auch an einen Entwickler geben und sehen, was passiert. Bei einem solch kleinen Projekt kann das gut gehen, aber es kann auch viel schief laufen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;die API wird sehr komplex&lt;/li&gt;
&lt;li&gt;die API kann Daten umwandeln, aber behandelt Fehler nicht richtig&lt;/li&gt;
&lt;li&gt;usw.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wenn es schief läuft, werden viele Ressourcen verschwendet, Konflikte und Missverständnisse entstehen, usw. Es wäre also viel besser, die Anforderungen detaillierter aufzuschreiben, um das zu vermeiden. Dabei profitieren alle Beteiligten:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Als Kunde will man, dass die Applikation korrekt arbeitet (manchmal ohne zu wissen, was das genau bedeutet).&lt;/li&gt;
&lt;li&gt;Als Entwickler will man genau das entwickeln, was gefordert ist (um Zeit zu sparen), und am Ende will man bezahlt werden.&lt;/li&gt;
&lt;li&gt;Als Tester möchte man genau wissen, was zu testen ist und ob ein Verhalten ein Bug oder ein Feature ist.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Im Grunde genommen will man alle beteiligten Parteien (und es können mehr als die drei genannten sein) dazu bringen, ihre Erwartungen zu kommunizieren und sich auf das akzeptable Verhalten der Anwendung zu einigen. Und das ist die grundlegende Idee hinter BDD: die Kommunikation zwischen allen Beteiligten zu verbessern, um sicherzustellen, dass jeder weiß, worüber geredet wird. &lt;/p&gt;

&lt;p&gt;Aber wie erreicht man das? Der Kunde könnte denken, dass eine Zeile zur Erklärung reicht: "API um Daten von BS zu AD und AD zu BS zu konvertieren". Der Manager möchte einen wasserdichten Vertrag und der Entwickler sagt: "Der Quellcode genügt als Dokumentation."&lt;br&gt;
Eine gute Möglichkeit, das gemeinsame Verständnis zu verbessern, ist das Aufschreiben der Anforderungen in der Gherkin-Sprache. Gherkin ist eine teilstrukturierte Sprache, die so einfach ist, dass sogar eine Gurke sie verstehen kann.&lt;/p&gt;
&lt;h2&gt;
  
  
  Wer will was und wozu erreichen
&lt;/h2&gt;

&lt;p&gt;Im Hauptverzeichnis unseres Projekts erstellen wir als Erstes eine Datei mit dem Namen &lt;code&gt;bs-to-ad-conversion.feature&lt;/code&gt;. In dieser Datei werden wir beschreiben, wie die Konvertierung in die eine Richtung funktionieren soll.&lt;br&gt;
Jede Funktion sollte in einer eigenen Datei beschrieben werden.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Randnotiz: es wird immer wieder darüber diskutiert, was eine Funktion / ein Feature ist. Konkret in unsrem Beispiel: Ist die Konvertierung in beide Richtungen ein oder zwei Features? Ist die Behandlung von Fehlern ein separates Feature oder ein Teil der Konvertierung? Wenn ich mir nicht sicher bin, ist die Lösung für mich oft einfach pragmatisch: Die Datei sollte nicht zu lang sein.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Wir fangen mit einer allgemeinen Beschreibung der Funktion an: &lt;br&gt;
(Ich werde den Quellcode und die Feature-Dateien nicht übersetzen, sondern so zeigen, wie sie in GitHub abgespeichert sind. Aber Gherkin kann in jeder beliebigen Sprache verwendet werden. Je nachdem, was die gemeinsame Sprache der Beteiligten ist.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Feature: convert dates from BS to AD using an API
  As an app-developer in Nepal
  I want to be able to send BS dates to an API endpoint and receive the corresponding AD dates
  So that I have a simple way to convert BS to AD dates, that can be used in different apps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Diese Zeilen sind sehr wichtig, weil sie die Fragen beantworten WER WAS und WOFÜR erreichen möchte. Wenn es niemanden gibt, der die Funktion benutzen möchte, warum sollte man die Funktion dann implementieren? Wenn man nicht weiß, was gemacht werden soll, gibt es eigentlich nichts zu programmieren. Und wenn es keinen übergeordneten Zweck für die Funktion gibt, hat sie keinen Wert.&lt;br&gt;
Falls die Beteiligten (Entwickler, Kunden, Manager, QA, usw.) diese drei Fragen nicht beantworten können, sollte niemand Zeit und Geld investieren, um die Funktion zu implementieren.&lt;/p&gt;
&lt;h2&gt;
  
  
  Szenarien
&lt;/h2&gt;

&lt;p&gt;Jede Funktion hat verschiedene Szenarien. Die Funktion "einen Artikel in den Warenkorb legen" in einem online shop kann diese Szenarien haben:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;einen Artikel in den Warenkorb legen, wenn der Benutzer angemeldet ist&lt;/li&gt;
&lt;li&gt;einen Artikel in den Warenkorb legen, wenn der Benutzer nicht angemeldet ist&lt;/li&gt;
&lt;li&gt;einen Artikel in den Warenkorb legen, wenn der Warenkorb leer ist&lt;/li&gt;
&lt;li&gt;einen Artikel in den Warenkorb legen, wenn sich schon Waren im Warenkorb befinden&lt;/li&gt;
&lt;li&gt;mehrere Artikel auf einmal in den Warenkorb legen&lt;/li&gt;
&lt;li&gt;usw.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gut möglich, dass sich die Anwendung in jedem Szenario anders zu verhalten hat. Ist das spezifische Verhalten für einen der Beteiligten wichtig, dann sollte es auch beschrieben werden.&lt;/p&gt;

&lt;p&gt;In Gherkin fängt jedes Szenario mit Schlüsselwort &lt;code&gt;Scenario&lt;/code&gt; an und es folgt ein kurzer, frei wählbarer Text, der das Szenario beschreibt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Scenario: converting a valid BS date

  Scenario: converting an invalid BS date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Given, When, Then
&lt;/h2&gt;

&lt;p&gt;Als Nächstes müssen wir das gewünschte Verhalten der Anwendung in diesem Szenario beschreiben. Dafür kennt Gherkin drei Schlüsselwörter: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Given&lt;/strong&gt; - die Voraussetzungen für das Szenario&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When&lt;/strong&gt; - die Aktion, die getestet werden soll&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then&lt;/strong&gt; - das gewünschte Ergebnis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zusätzlich kennt Gherkin auch das &lt;strong&gt;And&lt;/strong&gt; Schlüsselwort. Um die anderen Schlüsselwörter nicht zu wiederholen, kann man statt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;When doing A
When doing B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;das &lt;code&gt;And&lt;/code&gt; Schlüsselwort benutzen&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; When doing A
 And doing B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In den meisten Fällen müssen irgendwelche Schritte ausgeführt werden, um die Anwendung in einen Zustand zu bringen, in dem die entsprechende Funktion getestet werden kann. Das kann z.B. das Anlegen von Benutzern oder das Navigieren zu einer bestimmten Stelle sein. Um die Anwendung in den testbaren Zustand zu bringen, wird das &lt;code&gt;Given&lt;/code&gt; Schlüsselwort benutzt.&lt;br&gt;
Für die Beispielanwendung in diesem Blog kommt mir da aber nichts in den Sinn, und so gehen wir jetzt weiter zum &lt;code&gt;When&lt;/code&gt; Schlüsselwort.&lt;/p&gt;

&lt;p&gt;Das &lt;code&gt;When&lt;/code&gt; Schlüsselwort ist die Aktion die getestet werden soll.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Scenario: converting a valid BS date
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01"

  Scenario: converting an invalid BS date
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-13-01"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Was soll nun in den spezifischen Szenarien passieren? Was ist das Resultat, das der Benutzer beobachten kann? Wir benutzen das &lt;code&gt;Then&lt;/code&gt; Schlüsselwort, um die Resultate zu beschreiben.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Scenario: converting a valid BS date
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01"
    Then the HTTP-response code should be "200"
    And the response content should be "2003-07-17"

  Scenario: converting an invalid BS date
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01"
    Then the HTTP-response code should be "400"
    And the response content should be "not a valid date"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Damit haben wir alle Zutaten zusammen, um die Applikation zu beschreiben:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Funktionen - eine Funktion pro Datei.&lt;/li&gt;
&lt;li&gt;Szenarien - verschiedene Weisen, wie die Funktion sich verhalten soll.&lt;/li&gt;
&lt;li&gt;Schritte - detaillierte Beschreibung jedes Szenarios. Jeder Schritt startet mit &lt;code&gt;Given&lt;/code&gt;, &lt;code&gt;When&lt;/code&gt; oder &lt;code&gt;Then&lt;/code&gt; als Schlüsselwort.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Die komplette Beschreibung muss in natürlicher Sprache verfasst sein. Alle Beteiligten müssen die Beschreibung verstehen können. Was das genau bedeutet, ist ein Thema für einen separaten Blogpost. In unserem Fall hat der "Kunde" eine API bestellt, also sind technische Ausdrücke wie "HTTP-response code" meiner Meinung nach in Ordnung. Die Beschreibung einer grafischen Benutzeroberfläche würde wahrscheinlich weniger technische Begriffe enthalten. Grundsätzlich gilt: Benutze Worte die alle Beteiligten verstehen. BDD ist schließlich hauptsächlich dazu da, die Kommunikation zu verbessern.&lt;/p&gt;

&lt;p&gt;Für weitere Informationen, wie die Schritte formuliert werden sollen, siehe: &lt;a href="https://cucumber.io/docs/gherkin/reference/"&gt;https://cucumber.io/docs/gherkin/reference/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nachdem eine Funktion oder auch nur ein Szenario beschrieben wurde, kann die Entwicklung starten. In SCRUM-Sprache: jedes Feature ist eine user-story. Der ganze agile Zyklus funktioniert damit. Diese Beschreibung ist nicht nur die To-Do-Liste für die Entwicklung, sondern auch das Testprozedere für die Qualitätssicherung und sie dient auch als Dokumentation.&lt;/p&gt;

&lt;p&gt;Im zweiten Teil des Blogs tauchen wir dann in die technischen Details ein und benutzen die Feature-Dateien, um die Anwendung automatisch zu testen.&lt;/p&gt;

</description>
      <category>bdd</category>
      <category>testing</category>
    </item>
    <item>
      <title>Stay healthy and drink chiya 🍵</title>
      <dc:creator>Artur Neumann</dc:creator>
      <pubDate>Fri, 19 Feb 2021 08:23:14 +0000</pubDate>
      <link>https://dev.to/jankaritech/stay-healthy-and-drink-chiya-3bnl</link>
      <guid>https://dev.to/jankaritech/stay-healthy-and-drink-chiya-3bnl</guid>
      <description>&lt;p&gt;One thing, that you see in offices in Nepal 🇳🇵 at least once a day, is that someone makes chiya (milk tea) and brings it around to every desk.&lt;br&gt;
That is a really good customs, because chiya keeps your brain working [Citation needed].&lt;/p&gt;

&lt;p&gt;Still at &lt;a href="https://dev.to/jankaritech/"&gt;JankariTech&lt;/a&gt; we introduced the policy that it's not permitted to deliver the drink to the desk. The reason for that policy is: we are sitting too much, and that makes us sick. To get up and to get the chiya from the kitchen gives you a good reason to move, to stretch your muscles and to activate the cardio vascular system&lt;/p&gt;

&lt;p&gt;Even though all staff of &lt;a href="https://www.jankaritech.com"&gt;JankariTech&lt;/a&gt; are still in home-offices at the moment, that policy still applies! So don't let anyone deliver you the chiya to the desk, and if anyone still does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;⬆️ get up&lt;/li&gt;
&lt;li&gt;🚶‍♀️🖥 follow that person back to the kitchen&lt;/li&gt;
&lt;li&gt;🤗 give the person a hug or at least say something nice&lt;/li&gt;
&lt;li&gt;🖥🚶‍♀️ walk back to your desk&lt;/li&gt;
&lt;li&gt;🍵 enjoy the chiya&lt;/li&gt;
&lt;li&gt;👩‍💻 keep on coding&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;STAY HEALTHY AND DRINK CHIYA&lt;/p&gt;

&lt;p&gt;P.S. For those who are so unfortunate not to to live in Nepal and don't know how to make chiya here a &lt;a href="https://namaste.truman.edu/chiya-milk-tea/"&gt;recipe&lt;/a&gt;&lt;/p&gt;

</description>
      <category>health</category>
      <category>alternativetocoffee</category>
      <category>office</category>
      <category>productivity</category>
    </item>
    <item>
      <title>BDD on Software Rewrite</title>
      <dc:creator>Artur Neumann</dc:creator>
      <pubDate>Mon, 15 Feb 2021 11:16:22 +0000</pubDate>
      <link>https://dev.to/jankaritech/bdd-on-software-rewrite-6md</link>
      <guid>https://dev.to/jankaritech/bdd-on-software-rewrite-6md</guid>
      <description>&lt;p&gt;Imagine you have an application, it works great, but you need to rewrite it, maybe because the architecture is hitting some ceiling. There will be new features, but first, you need to make sure all the existing clients work with the new system. How do you make sure they do? It's simple: You need tests, tests that check the external behavior of the application. So if you have invested in a good UI and API test infrastructure in the first place it's a relatively easy task, and you can even do Behavior Driven Development while writing the new system without having to write new tests.&lt;/p&gt;

&lt;p&gt;Let me show how it works at &lt;a href="https://www.owncloud.com"&gt;ownCloud&lt;/a&gt;. They are exactly in that situation, they have a stable and great product (ownCloud X written in PHP) but want to rewrite it in Go - OCIS (you can read about the reasons and background &lt;a href="https://owncloud.com/infinite-scale/"&gt;here&lt;/a&gt;). &lt;br&gt;
We as &lt;a href="https://dev.to/jankaritech/"&gt;JankariTech&lt;/a&gt; have been working since 2017 for ownCloud to improve the test infrastructure, increase the coverage and reduce manual testing effort. As a result of this partnership, ownCloud has a huge &lt;a href="https://github.com/owncloud/core/tree/master/tests/acceptance/features"&gt;API and UI test-suite&lt;/a&gt; that covers nearly all functionality of ownCloud X. How do you transfer that over to OCIS?&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aTPEfg6A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/1j8hl8h9hk1f2m69zmcb.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aTPEfg6A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/1j8hl8h9hk1f2m69zmcb.JPG" alt="Coding" width="640" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup of the System under Test
&lt;/h2&gt;

&lt;p&gt;We haven't started to use the API &amp;amp; UI tests from the first day of development, but only after there has been a bare minimum of functionalities implemented in OCIS. To run the tests the first challenge was how to provision the system. The goal is to have feature parity, but of course OCIS did lack a lot of functionality at that stage of development, including APIs to create and delete users, set system relevant settings, etc. Additionally, to run tests in ownCloud X, we heavily rely on the command-line tool to bring the system into a testable state. There is no equivalent (yet) in OCIS. The solution to those challenges was to extend the test-code to do slightly different things on both systems. E.g. OCIS would get users from an LDAP server, in ownCloud X we would provision users and groups through the &lt;a href="https://doc.owncloud.com/server/developer_manual/core/apis/provisioning-api.html#instruction-set-for-users"&gt;provisioning api&lt;/a&gt;. Luckily we had the LDAP code already in the test-suite from testing the &lt;a href="https://github.com/owncloud/user_ldap"&gt;ownCloud LDAP app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Those different code paths should be reduced to the setup of the SUT (System Under Test) - in Gherkin speak: Given - steps.&lt;br&gt;
All external behaviour that needs to be tested and examined should be the same on both systems, the goal is to reach feature parity at some point.&lt;/p&gt;

&lt;p&gt;By now a lot of the provisioning API is also implemented in OCIS, so we could switch off the provisioning by LDAP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failing tests
&lt;/h2&gt;

&lt;p&gt;Of course at the beginning most of the tests would fail on OCIS, the application is not ready and does not claim to be ready. We started with skipping the failing tests and running only the tests that we knew would pass on OCIS. That way we got green CI and still prevented regressions. It was never an option to have CI failing because "we know those tests are allowed to fail". In that case the developers would have to check manually which tests are allowed to fail and which not. People would forget to do that or make wrong decisions about what is an expected failure and what not. Most important: red CI looks ugly on a pull request or worse a merge and is an embarrassment 🙈&lt;/p&gt;

&lt;p&gt;Simply skipping failing tests also had big disadvantages. To make sure the test coverage is increased with every new feature added, the developer needs to know all the tests related to that feature and run them during or after the development. Or someone needs to run the skipped tests on a regular basis to see which of them started to pass and enable them. Both approaches are not practical because of 1. the laziness of human beings and 2. the amount of tests.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EkjYl3i2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/s4w37qy4h30oa9uwui5g.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EkjYl3i2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/s4w37qy4h30oa9uwui5g.JPG" alt="Software Developers" width="640" height="449"&gt;&lt;/a&gt;&lt;br&gt;
A lot of test frameworks have a feature that allows some specific tests to fail without failing the entire test-run. We took that idea further and implemented an "expected to fail" feature in CI. Tests listed in the expected-to-fail list &lt;strong&gt;have&lt;/strong&gt; to fail, if they start to pass the CI run will fail.&lt;/p&gt;

&lt;p&gt;The advantage of that above just a simple "these tests are allowed to fail" is that, after adding a feature or fixing a bug, the developer is forced to look into the tests. If tests start to pass, the only job the developer has to do, is to remove them from the expected-to-fail list, &lt;a href="https://github.com/cs3org/reva/pull/1368#issuecomment-754179433"&gt;and what a joy is that&lt;/a&gt; 🎉.&lt;br&gt;
From that point on the test has to pass in all future runs, and we are sure not to introduce any regressions. If we would only have an allowed-to-fail list, there would be no pressure to remove tests from that list, humans are humans, so they would forget or miss some. Potentially a bug could get fixed, then see some regression again and none of that would be noticed by the test-suite. So let the computers do what they are good at - automate!&lt;/p&gt;

&lt;h2&gt;
  
  
  human-readable code
&lt;/h2&gt;

&lt;p&gt;To improve the readability of the expected-to-fail list, it got converted from a TXT file to an MD file. If you browse now the &lt;a href="https://github.com/owncloud/ocis/blob/master/tests/acceptance/expected-failures-API-on-OCIS-storage.md"&gt;list&lt;/a&gt; you can jump directly to the issue report, to see WHY this particular tests fails, and you can jump to the test itself, to see WHAT exactly it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  BDD for rewrite
&lt;/h2&gt;

&lt;p&gt;With all that in place the developers&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;can use the existing test-suites for Behaviour Driven Development while rewriting the whole system.&lt;/li&gt;
&lt;li&gt;know what features are missing and how far they are on the way to feature parity&lt;/li&gt;
&lt;li&gt;don't need to rewrite all the tests for the new system&lt;/li&gt;
&lt;li&gt;are safe from regressions for already implemented features&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The only job left is to reduce the amount of expected-to-fail tests to 0, how hard can that be? 😜&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uFFacyMa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/0ia62sevmsbuhgqmtbj3.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uFFacyMa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/0ia62sevmsbuhgqmtbj3.JPG" alt="Coders" width="535" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>bdd</category>
      <category>testing</category>
    </item>
    <item>
      <title>BDD (Behavior Driven Development) with Flutter</title>
      <dc:creator>Artur Neumann</dc:creator>
      <pubDate>Wed, 03 Jun 2020 06:46:57 +0000</pubDate>
      <link>https://dev.to/jankaritech/bdd-behavior-driven-development-with-flutter-31ao</link>
      <guid>https://dev.to/jankaritech/bdd-behavior-driven-development-with-flutter-31ao</guid>
      <description>&lt;p&gt;This tutorial will first show how to test a flutter app using the Gherkin language and in the second part walk through an example of BDD (Behavior Driven Development) in the same App. &lt;/p&gt;

&lt;p&gt;Flutter uses different types of tests &lt;a href="https://flutter.dev/docs/testing"&gt;(unit, widget, integration)&lt;/a&gt;. You should have all types of tests in your app, most of your tests should be unit tests, less widget and a few integration tests. The &lt;a href="https://martinfowler.com/bliki/TestPyramid.html"&gt;test pyramid&lt;/a&gt; explains the principle well (using different words for the test-types).&lt;/p&gt;

&lt;p&gt;In this tutorial I want to help you to start with integration tests but go a step further than the description in the &lt;a href="https://flutter.dev/docs/testing#integration-tests"&gt;flutter documentation&lt;/a&gt; and use the Gherkin language to describe the expected behavior.&lt;br&gt;
The basic idea behind Gherkin/Cucumber is to have a semi-structured language to be able to define the expected behaviour and requirements in a way that all stakeholders of the project (customer, management, developer, QA, etc.) understand them. Using Gherkin helps to reduce misunderstandings, wasted resources and conflicts by improving the communication. Additionally, you get a documentation of your project and finally you can use the Gherkin files to run automated tests.&lt;/p&gt;

&lt;p&gt;If you write the Gherkin files, before you write the code, you have reached the final level, as this is called BDD (Behaviour Driven Development)!&lt;/p&gt;

&lt;p&gt;Here are some readings about BDD and Gherkin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.dannorth.net/introducing-bdd"&gt;"Introducing BDD", by Dan North (2006)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Behavior-driven_development"&gt;Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://inviqa.com/blog/bdd-guide"&gt;"The beginner's guide to BDD (behaviour-driven development)", By Konstantin Kudryashov, Alistair Stead, Dan North&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cucumber.io/docs/bdd/"&gt;Behaviour-Driven Development&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But enough theory, lets get our hands dirty. (You can find all the code of this tutorial here: &lt;a href="https://github.com/JankariTech/flutterBDDexample"&gt;https://github.com/JankariTech/flutterBDDexample&lt;/a&gt;)&lt;/p&gt;
&lt;h1&gt;
  
  
  The feature files
&lt;/h1&gt;

&lt;p&gt;For the start you should have installed the flutter-tools stack and create a flutter test-drive app as explained in the &lt;a href="https://flutter.dev/docs/get-started/test-drive?tab=androidstudio"&gt;get-started document&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside the app folder create a folder called &lt;code&gt;test_driver&lt;/code&gt; and inside another one called &lt;code&gt;features&lt;/code&gt;. In &lt;code&gt;features&lt;/code&gt; we will place all the Gherkin descriptions of the expected app behavior. So create here a file called: &lt;code&gt;increment_counter.feature&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We start the feature file with a very general description of the feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Feature: Increment Counter

  As the good shepherd
  I want to be able to count my sheep
  So that I notice if one is missing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line is just a title of the feature, the other three lines should answer the questions &lt;a href="https://www.bibleserver.com/ESV/Luke15%3A4"&gt;Who, wants to achieve what and why with this particular feature&lt;/a&gt;. If you cannot answer those questions for a particular feature of your app then you actually should not implement that feature, there is no use-case for it.&lt;/p&gt;

&lt;p&gt;Next we have to describe the specific behavior of the app. For that Gherkin provides 3 different keywords:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Given&lt;/strong&gt; - prerequisites for the scenario&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When&lt;/strong&gt; - the action to be tested&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then&lt;/strong&gt; - the desired observable outcome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add a scenario to the feature file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Scenario: Counter increases when the button is pressed
    Given the counter is set to "0"
    When I tap the "increment" button 10 times
    Then I expect the "counter" to be "10"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later we will add more scenarios to the app, the feature might be the same, but in different scenarios it might have to react differently.&lt;/p&gt;

&lt;p&gt;Now we can start the app and use our behaviour description to check if it works as it should.&lt;/p&gt;

&lt;h1&gt;
  
  
  Test-automation
&lt;/h1&gt;

&lt;p&gt;Running manual tests from a description is nice, but not enough for us, we want to save time and reduce possible mistakes by running the tests automatically.&lt;/p&gt;

&lt;p&gt;To interpret the Gherkin file and interact with the app we are using the &lt;code&gt;flutter_gherkin&lt;/code&gt; package. Install it by placing &lt;code&gt;flutter_gherkin:&lt;/code&gt; in the &lt;code&gt;pubspec.yaml&lt;/code&gt; inside the &lt;code&gt;dev_depencencies&lt;/code&gt; section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_gherkin:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and run &lt;code&gt;flutter pub get&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we also need some glue-code and configuration.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;test_driver&lt;/code&gt; create a file called &lt;code&gt;app.dart&lt;/code&gt; with the content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import '../lib/main.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_driver/driver_extension.dart';

void main() {
  enableFlutterDriverExtension();
  runApp(MyApp());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and a file called &lt;code&gt;app_test.dart&lt;/code&gt; with the content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'dart:async';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
import 'package:glob/glob.dart';

Future&amp;lt;void&amp;gt; main() {
  final config = FlutterTestConfiguration()
    ..features = [Glob(r"test_driver/features/**.feature")]
    ..reporters = [
      ProgressReporter(),
      TestRunSummaryReporter(),
      JsonReporter(path: './report.json')
    ]
    ..stepDefinitions = []
    ..customStepParameterDefinitions = []
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart"
    ..exitAfterTestRun = true; // set to false if debugging to exit cleanly
  return GherkinRunner().execute(config);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was all we need to do for the installation, now we have to tell the test-software what actually to do with our Given, When and Then steps.&lt;br&gt;
The library gives us some built-in steps, that should work "out-of-the-box" but others we need to implement ourself.&lt;br&gt;
In our example the Then step is a built-in step but the Given and the When step have to be implemented. So let's do that. Inside &lt;code&gt;test_driver&lt;/code&gt; create a folder called &lt;code&gt;steps&lt;/code&gt; and in there create a file called &lt;code&gt;tap_button_n_times_step.dart&lt;/code&gt; with the content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';

class GivenCounterIsSetTo extends Given1WithWorld&amp;lt;String, FlutterWorld&amp;gt; {
  @override
  RegExp get pattern =&amp;gt; RegExp(r"the counter is set to {string}");

  @override
  Future&amp;lt;void&amp;gt; executeStep(String expectedCounter) async {
    final locator = find.byValueKey("counter");
    final actualCount = await FlutterDriverUtils.getText(world.driver, locator);
    expectMatch(actualCount, expectedCounter);
  }
}

class TapButtonNTimesStep extends When2WithWorld&amp;lt;String, int, FlutterWorld&amp;gt; {
  @override
  RegExp get pattern =&amp;gt; RegExp(r"I tap the {string} button {int} times");

  @override
  Future&amp;lt;void&amp;gt; executeStep(String buttonKey, int amount) async {
    final locator = find.byValueKey(buttonKey);
    for (var i = 0; i &amp;lt; amount; i += 1) {
      await FlutterDriverUtils.tap(world.driver, locator, timeout: timeout);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file we have two classes, one for every step we want to implement. Every class extends an abstract class. The Given step extends a class which name starts with &lt;code&gt;Given&lt;/code&gt; and analogously the When step extends a class which name starts with &lt;code&gt;When&lt;/code&gt;. Then there is a number in the class name. That number tells how many parameters we can pass from the step to the implementation. In &lt;code&gt;Given the counter is set to "0"&lt;/code&gt; there is one parameter (the &lt;code&gt;0&lt;/code&gt;) and in &lt;code&gt;When I tap the "increment" button 10 times&lt;/code&gt; two (the button name, and the amount of taps).&lt;/p&gt;

&lt;p&gt;The last part of the class to extend is &lt;code&gt;WithWorld&lt;/code&gt; that gives us access to the Flutter context.&lt;/p&gt;

&lt;p&gt;Next there is a variable called &lt;code&gt;pattern&lt;/code&gt; with a regular expression, that is used to associate the step in the feature file with the class.&lt;/p&gt;

&lt;p&gt;Last there is a function &lt;code&gt;executeStep&lt;/code&gt;. This function receives the parameters from the feature file and finally does all the hard work.&lt;br&gt;
In both cases it finds the element on the screen we want to interact with by using the &lt;code&gt;find.byValueKey()&lt;/code&gt; method and then in the case of the Given step, gets the text of the element and checks if its as expected or, in the case of the When step, taps the button.&lt;/p&gt;

&lt;p&gt;Similarly our Then step (remember it's a built-in step) will use the same &lt;code&gt;find.byValueKey()&lt;/code&gt; method to get the value and assert the content. If you are interested in the implementation, the step is defined in &lt;code&gt;flutter_gherkin-&amp;lt;version&amp;gt;/lib/src/flutter/steps/then_expect_element_to_have_value_step.dart&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The issue now is that the example code does not have any keys defined in the widgets. The test-code would not be able to locate the elements.&lt;br&gt;
So edit the &lt;code&gt;main.dart&lt;/code&gt; file and add &lt;code&gt;key: Key('counter'),&lt;/code&gt; to the counter widget and &lt;code&gt;key: Key('increment'),&lt;/code&gt; to the button widget.&lt;/p&gt;

&lt;p&gt;You could also use &lt;code&gt;find.byTooltip&lt;/code&gt;, &lt;code&gt;find.Type&lt;/code&gt; or &lt;code&gt;find.bySemanticsLabel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next the new .dart file with the step definitions need to be imported in &lt;code&gt;app_test.dart&lt;/code&gt;:&lt;br&gt;
&lt;code&gt;import 'steps/tap_button_n_times_step.dart';&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Additionally every class we add in the steps definitions we also have to register in the &lt;code&gt;stepDefinitions&lt;/code&gt; array in &lt;code&gt;app_test.dart&lt;/code&gt;, the line has to be:&lt;br&gt;
&lt;code&gt;..stepDefinitions = [TapButtonNTimesStep(), GivenCounterIsSetTo()]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Remember: The step &lt;code&gt;Then I expect the "counter" to be "10"&lt;/code&gt; is a built-in-step. So we don't need to write any code for it, it will look for a text-widget with the key &lt;code&gt;counter&lt;/code&gt; and assert its value.&lt;/p&gt;
&lt;h1&gt;
  
  
  run the tests
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;connect your phone or start the emulator&lt;/li&gt;
&lt;li&gt;run &lt;code&gt;dart test_driver/app_test.dart&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;after a while you should see an output like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running scenario: Counter increases when the button is pressed # ./test_driver/features/increment_counter.feature:5
   √ Given the counter is set to "0" # ./test_driver/features/increment_counter.feature:6 took 146ms
   √ When I tap the "increment" button 10 times # ./test_driver/features/increment_counter.feature:7 took 6420ms
   √ Then I expect the "counter" to be "10" # ./test_driver/features/increment_counter.feature:8 took 72ms
PASSED: Scenario Counter increases when the button is pressed # ./test_driver/features/increment_counter.feature:5
Restarting Flutter app under test
1 scenario (1 passed)
3 steps (3 passed)
0:00:16.767000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the app working on the phone screen.&lt;/p&gt;

&lt;h1&gt;
  
  
  BDD (this time for real)
&lt;/h1&gt;

&lt;p&gt;We know now how to write feature files and how to run automated tests from them, but that hasn't been BDD yet. We have only written a test for an existing feature in the app. To do BDD we have first to write the expected behaviour and then start coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. write down the expected behaviour
&lt;/h2&gt;

&lt;p&gt;Let's say we not only want to have a button to increment the counter, but also be able to decrement it. So in &lt;code&gt;features&lt;/code&gt; create a file called &lt;code&gt;decrement_counter.feature&lt;/code&gt; with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Feature: Decrement Counter
  As the good shepherd
  I want to be able to decrement the count of my sheep when one is lost
  So that I can have extra joy incrementing the counter when I find the lost sheep

  Scenario: Counter decreases when the (-) button is pressed
    Given the counter is set to "10"
    When I tap the "decrement" button 1 time
    Then I expect the "counter" to be "9"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trying to run this test we will have multiple issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the &lt;code&gt;Given&lt;/code&gt; step only asserts the counter, but does not set it to a specific value&lt;/li&gt;
&lt;li&gt;the regex will not match the &lt;code&gt;When&lt;/code&gt; step because it says &lt;code&gt;time&lt;/code&gt; and not &lt;code&gt;times&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;there is no functionality and no button to decrement the counter&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2. make the tests pass
&lt;/h2&gt;

&lt;p&gt;For the first issue we would need to pre-set the counter with a value, but as we are doing end-to-end tests and acting as a user, the only way for the user to get the counter up to a specific value is to press the (+) button. Our test-code will do the same. (Side note: that will take time during test-execution, the faster option would be to have a back-channel to pre-set the value e.g. &lt;code&gt;Data Handlers&lt;/code&gt;, but I could not make it work).&lt;/p&gt;

&lt;p&gt;So lets refactor our step definition, so that the Given step pre-sets the counter to the expected value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;index e4eea51..e2e1a38 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/myapp/test_driver/steps/tap_button_n_times_step.dart
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/myapp/test_driver/steps/tap_button_n_times_step.dart
&lt;/span&gt;&lt;span class="p"&gt;@@ -8,6 +8,7 @@&lt;/span&gt; class GivenCounterIsSetTo extends Given1WithWorld&amp;lt;String, FlutterWorld&amp;gt; {
&lt;span class="err"&gt;
&lt;/span&gt;   @override
   Future&amp;lt;void&amp;gt; executeStep(String expectedCounter) async {
&lt;span class="gi"&gt;+    await tapButton(world, timeout, "increment", int.parse(expectedCounter));
&lt;/span&gt;     final locator = find.byValueKey("counter");
     final actualCount = await FlutterDriverUtils.getText(world.driver, locator);
     expectMatch(actualCount, expectedCounter);
&lt;span class="p"&gt;@@ -20,9 +21,13 @@&lt;/span&gt; class TapButtonNTimesStep extends When2WithWorld&amp;lt;String, int, FlutterWorld&amp;gt; {
&lt;span class="err"&gt;
&lt;/span&gt;   @override
   Future&amp;lt;void&amp;gt; executeStep(String buttonKey, int amount) async {
&lt;span class="gd"&gt;-    final locator = find.byValueKey(buttonKey);
-    for (var i = 0; i &amp;lt; amount; i += 1) {
-      await FlutterDriverUtils.tap(world.driver, locator, timeout: timeout);
-    }
&lt;/span&gt;&lt;span class="gi"&gt;+    await tapButton(world, timeout, buttonKey, amount);
+  }
+}
+
+Future&amp;lt;void&amp;gt; tapButton(FlutterWorld world, Duration timeout, String buttonKey, int amount) async {
&lt;/span&gt; +  final locator = find.byValueKey(buttonKey);
 +  for (var i = 0; i &amp;lt; amount; i += 1) {
 +    await FlutterDriverUtils.tap(world.driver, locator, timeout: timeout);
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second issue should be fixed easily with some regex-magic. Just place the &lt;code&gt;s&lt;/code&gt; of &lt;code&gt;times&lt;/code&gt; in a non-capturing regex group:&lt;br&gt;
&lt;code&gt;RegExp get pattern =&amp;gt; RegExp(r"I tap the {string} button {int} time(?:s|)");&lt;/code&gt;&lt;br&gt;
Non-capturing because a normal group would be passed as argument to &lt;code&gt;TapButtonNTimesStep&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To fix the last issue, we actually need to implement a new functionality in the app. We need a decrement button in &lt;code&gt;main.dart&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;index 8795daa..068f558 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/myapp/lib/main.dart
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/myapp/lib/main.dart
&lt;/span&gt;&lt;span class="p"&gt;@@ -63,6 +63,12 @@&lt;/span&gt; class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
     });
   }
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+  void _decrementCounter() {
+    setState(() {
+      _counter--;
+    });
+  }
+
&lt;/span&gt;   @override
   Widget build(BuildContext context) {
     // This method is rerun every time setState is called, for instance as done
&lt;span class="p"&gt;@@ -95,7 +101,7 @@&lt;/span&gt; class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
           // center the children vertically; the main axis here is the vertical
           // axis because Columns are vertical (the cross axis would be
           // horizontal).
&lt;span class="gd"&gt;-          mainAxisAlignment: MainAxisAlignment.center,
&lt;/span&gt;&lt;span class="gi"&gt;+          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
&lt;/span&gt;           children: &amp;lt;Widget&amp;gt;[
             Text(
               'You have pushed the button this many times:',
&lt;span class="p"&gt;@@ -105,15 +111,28 @@&lt;/span&gt; class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
               key: Key('counter'),
               style: Theme.of(context).textTheme.headline4,
             ),
&lt;span class="gi"&gt;+            Row(
+              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+                children: &amp;lt;Widget&amp;gt;[
+                  FloatingActionButton(
+                    onPressed: _decrementCounter,
+                    key: Key('decrement'),
+                    tooltip: 'decrement',
+                    child: Icon(Icons.remove),
+                  ),
+                  FloatingActionButton(
+                    // Provide a Key to this button. This allows finding this
+                    // specific button inside the test suite, and tapping it.
+                    key: Key('increment'),
+                    onPressed: _incrementCounter,
+                    tooltip: 'Increment',
+                    child: Icon(Icons.add),
+                  ),
+                ]
+            )
&lt;/span&gt;           ],
         ),
       ),
&lt;span class="gd"&gt;-      floatingActionButton: FloatingActionButton(
-        onPressed: _incrementCounter,
-        key: Key('increment'),
-        tooltip: 'Increment',
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the tests should pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running scenario: Counter decreases when the (-) button is pressed # ./test_driver/features/decrement_counter.feature:5
   √ Given the counter is set to "10" # ./test_driver/features/decrement_counter.feature:6 took 2877ms
   √ When I tap the "decrement" button 1 time # ./test_driver/features/decrement_counter.feature:7 took 255ms
   √ Then I expect the "counter" to be "9" # ./test_driver/features/decrement_counter.feature:8 took 43ms
PASSED: Scenario Counter decreases when the (-) button is pressed # ./test_driver/features/decrement_counter.feature:5
Restarting Flutter app under test
...
Running scenario: Counter increases when the button is pressed # ./test_driver/features/increment_counter.feature:5
   √ Given the counter is set to "0" # ./test_driver/features/increment_counter.feature:6 took 46ms
   √ When I tap the "increment" button 10 times # ./test_driver/features/increment_counter.feature:7 took 2835ms
   √ Then I expect the "counter" to be "10" # ./test_driver/features/increment_counter.feature:8 took 84ms
PASSED: Scenario Counter increases when the button is pressed # ./test_driver/features/increment_counter.feature:5
Restarting Flutter app under test
2 scenarios (2 passed)
6 steps (6 passed)
0:00:22.451000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. multiply the scenarios by using an example table
&lt;/h2&gt;

&lt;p&gt;Now we might want to test more cases than only tapping the (-) button once. For that we can just copy and paste the existing scenario, or more elegantly we add an example table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Scenario Outline: Counter decreases when the (-) button is pressed
    Given the counter is set to "&amp;lt;initial-counter&amp;gt;"
    When I tap the "decrement" button &amp;lt;decrement&amp;gt; time
    Then I expect the "counter" to be "&amp;lt;final-counter&amp;gt;"
    Examples:
      | initial-counter | decrement | final-counter |
      | 10              | 1         | 9             |
      | 10              | 9         | 1             |
      | 3               | 3         | 0             |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will run the same scenario three different times with the values in the table substituted into the steps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running scenario: Counter decreases when the (-) button is pressed (Example 1) # ./test_driver/features/decrement_counter.feature:5
   √ Given the counter is set to "10" # ./test_driver/features/decrement_counter.feature:6 took 2658ms
   √ When I tap the "decrement" button 1 time # ./test_driver/features/decrement_counter.feature:7 took 243ms
   √ Then I expect the "counter" to be "9" # ./test_driver/features/decrement_counter.feature:8 took 60ms
PASSED: Scenario Counter decreases when the (-) button is pressed (Example 1) # ./test_driver/features/decrement_counter.feature:5

...

Running scenario: Counter decreases when the (-) button is pressed (Example 2) # ./test_driver/features/decrement_counter.feature:5
   √ Given the counter is set to "10" # ./test_driver/features/decrement_counter.feature:6 took 3325ms
   √ When I tap the "decrement" button 9 time # ./test_driver/features/decrement_counter.feature:7 took 2457ms
   √ Then I expect the "counter" to be "1" # ./test_driver/features/decrement_counter.feature:8 took 25ms
PASSED: Scenario Counter decreases when the (-) button is pressed (Example 2) # ./test_driver/features/decrement_counter.feature:5

...

Running scenario: Counter decreases when the (-) button is pressed (Example 3) # ./test_driver/features/decrement_counter.feature:5
   √ Given the counter is set to "3" # ./test_driver/features/decrement_counter.feature:6 took 878ms
   √ When I tap the "decrement" button 3 time # ./test_driver/features/decrement_counter.feature:7 took 877ms
   √ Then I expect the "counter" to be "0" # ./test_driver/features/decrement_counter.feature:8 took 63ms
PASSED: Scenario Counter decreases when the (-) button is pressed (Example 3) # ./test_driver/features/decrement_counter.feature:5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. repeat
&lt;/h2&gt;

&lt;p&gt;What about negative values? If a shepherd is using this app to count the sheep, there is no point to have a negative counter. To say it in Gherkin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Scenario: Counter should not be negative
    Given the counter is set to "0"
    When I tap the "decrement" button 1 time
    Then I expect the "counter" to be "0"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You also could add that to the previous table, but I would argue that it is another requirement and its easier to understand the feature file if its written out in a separate Scenario.&lt;/p&gt;

&lt;p&gt;Running this test fails with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   × Then I expect the "counter" to be "0" # ./test_driver/features/decrement_counter.feature:18 took 97ms 
      Expected: '0'
  Actual: '-1'
   Which: is different.
          Expected: 0
            Actual: -1
                    ^
           Differ at offset 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The counter becomes negative. Let's fix it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;index 068f558..5e0d8d0 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/myapp/lib/main.dart
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/myapp/lib/main.dart
&lt;/span&gt;&lt;span class="p"&gt;@@ -65,7 +65,9 @@&lt;/span&gt; class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
&lt;span class="err"&gt;
&lt;/span&gt;   void _decrementCounter() {
     setState(() {
&lt;span class="gd"&gt;-      _counter--;
&lt;/span&gt;&lt;span class="gi"&gt;+      if (_counter &amp;gt; 0) {
+        _counter--;
+      }
&lt;/span&gt;     });
   }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  conclusion
&lt;/h1&gt;

&lt;p&gt;You have seen how to write Gherkin files and how to run them as automated tests for a flutter application.&lt;br&gt;
I personally find flutter_gherkin a bit more complicated than other BDD frameworks, but it's possible, and I believe using BDD will improve the quality of your project greatly.&lt;/p&gt;

&lt;p&gt;If you need any help with the test-coverage of your app, BDD or other test-related topics, please contact us &lt;a href="https://www.jankaritech.com"&gt;@JankariTech&lt;/a&gt;&lt;/p&gt;

</description>
      <category>bdd</category>
      <category>testing</category>
      <category>flutter</category>
      <category>dart</category>
    </item>
    <item>
      <title>Demonstrating BDD (Behavior-driven development) in Go</title>
      <dc:creator>Artur Neumann</dc:creator>
      <pubDate>Wed, 05 Feb 2020 08:55:11 +0000</pubDate>
      <link>https://dev.to/jankaritech/demonstrating-bdd-behavior-driven-development-in-go-1eci</link>
      <guid>https://dev.to/jankaritech/demonstrating-bdd-behavior-driven-development-in-go-1eci</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/jankaritech/demonstrating-tdd-test-driven-development-in-go-27b0"&gt;Demonstrating TDD (Test-driven development) in Go&lt;/a&gt; I've written about TDD and this time I want to demonstrate BDD (Behavior-driven development) with Go.&lt;/p&gt;

&lt;p&gt;I will not explain all principles of BDD upfront, but explain some of them as I use them in the example. You can read more about them here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.dannorth.net/introducing-bdd"&gt;"Introducing BDD", by Dan North (2006)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Behavior-driven_development"&gt;Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://inviqa.com/blog/bdd-guide"&gt;"The beginner's guide to BDD (behaviour-driven development)", By Konstantin Kudryashov, Alistair Stead, Dan North&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cucumber.io/docs/bdd/"&gt;Behaviour-Driven Development&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have more good resources, please post them in the comment section.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic idea
&lt;/h2&gt;

&lt;p&gt;I'm a fan of explaining things with real examples, that's why in &lt;a href="https://dev.to/jankaritech/demonstrating-tdd-test-driven-development-in-go-27b0"&gt;Demonstrating TDD (Test-driven development) in Go&lt;/a&gt; I've created that small library to convert from Bikram Sambat (BS) (also called Vikram Samvat) dates to Gregorian dates and vice-versa. Now I want to use that library to create an API-driven service to do the conversion. (The project can be found on &lt;a href="https://github.com/JankariTech/bsDateServer"&gt;github&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;One could now give that "requirement" to a developer and see what happens. With that kind of small project, chances are, something good will come out, but bad things might also happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the API will be super-complex and over-engineered&lt;/li&gt;
&lt;li&gt;the API does the conversion, but does not handle errors correctly&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So there is a lot of potential for wasted resources, conflicts, misunderstandings etc. So it would be better to write down the requirements in more detail, because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;As customer you want your application to behave correctly (sometimes without knowing exactly what that means).&lt;/li&gt;
&lt;li&gt;As developer your want to develop exactly what is requested and needed (to save time) and get paid afterwards.&lt;/li&gt;
&lt;li&gt;As as QA-person, you want to know what you have to test, and you want to know what is a bug and what is a feature.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So basically the goal is to get all the stakeholders (there might be more than the listed 3) to communicate and agree on what should be the acceptable behavior of the application. And that is in a nutshell the idea of BDD: improve the communication between stakeholders so that everybody knows what is talked about.&lt;/p&gt;

&lt;p&gt;But how to do that? The customer might think that the one-line explanation: "API to convert dates from BS to AD and vice-versa" is enough, the manager wants to write a contract and the developer says: "code is documentation enough".&lt;br&gt;
A good way to bring everybody on the same page is to describe the features of an application using the Gherkin language. Its a semi-structured language, that is so simple a cucumber could understand.&lt;/p&gt;
&lt;h2&gt;
  
  
  Who wants to achieve what and why?
&lt;/h2&gt;

&lt;p&gt;In the project folder we create a new file called &lt;code&gt;bs-to-ad-conversion.feature&lt;/code&gt;. Here we want to describe the feature to convert the dates in one direction. The description of every feature of the app is supposed to go into a separate file.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Side note: there is always the discussion what is a "feature"? In our example: is the conversion in both directions one or two features? Is the error-handling a separate feature or a part of the conversion feature? If you are not sure, be practical and simply make sure the file does not get too long.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We start the feature file with a very general description of the feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Feature: convert dates from BS to AD using an API
  As an app-developer in Nepal
  I want to be able to send BS dates to an API endpoint and receive the corresponding AD dates
  So that I have a simple way to convert BS to AD dates, that can be used in different apps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These lines are very important. They answer the question WHO wants to achieve WHAT with that feature and WHY. If you don't know who will use that feature, why do you implement it? If there is nothing to achieve with that feature, you actually don't have a feature. And if there is no reason to use that feature, it doesn't have a business value. So if the stakeholders (developer, customer, manager, QA, etc.) cannot answer these 3 questions, nobody really should spend time and money to implement it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Scenarios
&lt;/h2&gt;

&lt;p&gt;Every feature has different scenarios. A "add item to shopping basket"-feature in an online-shop could have scenarios like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adding item to the basket while user is logged in&lt;/li&gt;
&lt;li&gt;adding item to the basket while user is not logged in&lt;/li&gt;
&lt;li&gt;adding item to the basket when the card is empty&lt;/li&gt;
&lt;li&gt;adding item to the basket when there is already the same item in the basket&lt;/li&gt;
&lt;li&gt;adding multiple items to the basket at once&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In every scenario your app might behave differently. If that specific behavior in that scenario matters for one or more stakeholders, better describe it.&lt;/p&gt;

&lt;p&gt;In Gherkin we have to start the scenario description with the &lt;code&gt;Scenario:&lt;/code&gt; keyword and a short free-text sentence:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Scenario: converting a valid BS date

  Scenario: converting an invalid BS date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Given, When, Then
&lt;/h2&gt;

&lt;p&gt;Now we want to describe the specific behavior of the app in that scenario. For that Gherkin provides 3 different keywords:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Given&lt;/strong&gt; - prerequisites for the scenario&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When&lt;/strong&gt; - the action to be tested&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then&lt;/strong&gt; - the desired observable outcome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally there is &lt;strong&gt;And&lt;/strong&gt;, if you have multiple of one of the above, you don't need to write&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;When doing A
When doing B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;but you can use &lt;code&gt;And&lt;/code&gt; (it just sounds and reads nicer)&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; When doing A
 And doing B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For a complex application there will be most-likely some steps to bring the application into the state that you want to test (e.g. create users, navigate to a specific page, etc), for those prerequisites you should use the &lt;code&gt;Given&lt;/code&gt; keyword.&lt;br&gt;
For our app, I cannot really think of anything. So I skip over to the &lt;code&gt;When&lt;/code&gt; keyword.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;When&lt;/code&gt; keyword is for the action (or multiple) you really want to test.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Scenario: converting a valid BS date
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01"

  Scenario: converting an invalid BS date
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-13-01"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, what should happen in those specific scenarios? What is the observable outcome? Use the &lt;code&gt;Then&lt;/code&gt; keyword to describe that (if there are different outcomes connect multiple &lt;code&gt;Then&lt;/code&gt;s with &lt;code&gt;And&lt;/code&gt;s)&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Scenario: converting a valid BS date
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01"
    Then the HTTP-response code should be "200"
    And the response content should be "2003-07-17"

  Scenario: converting an invalid BS date
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01"
    Then the HTTP-response code should be "400"
    And the response content should be "not a valid date"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So as pieces of our description we have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;features - one feature per file&lt;/li&gt;
&lt;li&gt;scenarios - different ways that the feature should behave&lt;/li&gt;
&lt;li&gt;steps - detailed description of every scenario. Every step starts with &lt;code&gt;Given&lt;/code&gt;, &lt;code&gt;When&lt;/code&gt; or &lt;code&gt;Then&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All these pieces have to be written in a natural language, that all stakeholders can understand. What that means in detail would be a whole own post. In our case the "customer", requested an API, so IMO using technical terms like "HTTP-response code" should be OK. If you describe a GUI, the descriptions should be probably even less technical. The bottom line is: use words that all understand. Remember: BDD is all about improving communication!&lt;/p&gt;

&lt;p&gt;For more information about how to phrase the steps definitions see: &lt;a href="https://cucumber.io/docs/gherkin/reference/"&gt;https://cucumber.io/docs/gherkin/reference/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After specifying one feature (or even one scenario) the developer could start developing. In SCRUM-terms: one feature is one user-story, so you do all your agile development cycle with it. Create one or multiple, put them in sprints, work on them, test them, etc. The description is not only the ToDo list for the developer, but also the test-procedure for QA and the documentation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Test it automatically
&lt;/h2&gt;

&lt;p&gt;We could stop there, but there is a great bonus-point: let's use these descriptions to run automatic tests.&lt;/p&gt;

&lt;p&gt;For that we need software that interprets the Gherkin language and runs code that executes the tests. For Go there is the &lt;a href="https://github.com/cucumber/godog"&gt;godog package&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To install godog we fist have to create a simple &lt;code&gt;go.mod&lt;/code&gt; file with the content&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module github.com/JankariTech/bsDateServer

go 1.19
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and then run &lt;code&gt;go get github.com/cucumber/godog@v0.12.6&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you are running within $GOPATH, you will need to set &lt;code&gt;GO111MODULE=on&lt;/code&gt;, as:&lt;br&gt;
(The version number &lt;code&gt;@v0.12.6&lt;/code&gt; is optional, if it's not given the latest version will be installed. I set the version here to make sure this blog-post stays valid also when s.th. changes in godog)&lt;/p&gt;

&lt;p&gt;We also, need the godog cli command to run our tests. Run the following command to add the godog cli to &lt;code&gt;$GOPATH/bin&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/cucumber/godog/cmd/godog@v0.12.6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now you should be able to run godog with &lt;code&gt;$GOPATH/bin/godog *.feature&lt;/code&gt; and the output would be something like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Feature: convert dates from BS to AD using an API
  As an app-developer in Nepal
  I want to be able to send BS dates to an API endpoint and receive the corresponding AD dates
  So that I have a simple way to convert BS to AD dates, that can be used in different apps

  Scenario: converting a valid BS date                                    # bs-to-ad-conversion.feature:6
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01"
    Then the HTTP-response code should be "200"
    And the response content should be "2003-07-17"

  Scenario: converting an invalid BS date                               # bs-to-ad-conversion.feature:11
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01"
    Then the HTTP-response code should be "400"
    And the response content should be "not a valid date"

2 scenarios (2 undefined)
6 steps (6 undefined)
281.282µs

You can implement step definitions for undefined steps with these snippets:

func aRequestIsSentToTheEndpoint(arg1, arg2 string) error {
    return godog.ErrPending
}

func theHTTPresponseCodeShouldBe(arg1 string) error {
    return godog.ErrPending
}

func theResponseContentShouldBe(arg1 string) error {
    return godog.ErrPending
}

func InitializeScenario(ctx *godog.ScenarioContext) {
    ctx.Step(`^a "([^"]*)" request is sent to the endpoint "([^"]*)"$`, aRequestIsSentToTheEndpoint)
    ctx.Step(`^the HTTP-response code should be "([^"]*)"$`, theHTTPresponseCodeShouldBe)
    ctx.Step(`^the response content should be "([^"]*)"$`, theResponseContentShouldBe)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Godog lists all the scenarios we want to run and tells us that it has no idea what to do with them, because we haven't implemented any of the steps. Now we actually need to write code to tell godog how to execute our scenarios.&lt;/p&gt;

&lt;p&gt;For that create a file with the name &lt;code&gt;bsdateServer_test.go&lt;/code&gt; and the content:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "github.com/cucumber/godog"
)

func aRequestIsSentToTheEndpoint(arg1, arg2 string) error {
    return godog.ErrPending
}

func theHTTPresponseCodeShouldBe(arg1 string) error {
    return godog.ErrPending
}

func theResponseContentShouldBe(arg1 string) error {
    return godog.ErrPending
}

func InitializeScenario(ctx *godog.ScenarioContext) {
    ctx.Step(`^a "([^"]*)" request is sent to the endpoint "([^"]*)"$`, aRequestIsSentToTheEndpoint)
    ctx.Step(`^the HTTP-response code should be "([^"]*)"$`, theHTTPresponseCodeShouldBe)
    ctx.Step(`^the response content should be "([^"]*)"$`, theResponseContentShouldBe)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the &lt;code&gt;InitializeScenario&lt;/code&gt; function we have the link between the human-readable Gherkin, and the function that the computer has to execute for that step. The output of &lt;code&gt;$GOPATH/bin/godog&lt;/code&gt; now looks a bit different:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Feature: convert dates from BS to AD using an API
  As an app-developer in Nepal
  I want to be able to send BS dates to an API endpoint and receive the corresponding AD dates
  So that I have a simple way to convert BS to AD dates, that can be used in different apps

  Scenario: converting a valid BS date                                    # bs-to-ad-conversion.feature:6
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01" # bsdateServer_test.go:8 -&amp;gt; aRequestIsSentToTheEndpoint
      TODO: write pending definition
    Then the HTTP-response code should be "200"                           # bsdateServer_test.go:12 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "2003-07-17"                       # bsdateServer_test.go:16 -&amp;gt; theResponseContentShouldBe

  Scenario: converting an invalid BS date                               # bs-to-ad-conversion.feature:11
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01" # bsdateServer_test.go:8 -&amp;gt; aRequestIsSentToTheEndpoint
      TODO: write pending definition
    Then the HTTP-response code should be "400"                         # bsdateServer_test.go:12 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "not a valid date"               # bsdateServer_test.go:16 -&amp;gt; theResponseContentShouldBe

2 scenarios (2 pending)
6 steps (2 pending, 4 skipped)
576.1µs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Godog found the functions that correspond to every step, but those don't do anything yet, just returning an error.&lt;/p&gt;

&lt;p&gt;Let's implement the first function to send the request:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;index c8b0144..f7ee56d 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/bsdateServer_test.go
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/bsdateServer_test.go
&lt;/span&gt;&lt;span class="p"&gt;@@ -1,11 +1,26 @@&lt;/span&gt;
 package main
&lt;span class="err"&gt;
&lt;/span&gt; import (
&lt;span class="gi"&gt;+    "fmt"
&lt;/span&gt;     "github.com/cucumber/godog"
&lt;span class="gi"&gt;+    "net/http"
+    "strings"
&lt;/span&gt; )
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-func aRequestIsSentToTheEndpoint(arg1, arg2 string) error {
-    return godog.ErrPending
&lt;/span&gt;&lt;span class="gi"&gt;+var host = "http://localhost:10000"
+var res *http.Response
+
+func aRequestIsSentToTheEndpoint(method, endpoint string) error {
+    var reader = strings.NewReader("")
+    var request, err = http.NewRequest(method, host+endpoint, reader)
+    if err != nil {
+        return fmt.Errorf("could not create request %s", err.Error())
+    }
+    res, err = http.DefaultClient.Do(request)
+    if err != nil {
+        return fmt.Errorf("could not send request %s", err.Error())
+    }
+    return nil
&lt;/span&gt; }
&lt;span class="err"&gt;
&lt;/span&gt; func theHTTPresponseCodeShouldBe(arg1 string) error {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here we create a request and send it using the &lt;code&gt;net/http&lt;/code&gt; package. The trick in godog is to return &lt;code&gt;nil&lt;/code&gt; if everything goes well, that will make the step pass. If a step function returns something that implements the &lt;code&gt;error&lt;/code&gt; interface the step will fail.&lt;/p&gt;

&lt;p&gt;BTW: the &lt;code&gt;res&lt;/code&gt; variable is defined outside the function because we need to access it from other steps also.&lt;/p&gt;

&lt;p&gt;Running godog now gives us this result&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
  Scenario: converting a valid BS date                                    # bs-to-ad-conversion.feature:6
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01" # bsdateServer_test.go:13 -&amp;gt; aRequestIsSentToTheEndpoint
    could not send request Get "http://localhost:10000/ad-from-bs/2060-04-01": dial tcp 127.0.0.1:10000: connect: connection refused
    Then the HTTP-response code should be "200"                           # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "2003-07-17"                       # bsdateServer_test.go:31 -&amp;gt; theResponseContentShouldBe

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It cannot connect to the server, because nothing is listening on that port. Let's change that. For a minimal implementation of a server waiting on the port put this code into &lt;code&gt;main.go&lt;/code&gt; and run it with &lt;code&gt;go run main.go&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
)

func homePage(w http.ResponseWriter, r *http.Request){
    fmt.Fprintf(w, "Bikram Sambat Server")
}

func handleRequests() {
    myRouter := mux.NewRouter().StrictSlash(true)
    myRouter.HandleFunc("/", homePage)
    log.Fatal(http.ListenAndServe(":10000", myRouter))
}

func main() {
    handleRequests()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now we are a step further:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Scenario: converting a valid BS date                                    # bs-to-ad-conversion.feature:6
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01" # bsdateServer_test.go:13 -&amp;gt; aRequestIsSentToTheEndpoint
    Then the HTTP-response code should be "200"                           # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
      TODO: write pending definition
    And the response content should be "2003-07-17"                       # bsdateServer_test.go:31 -&amp;gt; theResponseContentShouldBe

  Scenario: converting an invalid BS date                               # bs-to-ad-conversion.feature:11
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01" # bsdateServer_test.go:13 -&amp;gt; aRequestIsSentToTheEndpoint
    Then the HTTP-response code should be "400"                         # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
      TODO: write pending definition
    And the response content should be "not a valid date"               # bsdateServer_test.go:31 -&amp;gt; theResponseContentShouldBe

2 scenarios (2 pending)
6 steps (2 passed, 2 pending, 2 skipped)
1.849695ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;When&lt;/code&gt; step passed, it sent the request, but the first &lt;code&gt;Then&lt;/code&gt; step failed as expected, because it's not implemented yet.&lt;/p&gt;

&lt;p&gt;Let's do that:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;--- a/bsdateServer_test.go
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/bsdateServer_test.go
&lt;/span&gt;&lt;span class="p"&gt;@@ -3,6 +3,7 @@&lt;/span&gt; package main
 import (
     "fmt"
     "github.com/cucumber/godog"
&lt;span class="gi"&gt;+    "io/ioutil"
&lt;/span&gt;     "net/http"
     "strings"
 )
&lt;span class="p"&gt;@@ -23,16 +24,23 @@&lt;/span&gt; func aRequestIsSentToTheEndpoint(method, endpoint string) error {
     return nil
 }
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-func theHTTPresponseCodeShouldBe(arg1 string) error {
-    return godog.ErrPending
&lt;/span&gt;&lt;span class="gi"&gt;+func theHTTPresponseCodeShouldBe(expectedCode int) error {
+    if expectedCode != res.StatusCode {
+        return fmt.Errorf("status code not as expected! Expected '%d', got '%d'", expectedCode, res.StatusCode)
+    }
+    return nil
&lt;/span&gt; }
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-func theResponseContentShouldBe(arg1 string) error {
-    return godog.ErrPending
&lt;/span&gt;&lt;span class="gi"&gt;+func theResponseContentShouldBe(expectedContent string) error {
+    body, _ := ioutil.ReadAll(res.Body)
+    if expectedContent != string(body) {
+        return fmt.Errorf("status code not as expected! Expected '%s', got '%s'", expectedContent, string(body))
+    }
+    return nil
&lt;/span&gt; }
&lt;span class="err"&gt;
&lt;/span&gt; func InitializeScenario(ctx *godog.ScenarioContext) {
     ctx.Step(`^a "([^"]*)" request is sent to the endpoint "([^"]*)"$`, aRequestIsSentToTheEndpoint)
&lt;span class="gd"&gt;-    ctx.Step(`^the HTTP-response code should be "([^"]*)"$`, theHTTPresponseCodeShouldBe)
&lt;/span&gt;&lt;span class="gi"&gt;+    ctx.Step(`^the HTTP-response code should be "(\d+)"$`, theHTTPresponseCodeShouldBe)
&lt;/span&gt;     ctx.Step(`^the response content should be "([^"]*)"$`, theResponseContentShouldBe)
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here we simply get the status code and the result body and compare it with the expectation. If it does not match, return an error. Make sure you show good error messages, the goal is to direct the developer as much as possible to the problem. The clearer the message is the quicker the developer will be able to fix the issue. Remember: these tests will not only be used during the initial development but also in the future to prevent regressions.&lt;/p&gt;

&lt;p&gt;The regular-expression change in the &lt;code&gt;FeatureContext&lt;/code&gt; just makes sure that we only accept decimal numbers in that step.&lt;/p&gt;

&lt;p&gt;Now the tests fail with:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
  Scenario: converting a valid BS date # bs-to-ad-conversion.feature:6
    Then the HTTP-response code should be "200" # bs-to-ad-conversion.feature:8
      Error: status code not as expected! Expected '200', got '404'

  Scenario: converting an invalid BS date # bs-to-ad-conversion.feature:11
    Then the HTTP-response code should be "400" # bs-to-ad-conversion.feature:13
      Error: status code not as expected! Expected '400', got '404'

2 scenarios (2 failed)
6 steps (2 passed, 2 failed, 2 skipped)
1.766438ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Why? Because the endpoint does not exist! The server returns 404. It's time to write the software itself!&lt;/p&gt;

&lt;p&gt;Here are the changes in &lt;code&gt;main.go&lt;/code&gt; to do a simple conversion:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;index ae01ed0..06299b0 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/main.go
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/main.go
&lt;/span&gt;&lt;span class="p"&gt;@@ -2,18 +2,34 @@&lt;/span&gt; package main
&lt;span class="err"&gt;
&lt;/span&gt; import (
        "fmt"
&lt;span class="gi"&gt;+       "github.com/JankariTech/GoBikramSambat"
&lt;/span&gt;        "github.com/gorilla/mux"
        "log"
        "net/http"
&lt;span class="gi"&gt;+       "strconv"
+       "strings"
&lt;/span&gt; )
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+func getAdFromBs(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       dateString := vars["date"]
+       var splitedDate = strings.Split(dateString, "-")
+       day, _ := strconv.Atoi(splitedDate[2])
+       month, _ := strconv.Atoi(splitedDate[1])
+       year, _ := strconv.Atoi(splitedDate[0])
+       date, _ := bsdate.New(day, month, year)
+       gregorianDate, _ := date.GetGregorianDate()
+       fmt.Fprintf(w, gregorianDate.Format("2006-01-02"))
+}
+
&lt;/span&gt; func handleRequests() {
        myRouter := mux.NewRouter().StrictSlash(true)
        myRouter.HandleFunc("/", homePage)
&lt;span class="gi"&gt;+       myRouter.HandleFunc("/ad-from-bs/{date}", getAdFromBs)
&lt;/span&gt;        log.Fatal(http.ListenAndServe(":10000", myRouter))
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Basically: split the incoming string, send it to the &lt;code&gt;GoBikramSambat&lt;/code&gt; lib and return the formatted result.&lt;/p&gt;

&lt;p&gt;And with that the first scenario passes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
  Scenario: converting a valid BS date                                    # bs-to-ad-conversion.feature:6
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01" # bsdateServer_test.go:14 -&amp;gt; aRequestIsSentToTheEndpoint
    Then the HTTP-response code should be "200"                           # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "2003-07-17"                       # bsdateServer_test.go:34 -&amp;gt; theResponseContentShouldBe

  Scenario: converting an invalid BS date                               # bs-to-ad-conversion.feature:11
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01" # bsdateServer_test.go:14 -&amp;gt; aRequestIsSentToTheEndpoint
    could not send request Get "http://localhost:10000/ad-from-bs/60-13-01": EOF
    Then the HTTP-response code should be "400"                         # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "not a valid date"               # bsdateServer_test.go:34 -&amp;gt; theResponseContentShouldBe

--- Failed steps:

  Scenario: converting an invalid BS date # bs-to-ad-conversion.feature:11
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01" # bs-to-ad-conversion.feature:12
      Error: could not send request Get "http://localhost:10000/ad-from-bs/60-13-01": EOF


2 scenarios (1 passed, 1 failed)
6 steps (3 passed, 1 failed, 2 skipped)
2.035601ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With a bit of error-handling we should be able to make the other one pass also.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;index 06299b0..a62eaf6 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/main.go
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/main.go
&lt;/span&gt;&lt;span class="p"&gt;@@ -21,7 +21,11 @@&lt;/span&gt; func getAdFromBs(w http.ResponseWriter, r *http.Request) {
        day, _ := strconv.Atoi(splitedDate[2])
        month, _ := strconv.Atoi(splitedDate[1])
        year, _ := strconv.Atoi(splitedDate[0])
&lt;span class="gd"&gt;-       date, _ := bsdate.New(day, month, year)
&lt;/span&gt;&lt;span class="gi"&gt;+       date, err := bsdate.New(day, month, year)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusBadRequest)
+               return
+       }
&lt;/span&gt;        gregorianDate, _ := date.GetGregorianDate()
        fmt.Fprintf(w, gregorianDate.Format("2006-01-02"))
 }
&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="gh"&gt;index 3156498..16c48ab 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/bsdateServer_test.go
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/bsdateServer_test.go
&lt;/span&gt;&lt;span class="p"&gt;@@ -35,7 +35,7 @@&lt;/span&gt; func theHTTPresponseCodeShouldBe(expectedCode int) error {
&lt;span class="err"&gt;
&lt;/span&gt; func theResponseContentShouldBe(expectedContent string) error {
     body, _ := ioutil.ReadAll(res.Body)
&lt;span class="gd"&gt;-    if expectedContent != string(body) {
&lt;/span&gt;&lt;span class="gi"&gt;+    if expectedContent != strings.TrimSpace(string(body)) {
&lt;/span&gt;         return fmt.Errorf("status code not as expected! Expected '%s', got '%s'", expectedContent, string(body))
     }
     return nil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In &lt;code&gt;main.go&lt;/code&gt; we now spit out an Error if the conversion does not work and in the tests we trim the body, because &lt;code&gt;http.Error&lt;/code&gt; likes to send an &lt;code&gt;\n&lt;/code&gt; at the end of the body.&lt;/p&gt;

&lt;p&gt;Finally, the scenarios pass:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Feature: convert dates from BS to AD using an API
  As an app-developer in Nepal
  I want to be able to send BS dates to an API endpoint and receive the corresponding AD dates
  So that I have a simple way to convert BS to AD dates, that can be used in different apps

  Scenario: converting a valid BS date                                    # bs-to-ad-conversion.feature:6
    When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01" # bsdateServer_test.go:14 -&amp;gt; aRequestIsSentToTheEndpoint
    Then the HTTP-response code should be "200"                           # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "2003-07-17"                       # bsdateServer_test.go:34 -&amp;gt; theResponseContentShouldBe

  Scenario: converting an invalid BS date                               # bs-to-ad-conversion.feature:11
    When a "GET" request is sent to the endpoint "/ad-from-bs/60-13-01" # bsdateServer_test.go:14 -&amp;gt; aRequestIsSentToTheEndpoint
    Then the HTTP-response code should be "400"                         # bsdateServer_test.go:27 -&amp;gt; theHTTPresponseCodeShouldBe
    And the response content should be "not a valid date"               # bsdateServer_test.go:34 -&amp;gt; theResponseContentShouldBe

2 scenarios (2 passed)
6 steps (6 passed)
1.343415ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;p&gt;The scenarios we have written down are pretty limited, probably there are more requirements of the software. Specially there will be those that have not been spoken about. To reduce the size of the feature-file Gherkin has the &lt;code&gt;Examples:&lt;/code&gt; keyword.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;index 5a00814..18db1ed 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/features/bs-to-ad-convertion.feature
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/features/bs-to-ad-convertion.feature
&lt;/span&gt;&lt;span class="p"&gt;@@ -3,12 +3,25 @@&lt;/span&gt; Feature: convert dates from BS to AD using an API
   I want to be able to send BS dates to an API endpoint and receive the corresponding AD dates
   So that I have a simple way to convert BS to AD dates, that can be used in other apps
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-  Scenario: converting a valid BS date
-  When a "GET" request is sent to the endpoint "/ad-from-bs/2060-04-01"
&lt;/span&gt;&lt;span class="gi"&gt;+  Scenario Outline: converting a valid BS date
+    When a "GET" request is sent to the endpoint "/ad-from-bs/&amp;lt;bs-date&amp;gt;"
&lt;/span&gt;     Then the HTTP-response code should be "200"
&lt;span class="gd"&gt;-    And the response content should be "2003-07-17"
&lt;/span&gt;&lt;span class="gi"&gt;+    And the response content should be "&amp;lt;ad-date&amp;gt;"
+    Examples:
+      | bs-date    | ad-date    |
+      | 2060-04-01 | 2003-07-17 |
+      | 2040-01-01 | 1983-04-14 |
+      | 2040-12-30 | 1984-04-12 |
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Instead of &lt;code&gt;Scenario&lt;/code&gt; we have to use &lt;code&gt;Scenario Outline&lt;/code&gt; and at the bottom of the Outline we add a table. The headings of the table are used as "variables" and the table rows are substituted into the steps e.g. &lt;code&gt;&amp;lt;bs-date&amp;gt;&lt;/code&gt; becomes &lt;code&gt;2060-04-01&lt;/code&gt;.&lt;br&gt;
Godog will run a single scenario for every line in the examples table. That way you can very easily multiply out the test cases.&lt;/p&gt;

&lt;p&gt;To learn more about Scenario Outlines and Example-tables read this blog post: &lt;a href="https://dev.to/jankaritech/scenario-outline-in-gherkin-feature-file-16jh"&gt;Scenario Outline In Gherkin Feature File &lt;/a&gt; by &lt;/p&gt;
&lt;div class="ltag__user ltag__user__id__338710"&gt;
    &lt;a href="/jasson99" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s63l-ZZY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/practicaldev/image/fetch/s--ikRqYrG9--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/338710/c3a14bef-1daf-4af6-8a52-192408b68681.jpeg" alt="jasson99 image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/jasson99"&gt;jasson99&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/jasson99"&gt;Computer Engineer&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Writing down the expected behaviors using the Gherkin language can improve the communication between the different stakeholders and with that increase customer satisfaction, productivity and the chances to make the project a success.&lt;/li&gt;
&lt;li&gt;The feature descriptions become the requirement documentation.&lt;/li&gt;
&lt;li&gt;Additionally, the same feature descriptions can be used to run automatic tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you need help with setting up BDD or you want to outsource your test-development, please contact us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.jankaritech.com/"&gt;https://www.jankaritech.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/company/jankaritech/"&gt;https://www.linkedin.com/company/jankaritech/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>bdd</category>
      <category>go</category>
      <category>qa</category>
    </item>
    <item>
      <title>performance testing with locust - 04 - interpret the results</title>
      <dc:creator>Artur Neumann</dc:creator>
      <pubDate>Tue, 14 Jan 2020 11:28:18 +0000</pubDate>
      <link>https://dev.to/jankaritech/performance-testing-with-locust-04-interpret-the-results-3hco</link>
      <guid>https://dev.to/jankaritech/performance-testing-with-locust-04-interpret-the-results-3hco</guid>
      <description>&lt;p&gt;In the last posts of this series we setup locust and made some basic performance tests to test the ownCloud WebDAV-API. This time we will try to make some sense of the locust output.&lt;/p&gt;

&lt;p&gt;Here is the locust file we are using:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

 from locust import HttpLocust, TaskSet, task, constant
import uuid

userNo = 0

class UserBehaviour(TaskSet):
    adminUserName = 'admin'
    davEndpoint = "/remote.php/dav/files/"
    fileName = ''
    userName = ''

    def on_start(self):
        #create user
        global userNo
        self.userName = "locust" + str(userNo)
        userNo = userNo + 1
        self.client.post(
            "/ocs/v2.php/cloud/users",
            {"userid": self.userName, "password": self.userName},
            auth=(self.adminUserName, self.adminUserName)
        )

    def on_stop(self):
        from locust.clients import HttpSession
        self.admin_client = HttpSession(base_url=self.client.base_url)
        self.admin_client.delete(
            "/ocs/v2.php/cloud/users/" + self.userName,
            auth=(self.adminUserName, self.adminUserName)
        )

    @task(3)
    def downloadFile(self):
        self.client.get(
            self.davEndpoint  + self.userName + "/ownCloud%20Manual.pdf",
            auth=(self.userName, self.userName)
        )

    @task(1)
    def uploadFile(self):
        if self.fileName == '':
            self.fileName = "/locust-perfomance-test-file" + str(uuid.uuid4()) + ".txt"
        self.client.put(
            self.davEndpoint + self.userName + self.fileName,
            "my data",
            auth=(self.userName, self.userName)
        )

class User(HttpLocust):
    task_set = UserBehaviour
    wait_time = constant(1)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To start ownCloud we have used docker: &lt;code&gt;docker run -p 8080:8080 --name owncloud owncloud/server&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and then started locust with: &lt;code&gt;locust --host=http://localhost:8080&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  test the right thing
&lt;/h2&gt;

&lt;p&gt;When I now run both ownCloud and locust on my workstation (i5-7500 CPU @ 3.40GHz; 8GB RAM) and hatch 100 locust-users I get this graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fttgci4s4bwoyl8mpvi9n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fttgci4s4bwoyl8mpvi9n.png" alt="locust output when running app and locust on same computer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But now have a look at the CPU usage (on Linux the easiest way to see it is to use the &lt;code&gt;top&lt;/code&gt; command)&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcecd04c0mti3xhn4n257.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fcecd04c0mti3xhn4n257.png" alt="CPU usage when running app and locust on same computer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;WOW, 61.7% CPU is used by locust itself. I'm not really testing the performance of ownCloud (or not alone). Beside locust gnome, X and Firefox are eating up a significant amount of resources, so the results will never be accurate. Better get some dedicated hardware to run ownCloud on.&lt;/p&gt;

&lt;p&gt;I have here an old Lenovo X201 Laptop (i5 M 540 CPU @ 2.53GHz; 4GB RAM). Not really fast, but should be OK for this example. I will run ownCloud on that Laptop and locust on my workstation. That way hatching 100 users still eats up the same amount of resources on the workstation, but because its fast enough that should not be the limiting factor. We really don't want the test-runner computer to limit our performance tests. If you don't have a computer that is fast enough to fully load your SUT (System Under Test), you can run &lt;a href="https://docs.locust.io/en/stable/running-locust-distributed.html" rel="noopener noreferrer"&gt;locus distributed&lt;/a&gt; and that way utilize multiple computers to fully load your SUT.&lt;/p&gt;

&lt;h2&gt;
  
  
  interpret the results
&lt;/h2&gt;

&lt;p&gt;Lets start the tests and increase the amount of users.&lt;br&gt;
I started the test with 20 users and 1 user/s hatch rate, then increased the users to 50 with 2 users/s hatch rate and finally to 100 users with 4 users/s hatch rate.&lt;/p&gt;

&lt;p&gt;In the response-time graph the green line shows the median response time and the yellow one the 95th percentile (95% of the requests finish before that time).&lt;/p&gt;

&lt;p&gt;To calculate the &lt;em&gt;current&lt;/em&gt; response time a sliding window of (approximately) the last 10 seconds is used see: &lt;a href="https://github.com/locustio/locust/blob/6ba31c83acae6d26297a23de0eaaef34b3838330/locust/stats.py#L504" rel="noopener noreferrer"&gt;get_current_response_time_percentile function&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As you can see, the median response time goes up as we add more users. And there is a "bump" in the 95th percentile line every time new users are created. So it looks like user creation is "expensive". (The "bump" is also visible in the median-line, but not that obvious).&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnvv42xjpkenbvj28nns9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnvv42xjpkenbvj28nns9.png" alt="response time vs users"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rerunning the tests shows a similar result.&lt;/p&gt;

&lt;p&gt;BTW: Because there is always other stuff happening on the server its always good to run performance tests multiple times and see if you get similar results.&lt;/p&gt;

&lt;p&gt;So from that information, how many users can our system handle? Maybe the better question is how long do you want your user to wait? In our test-scenario the user sends one request every second, either a download or an upload request. The download request appears 3 times more often than the upload (see "Weight of a task" in the &lt;a href="https://dev.to/jankaritech/performance-testing-with-locust-02-multiple-tasks-4ckn"&gt;multiple tasks&lt;/a&gt; part).&lt;br&gt;
If our server tries to serve 20 such users, 95% of the time it will be able to respond within 400-450ms or less (not taking the user-creation into account, in normal life we would not create new users all the time). When trying to serve 50 concurrent users, 95% of the time it will be able to respond within 1600-1800ms or less. And half of the time (median response time) users will have to wait for around 1000ms or more for a response. For 100 users that obviously looks even worse, 95th percentile is around 6000ms and median response time around 3200ms.&lt;/p&gt;

&lt;p&gt;Would it be acceptable for your application to let the user wait for 3sec or more for half of the requests? If not, you need to optimize the software or buy more hardware.&lt;/p&gt;

&lt;p&gt;To see more details and maybe make more analysis download the CSV data and open in a spreadsheets app.&lt;br&gt;
These files have one line per request type &amp;amp; URL, because we have the username in the URL, there will be a lot of lines.&lt;/p&gt;

&lt;p&gt;In the request statistics CSV file we have the median/average/min/max response time for all uploads user0 has done, and all uploads user1 has done and so on. We can e.g. calculate the average response time of all uploads with the formula &lt;code&gt;=AVERAGEIF(A2:A301;"PUT";F2:F301)&lt;/code&gt; (tested with &lt;a href="https://www.libreoffice.org" rel="noopener noreferrer"&gt;LibreOffice&lt;/a&gt;). Column A holds the method name, column F is the average response time and in my table there are 301 lines.&lt;/p&gt;

&lt;p&gt;Be aware that this list will now hold ALL the results, from the time when we had 20 users, 50 users and 100 users, so if we want to know the average response time of uploads with a particular amount of users, we would have to rerun the test with a fixed amount of users and not change it in between.&lt;/p&gt;

&lt;h2&gt;
  
  
  optimization
&lt;/h2&gt;

&lt;p&gt;When we have started the ownCloud docker container, it created an database and for that it used SQlite database, that is good for quick testing and evaluation, but its soooo slow. Have a look at the &lt;a href="https://doc.owncloud.com/server/10.3/admin_manual/installation/system_requirements.html#server" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, ownCloud says SQLite is not for production and recommends to use MySQL or MariaDB.&lt;br&gt;
The simplest way to start ownCloud with MariaDB is to use &lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;docker-compose&lt;/a&gt; as described &lt;a href="https://doc.owncloud.com/server/admin_manual/installation/docker/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. In addition you also receive a Redis server, to do some caching.&lt;/p&gt;

&lt;p&gt;Running that proposed setup on my system shows that it improves the response time a lot when running with 20 users, the 95th percentile goes down to 220-250ms (vs 400-450 before), there is also some improvement when running with 50 users, but when running with 100 users, it actually gets worse (median 5200-6000ms and 95th percentile is often over 7000ms).&lt;/p&gt;

&lt;p&gt;More tests showed that with 15 concurrent users there is still 20-30% CPU time left most of the time, but with 20+ users the CPU is basically flat out.&lt;br&gt;
Another interesting finding is, that in the area around 15 users the CPU is still not fully utilized, but the hard-drive works already pretty hard (see &lt;code&gt;iotop&lt;/code&gt;). My guess is that when running with &amp;lt;= 15 users a faster hard-drive, e.g. a SSD would improve the performance, but with more than 20 users an SSD would be a waste of money, because even if the data would arrive faster at the CPU, it struggles to do its calculation.&lt;/p&gt;

&lt;h2&gt;
  
  
  cross-check
&lt;/h2&gt;

&lt;p&gt;Let's see if we can prove our assumption that ~15 users should be the max for our system. I'm simulating 30 users, but with a hatch-rate of 0.025 users/sec (I want to give the system enough time to create the user and to refresh the sliding window for the chart after user-creation).&lt;/p&gt;

&lt;p&gt;Looking at the graph I see that up to ~10 users the median time does not change much (160-180ms), looking at the output of &lt;code&gt;top&lt;/code&gt; at the same time I see that there is still a lot of CPU time unused and even with 14-15 users, the median time goes down to 190ms. After that pretty flat area in the graph, it goes up pretty steep, the CPU is totally flat out.&lt;/p&gt;

&lt;p&gt;Also have a look at the "Total Requests per Second" graph. Up to 15 users it steadily climbs up, but then there are valleys and hills, but the system struggles to serve more requests/s.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F37vg2xi4wxdsg1wtn0mp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F37vg2xi4wxdsg1wtn0mp.png" alt="slowly increasing the number of users"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  conclusion
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The system scales well up to 15 users, meaning the single user would not experience any performance issues up to 15 concurrent users. Also the user would not experience any faster system if she is the only user on the system.&lt;/li&gt;
&lt;li&gt;Up to 15 users the system can be optimized by using a better DB, caching, faster HDD and memory&lt;/li&gt;
&lt;li&gt;Above 15 users, the CPU is the bottleneck and working on the suggestions in point 2, would not help.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>qa</category>
      <category>performance</category>
      <category>testing</category>
      <category>python</category>
    </item>
    <item>
      <title>Demonstrating TDD (Test-driven development) in Go</title>
      <dc:creator>Artur Neumann</dc:creator>
      <pubDate>Wed, 01 Jan 2020 09:40:39 +0000</pubDate>
      <link>https://dev.to/jankaritech/demonstrating-tdd-test-driven-development-in-go-27b0</link>
      <guid>https://dev.to/jankaritech/demonstrating-tdd-test-driven-development-in-go-27b0</guid>
      <description>&lt;p&gt;TDD is the practice to write tests before code and it should reduce failure rates and defects in your software.&lt;br&gt;
In this blog-post I want to demonstrate how it can work.&lt;/p&gt;
&lt;h2&gt;
  
  
  starting point
&lt;/h2&gt;

&lt;p&gt;I'm writing an application in Go that should convert Bikram Sambat (BS) (also called Vikram Samvat) dates to Gregorian dates and vice-versa. &lt;a href="https://en.wikipedia.org/wiki/Vikram_Samvat"&gt;Vikram Samvat&lt;/a&gt; is a calendar used mostly in Nepal and India. But even if you don't use it, this demonstration might be useful for you to understand TDD.&lt;/p&gt;

&lt;p&gt;So far I have done a bit of work that makes it possible to create a BS (Bikram Sambat) date instance, to get its details and to convert it to a Gregorian date. See: &lt;a href="https://github.com/JankariTech/GoBikramSambat/blob/b99c510b22faf8395becda9a6dec1d0239504bb1/bsdate.go"&gt;https://github.com/JankariTech/GoBikramSambat/blob/b99c510b22faf8395becda9a6dec1d0239504bb1/bsdate.go&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These functions are also tested: &lt;a href="https://github.com/JankariTech/GoBikramSambat/blob/b99c510b22faf8395becda9a6dec1d0239504bb1/bsdate_test.go"&gt;https://github.com/JankariTech/GoBikramSambat/blob/b99c510b22faf8395becda9a6dec1d0239504bb1/bsdate_test.go&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I want to add the possibility to convert a Gregorian date to a Bikram Sambat date. To do so, I want to be able to create a BS-date-instance by using a Gregorian date, then I could just get the BS-date details and the conversion is done.&lt;/p&gt;

&lt;p&gt;Something like &lt;code&gt;nepaliDate, err := NewFromGregorian(gregorianDay, gregorianMonth, gregorianYear)&lt;/code&gt; would be great and then simply use the existing &lt;code&gt;nepaliDate.GetDay()&lt;/code&gt; &lt;code&gt;nepaliDate.GetMonth()&lt;/code&gt; and &lt;code&gt;nepaliDate.GetYear()&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  1. create the test
&lt;/h2&gt;

&lt;p&gt;According to TDD I first have to create a test.&lt;br&gt;
So in the file &lt;code&gt;bsdate_test.go&lt;/code&gt; I create a new function called &lt;code&gt;TestCreateFromGregorian()&lt;/code&gt;.&lt;br&gt;
As I already have a table of test-dates that are used for the conversion from Nepali to Gregorian I will use that data also to test the reverse conversion.&lt;/p&gt;

&lt;p&gt;Here is the test data and the test function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type TestDateConversionStruc struct {
    bsDate        string
    gregorianDate string
}

var convertedDates = []TestDateConversionStruc{
    {"2068-04-01", "2011-07-17"}, //a random date
    {"2068-01-01", "2011-04-14"}, //1st Baisakh
    {"2037-11-28", "1981-03-11"},
    {"2038-09-17", "1982-01-01"}, //1st Jan
    {"2040-09-17", "1984-01-01"}, //1st Jan in a leap year
...
}

func TestCreateFromGregorian(t *testing.T) {
    for _, testCase := range convertedDates {
        t.Run(testCase.bsDate, func(t *testing.T) {
            var splitedBSDate = strings.Split(testCase.bsDate, "-")
            var expectedBsDay, _ = strconv.Atoi(splitedBSDate[2])
            var expectedBsMonth, _ = strconv.Atoi(splitedBSDate[1])
            var expectedBsYear, _ = strconv.Atoi(splitedBSDate[0])

            var splitedGregorianDate = strings.Split(testCase.gregorianDate, "-")
            var gregorianDay, _ = strconv.Atoi(splitedGregorianDate[2])
            var gregorianMonth, _ = strconv.Atoi(splitedGregorianDate[1])
            var gregorianYear, _ = strconv.Atoi(splitedGregorianDate[0])

            nepaliDate, err := NewFromGregorian(gregorianDay, gregorianMonth, gregorianYear)
            assert.Equal(t, err, nil)
            assert.Equal(t, nepaliDate.GetDay(), expectedBsDay)
            assert.Equal(t, nepaliDate.GetMonth(), expectedBsMonth)
            assert.Equal(t, nepaliDate.GetYear(), expectedBsYear)
        })
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function takes entries from the &lt;code&gt;convertedDates&lt;/code&gt; list, splits them, tries to create a BS date out of the particular gregorian test-case and then asserts that the BS date (day, month, year) is as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. run the tests
&lt;/h2&gt;

&lt;p&gt;The test is done, according to TDD I have to run it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;go test -v&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;results in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# NepaliCalendar/bsdate [NepaliCalendar/bsdate.test]
./bsdate_test.go:171:23: undefined: NewFromGregorian
FAIL    NepaliCalendar/bsdate [build failed]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was expected, the function does not exist, no wonder my tests fail. What to do next? Guess what: implement the function.&lt;br&gt;
That makes TDD so easy, you just do what the tests tell you to fix.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. fix it
&lt;/h2&gt;

&lt;p&gt;That's easy, add to &lt;code&gt;bsdate.go&lt;/code&gt; a new function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func NewFromGregorian(gregorianDay, gregorianMonth, gregorianYear int) (Date, error) {

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. repeat
&lt;/h2&gt;

&lt;p&gt;running the tests again I get:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./bsdate.go:195:1: missing return at end of function&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That is true, let's return something, but what? Hey let's just create a BS date with the Gregorian numbers&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; func NewFromGregorian(gregorianDay, gregorianMonth, gregorianYear int) (Date, error) {
&lt;span class="gd"&gt;-
&lt;/span&gt;&lt;span class="gi"&gt;+       return New(gregorianDay, gregorianMonth, gregorianYear)
&lt;/span&gt; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are saying that will not work? I don't care, I do TDD, the test tells me to return something, and I do return, I even return the correct type of value.&lt;/p&gt;

&lt;p&gt;lets run the tests again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== RUN   TestCreateFromGregorian/2068-04-01
    assert.go:24: got '17' want '1'

    assert.go:24: got '7' want '4'

    assert.go:24: got '2011' want '2068'

=== RUN   TestCreateFromGregorian/2068-01-01
    assert.go:24: got '14' want '1'

    assert.go:24: got '4' want '1'

    assert.go:24: got '2011' want '2068'

....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;a lot of failures, you have guessed it, the conversion does not work. So lets implement some bits.&lt;/p&gt;

&lt;p&gt;We know that BS is 56 point something years ahead of Gregorian. So adding 56 to the gregorian year should help:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; func NewFromGregorian(gregorianDay, gregorianMonth, gregorianYear int) (Date, error) {
&lt;span class="gd"&gt;-       return New(gregorianDay, gregorianMonth, gregorianYear)
&lt;/span&gt;&lt;span class="gi"&gt;+       var bsYear = gregorianYear + 56
+       return New(gregorianDay, gregorianMonth, bsYear)
&lt;/span&gt; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;test results look better, instead of&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;....
=== RUN   TestCreateFromGregorian/2037-11-28
    assert.go:24: got '11' want '28'

    assert.go:24: got '3' want '11'

    assert.go:24: got '1981' want '2037'

=== RUN   TestCreateFromGregorian/2038-09-17
    assert.go:24: got '1' want '17'

    assert.go:24: got '1' want '9'

    assert.go:24: got '1982' want '2038'
....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;....
=== RUN   TestCreateFromGregorian/2037-11-28
    assert.go:24: got '11' want '28'

    assert.go:24: got '3' want '11'

=== RUN   TestCreateFromGregorian/2038-09-17
    assert.go:24: got '1' want '17'

    assert.go:24: got '1' want '9'
....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So some years are calculated correctly, at least. Lets fix more tests by calculating the years more accurate and also calculate the BS month.&lt;/p&gt;

&lt;p&gt;Because of the way the BS-calendar works, there is no algorithm to convert the date directly from the Gregorian calendar, we need a table. We know that Jan 1st falls always in the 9th BS month (Paush). So we have a table of BS years where the first value is the day in Paush that is the 1st Jan in that year, then a list of days of every BS month.&lt;br&gt;
We can easily get the day-of-year from the gregorian date. Starting from Paush, we count the days of each BS month, whenever we get over the gregorian day-of-year, we found the correct BS month.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2074: [13]int{17, 31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30},
2075: [13]int{17, 31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30},
2076: [13]int{16, 31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 30},
2077: [13]int{17, 31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31},
2078: [13]int{17, 31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These details have nothing to do with TDD, but help you to understand the coming algorithm.&lt;/p&gt;

&lt;p&gt;lets put it into code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; func NewFromGregorian(gregorianDay, gregorianMonth, gregorianYear int) (Date, error) {
        var bsYear = gregorianYear + 56
&lt;span class="gd"&gt;-       return New(gregorianDay, gregorianMonth, bsYear)
&lt;/span&gt;&lt;span class="gi"&gt;+       var bsMonth = 9                         //Jan 1 always fall in BS month Paush which is the 9th month
+       var daysSinceJanFirstToEndOfBsMonth int //days calculated from 1st Jan till the end of the actual BS month,
+                                               // we use this value to check if the gregorian Date is in the actual BS month
+
+       year := time.Date(gregorianYear, time.Month(gregorianMonth), gregorianDay, 0, 0, 0, 0, time.UTC)
+       var gregorianDayOfYear = year.YearDay()
+
+       //get the BS day in Paush (month 9) of 1st January
+       var dayOfFirstJanInPaush = calendardata[bsYear][0]
+
+       //check how many days are left of Paush
+       daysSinceJanFirstToEndOfBsMonth = calendardata[bsYear][bsMonth] - dayOfFirstJanInPaush + 1
+
+       //If the gregorian day-of-year is smaller or equal to the sum of days between the 1st January and
+       //the end of the actual BS month we found the correct nepali month.
+       //Example:
+       //The 4th February 2011 is the gregorianDayOfYear 35 (31 days of January + 4)
+       //1st January 2011 is in the BS year 2067 and its the 17th day of Paush (9th month)
+       //In 2067 Paush had 30days, This means (30-17+1=14) there are 14days between 1st January and end of Paush
+       //(including 17th January)
+       //The gregorianDayOfYear (35) is bigger than 14, so we check the next month
+       //The next BS month (Mangh) has 29 days
+       //29+14=43, this is bigger than gregorianDayOfYear(35) so, we found the correct nepali month
+       for ; gregorianDayOfYear &amp;gt; daysSinceJanFirstToEndOfBsMonth; {
+               bsMonth++
+               if bsMonth &amp;gt; 12 {
+                       bsMonth = 1
+                       bsYear++
+               }
+               daysSinceJanFirstToEndOfBsMonth += calendardata[bsYear][bsMonth]
+       }
+
+       return New(gregorianDay, bsMonth, bsYear)
&lt;/span&gt; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and now? You guessed it! Run the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== RUN   TestCreateFromGregorian
=== RUN   TestCreateFromGregorian/2068-04-01
    assert.go:24: got '17' want '1'

=== RUN   TestCreateFromGregorian/2068-01-01
    assert.go:24: got '14' want '1'

=== RUN   TestCreateFromGregorian/2037-11-28
    assert.go:24: got '11' want '28'

....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Actually, while implementing the algorithm I've run the tests multiple times and found mistakes in mixed-up variable names and other rubbish. That's cool, the tests helped me to find the issues right away.&lt;/p&gt;

&lt;p&gt;But the tests still fail, I better get the day calculation correct.&lt;br&gt;
We know the correct BS month, and we know the days since 1st Jan till the end of this month. Subtracting the day-of-the-year of the gregorian calendar from the days since 1st Jan till the end of the correct BS month will give us the amount of days between the searched day and the end of the BS month. Subtracting that number from the amount of days in the BS month should bring us to the correct date.&lt;/p&gt;

&lt;p&gt;So many words to describe it, so little effort to write it in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-       return New(gregorianDay, bsMonth, bsYear)
&lt;/span&gt;&lt;span class="gi"&gt;+       var bsDay = calendardata[bsYear][bsMonth] - (daysSinceJanFirstToEndOfBsMonth - gregorianDayOfYear)
+
+       return New(bsDay, bsMonth, bsYear)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hear you shouting: "Run the tests, run the tests!" Don't worry, I will:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== RUN   TestCreateFromGregorian
=== RUN   TestCreateFromGregorian/2068-04-01
=== RUN   TestCreateFromGregorian/2068-01-01
=== RUN   TestCreateFromGregorian/2037-11-28
=== RUN   TestCreateFromGregorian/2038-09-17
=== RUN   TestCreateFromGregorian/2040-09-17
=== RUN   TestCreateFromGregorian/2040-09-18
=== RUN   TestCreateFromGregorian/2041-09-17
=== RUN   TestCreateFromGregorian/2041-09-18
=== RUN   TestCreateFromGregorian/2068-09-01
=== RUN   TestCreateFromGregorian/2068-08-29
=== RUN   TestCreateFromGregorian/2068-09-20
=== RUN   TestCreateFromGregorian/2077-08-30
=== RUN   TestCreateFromGregorian/2077-09-16
=== RUN   TestCreateFromGregorian/2074-09-16
=== RUN   TestCreateFromGregorian/2077-09-17
=== RUN   TestCreateFromGregorian/2077-09-01
=== RUN   TestCreateFromGregorian/2076-11-17
=== RUN   TestCreateFromGregorian/2076-11-18
=== RUN   TestCreateFromGregorian/2075-11-16
=== RUN   TestCreateFromGregorian/2076-02-01
=== RUN   TestCreateFromGregorian/2076-02-32
=== RUN   TestCreateFromGregorian/2076-03-01
--- PASS: TestCreateFromGregorian (0.00s)
    --- PASS: TestCreateFromGregorian/2068-04-01 (0.00s)
    --- PASS: TestCreateFromGregorian/2068-01-01 (0.00s)
    --- PASS: TestCreateFromGregorian/2037-11-28 (0.00s)
    --- PASS: TestCreateFromGregorian/2038-09-17 (0.00s)
    --- PASS: TestCreateFromGregorian/2040-09-17 (0.00s)
    --- PASS: TestCreateFromGregorian/2040-09-18 (0.00s)
    --- PASS: TestCreateFromGregorian/2041-09-17 (0.00s)
    --- PASS: TestCreateFromGregorian/2041-09-18 (0.00s)
    --- PASS: TestCreateFromGregorian/2068-09-01 (0.00s)
    --- PASS: TestCreateFromGregorian/2068-08-29 (0.00s)
    --- PASS: TestCreateFromGregorian/2068-09-20 (0.00s)
    --- PASS: TestCreateFromGregorian/2077-08-30 (0.00s)
    --- PASS: TestCreateFromGregorian/2077-09-16 (0.00s)
    --- PASS: TestCreateFromGregorian/2074-09-16 (0.00s)
    --- PASS: TestCreateFromGregorian/2077-09-17 (0.00s)
    --- PASS: TestCreateFromGregorian/2077-09-01 (0.00s)
    --- PASS: TestCreateFromGregorian/2076-11-17 (0.00s)
    --- PASS: TestCreateFromGregorian/2076-11-18 (0.00s)
    --- PASS: TestCreateFromGregorian/2075-11-16 (0.00s)
    --- PASS: TestCreateFromGregorian/2076-02-01 (0.00s)
    --- PASS: TestCreateFromGregorian/2076-02-32 (0.00s)
    --- PASS: TestCreateFromGregorian/2076-03-01 (0.00s)
PASS
ok      NepaliCalendar/bsdate   0.002s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tests pass, job done! Commit it, push it and get yourself some &lt;a href="https://www.youtube.com/watch?v=LO0k9rmKneI"&gt;Chiya&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;You can find all the changes of this post here: &lt;a href="https://github.com/JankariTech/GoBikramSambat/pull/4/"&gt;https://github.com/JankariTech/GoBikramSambat/pull/4/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  conclusion
&lt;/h2&gt;

&lt;p&gt;TDD is easy: think about what you want to achieve, write tests for it and wildly hack code till your tests pass.&lt;/p&gt;

&lt;p&gt;An other big advantage is: I can refactor my code all I like and still be confident it works fine. Maybe I want to optimize the speed of the algorithm, maybe I don't like it altogether and come up with a better one, or I simply want to change variable names. I can do that all without fear of messing up the functionality, as long as my tests are passing I'm pretty sure the code reacts the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  maybe the next step
&lt;/h2&gt;

&lt;p&gt;An other useful principle in software development is &lt;a href="https://dev.to/jankaritech/demonstrating-bdd-behavior-driven-development-in-go-1eci"&gt;BDD (Behavior-driven development)&lt;/a&gt;, it emerged out of TDD and uses its general principles but focuses not on defining and testing a single unit (function) but on describing the behaviour of the system and by that improving the communication between different stakeholders of the project. I've written a post about BDD using the same project and taking it further: &lt;a href="https://dev.to/jankaritech/demonstrating-bdd-behavior-driven-development-in-go-1eci"&gt;https://dev.to/jankaritech/demonstrating-bdd-behavior-driven-development-in-go-1eci&lt;/a&gt; &lt;/p&gt;

</description>
      <category>tdd</category>
      <category>testing</category>
      <category>go</category>
    </item>
    <item>
      <title>performance testing with locust - 03 - setup your system</title>
      <dc:creator>Artur Neumann</dc:creator>
      <pubDate>Wed, 27 Nov 2019 08:43:24 +0000</pubDate>
      <link>https://dev.to/jankaritech/performance-testing-with-locust-03-setup-your-system-3ai7</link>
      <guid>https://dev.to/jankaritech/performance-testing-with-locust-03-setup-your-system-3ai7</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/jankaritech/performance-testing-with-locust-02-multiple-tasks-4ckn"&gt;last part of this series&lt;/a&gt; we created multiple tasks to be able to simulate more realistically uploading and downloading files.&lt;/p&gt;

&lt;p&gt;Now we want to go the next step and use multiple ownCloud users.&lt;/p&gt;

&lt;p&gt;I will use the term "ownCloud user" for users that are set-up in the ownCloud system, have a username and password and can be used to login into the system. When I'm using the term "locust user" I'm talking about simulated users that hammer the server with requests. So far we used only one ownCloud user "admin" and multiple locust users. All locust users used that one ownCloud user to access the ownCloud server.&lt;/p&gt;

&lt;p&gt;In this part of the series we want to have one ownCloud user for every locust user, so every &lt;code&gt;TaskSet&lt;/code&gt; will be connecting with an own ownCloud user to the ownCloud server.&lt;/p&gt;

&lt;h2&gt;
  
  
  setup and teardown
&lt;/h2&gt;

&lt;p&gt;This situation is pretty common in any kind of automated testing, before starting the tests often you have to set your system up and bring it into a "testable" state, or simply into the state you want to test.&lt;br&gt;
Basically all test frameworks will have some kind of &lt;code&gt;setup&lt;/code&gt; and &lt;code&gt;teardown&lt;/code&gt; methods or hooks. &lt;a href="https://docs.locust.io/en/stable/writing-a-locustfile.html#setups-and-teardowns" rel="noopener noreferrer"&gt;Same applies to locust, you even can have a separate &lt;code&gt;setup&lt;/code&gt; and &lt;code&gt;teardown&lt;/code&gt; method in your Locust class and your TaskSet class.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;consider this simple locust script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from locust import HttpLocust, TaskSet, task, constant

class UserBehaviour(TaskSet):
    def setup(self):
        print ("setup of TaskSet")

    def teardown(self):
        print ("teardown of TaskSet")

    @task(2)
    def one_task(self):
        print ("running one task")

    @task(1)
    def an_other_task(self):
        print ("running another task")

class User(HttpLocust):
    def setup(self):
        print ("setup of Locust class")

    def teardown(self):
        print ("teardown of Locust class")

    task_set = UserBehaviour
    wait_time = constant(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have two tasks and a &lt;code&gt;setup&lt;/code&gt; and a &lt;code&gt;teardown&lt;/code&gt;, one in the &lt;code&gt;User&lt;/code&gt; class and one in the &lt;code&gt;UserBehavior&lt;/code&gt; class&lt;/p&gt;

&lt;p&gt;Now lets see what happens.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Starting locust from the CLI:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[2019-11-29 08:55:19,213] artur-OptiPlex-3050/INFO/locust.main: Starting web monitor at *:8089
[2019-11-29 08:55:19,213] artur-OptiPlex-3050/INFO/locust.main: Starting Locust 0.13.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Starting the test from the webUI with 2 users&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[2019-11-29 08:55:22,932] artur-OptiPlex-3050/INFO/locust.runners: Hatching and swarming 2 clients at the rate 1 clients/s...
[2019-11-29 08:55:22,932] artur-OptiPlex-3050/INFO/stdout: setup of Locust class
[2019-11-29 08:55:22,932] artur-OptiPlex-3050/INFO/stdout: 
[2019-11-29 08:55:22,933] artur-OptiPlex-3050/INFO/stdout: setup of TaskSet
[2019-11-29 08:55:22,933] artur-OptiPlex-3050/INFO/stdout: 
[2019-11-29 08:55:22,933] artur-OptiPlex-3050/INFO/stdout: running one task
[2019-11-29 08:55:22,933] artur-OptiPlex-3050/INFO/stdout: 
[2019-11-29 08:55:23,933] artur-OptiPlex-3050/INFO/stdout: running another task
[2019-11-29 08:55:23,933] artur-OptiPlex-3050/INFO/stdout: 
[2019-11-29 08:55:23,934] artur-OptiPlex-3050/INFO/stdout: running one task
[2019-11-29 08:55:23,934] artur-OptiPlex-3050/INFO/stdout: 
[2019-11-29 08:55:24,934] artur-OptiPlex-3050/INFO/locust.runners: All locusts hatched: User: 2
[2019-11-29 08:55:24,934] artur-OptiPlex-3050/INFO/stdout: running one task
[2019-11-29 08:55:24,934] artur-OptiPlex-3050/INFO/stdout: 
[2019-11-29 08:55:24,934] artur-OptiPlex-3050/INFO/stdout: running another task
[2019-11-29 08:55:24,935] artur-OptiPlex-3050/INFO/stdout: 
[2019-11-29 08:55:25,935] artur-OptiPlex-3050/INFO/stdout: running one task
[2019-11-29 08:55:25,935] artur-OptiPlex-3050/INFO/stdout: 
[2019-11-29 08:55:25,935] artur-OptiPlex-3050/INFO/stdout: running one task
[2019-11-29 08:55:25,935] artur-OptiPlex-3050/INFO/stdout: 

...

[2019-11-29 08:55:31,939] artur-OptiPlex-3050/INFO/stdout: 
[2019-11-29 08:55:32,939] artur-OptiPlex-3050/INFO/stdout: running another task
[2019-11-29 08:55:32,939] artur-OptiPlex-3050/INFO/stdout: 
[2019-11-29 08:55:32,939] artur-OptiPlex-3050/INFO/stdout: running one task
[2019-11-29 08:55:32,939] artur-OptiPlex-3050/INFO/stdout: 
[2019-11-29 08:55:33,940] artur-OptiPlex-3050/INFO/stdout: running one task
[2019-11-29 08:55:33,940] artur-OptiPlex-3050/INFO/stdout: 
[2019-11-29 08:55:33,940] artur-OptiPlex-3050/INFO/stdout: running another task
[2019-11-29 08:55:33,940] artur-OptiPlex-3050/INFO/stdout:
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stopping the tests on the webUI&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;pressing &lt;code&gt;Ctrl+C&lt;/code&gt; on the CLI&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^C[2019-11-29 08:55:57,191] artur-OptiPlex-3050/ERROR/stderr: KeyboardInterrupt
[2019-11-29 08:55:57,191] artur-OptiPlex-3050/ERROR/stderr: 2019-11-29T03:10:57Z
[2019-11-29 08:55:57,191] artur-OptiPlex-3050/ERROR/stderr: 
[2019-11-29 08:55:57,191] artur-OptiPlex-3050/INFO/locust.main: Shutting down (exit code 0), bye.
[2019-11-29 08:55:57,191] artur-OptiPlex-3050/INFO/locust.main: Cleaning up runner...
[2019-11-29 08:55:57,191] artur-OptiPlex-3050/INFO/locust.main: Running teardowns...
[2019-11-29 08:55:57,191] artur-OptiPlex-3050/INFO/stdout: teardown of TaskSet
[2019-11-29 08:55:57,191] artur-OptiPlex-3050/INFO/stdout: 
[2019-11-29 08:55:57,191] artur-OptiPlex-3050/INFO/stdout: teardown of Locust class
[2019-11-29 08:55:57,191] artur-OptiPlex-3050/INFO/stdout:
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Did you expect that? &lt;code&gt;teardown&lt;/code&gt; only runs after locust is completely stopped, not when the test is stopped.&lt;br&gt;
Makes sense, but does not help us with our issue, we want to create users before running the actual test and delete them afterwards. We might start and stop the test, without stopping locust and we can increase the locust users during the test and in that case we want to create more ownCloud users on the fly.&lt;/p&gt;

&lt;p&gt;Luckily we have also &lt;code&gt;on_start&lt;/code&gt; and &lt;code&gt;on_stop&lt;/code&gt; methods&lt;/p&gt;
&lt;h2&gt;
  
  
  create users with on_start
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;on_start&lt;/code&gt; is called when the locust user starts to run the TaskSet&lt;/p&gt;

&lt;p&gt;Starting from the last script let's add an &lt;code&gt;on_start&lt;/code&gt; method to create users&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from locust import HttpLocust, TaskSet, task, constant
import uuid

userNo = 0

class UserBehaviour(TaskSet):
    adminUserName = 'admin'
    davEndpoint = "/remote.php/dav/files/"
    fileName = ''
    userName = ''

    def on_start(self):
        #create user
        global userNo
        self.userName = "locust" + str(userNo)
        userNo = userNo + 1
        self.client.post(
            "/ocs/v2.php/cloud/users",
            {"userid": self.userName, "password": self.userName},
            auth=(self.adminUserName, self.adminUserName)
        )

    @task(3)
    def downloadFile(self):
        self.client.get(
            self.davEndpoint  + self.userName + "/ownCloud%20Manual.pdf",
            auth=(self.userName, self.userName)
        )

    @task(1)
    def uploadFile(self):
        if self.fileName == '':
            self.fileName = "/locust-perfomance-test-file" + str(uuid.uuid4()) + ".txt"
        self.client.put(
            self.davEndpoint + self.userName + self.fileName,
            "my data",
            auth=(self.userName, self.userName)
        )

class User(HttpLocust):
    task_set = UserBehaviour
    wait_time = constant(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what is new here?&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;on_start&lt;/code&gt; method first constructs a ownCloud username out of "locust" &amp;amp; a number. The &lt;code&gt;userNo&lt;/code&gt; variable has to be defined globally, so that it survives when locust initialize the next instance of the &lt;code&gt;User&lt;/code&gt; class. Remember: the &lt;code&gt;Locust&lt;/code&gt; class (&lt;code&gt;HttpLocust&lt;/code&gt; inherits from &lt;code&gt;Locust&lt;/code&gt;) represents one simulated user that accesses your application.  &lt;/p&gt;

&lt;p&gt;Next a &lt;code&gt;POST&lt;/code&gt; request is send with the username as userid and password. That requests needs to be authenticated as the admin-user. (&lt;a href="https://doc.owncloud.com/server/10.0/admin_manual/configuration/user/user_provisioning_api.html" rel="noopener noreferrer"&gt;Check the ownCloud docu if you are interested to learn more about those requests.&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;At last there is the &lt;code&gt;davEndpoint&lt;/code&gt;, now it needs the specific username, so that information has been moved into the specific &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;PUT&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;If you run that script now with locust and start a test with, lets say 3 users, you should see something like that:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Flvrkvtk31yq9l3hb88o8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Flvrkvtk31yq9l3hb88o8.png" alt="users created in locust"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first line tells us that 3 &lt;code&gt;POST&lt;/code&gt; requests have been sent to &lt;code&gt;/ocs/v2.php/cloud/users&lt;/code&gt;, that looks promising.&lt;br&gt;
And in the &lt;code&gt;PUT&lt;/code&gt; ans &lt;code&gt;GET&lt;/code&gt; requests, the usernames "locust0" till "locust2" are mentioned, very good!&lt;/p&gt;

&lt;p&gt;Now lets look into the users list of ownCloud. For that login with "admin" / "admin" to &lt;a href="http://localhost:8080/" rel="noopener noreferrer"&gt;http://localhost:8080/&lt;/a&gt; and in the top right corner click on "admin" and then on "Users".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdok79ynnlwknctd1h5yl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdok79ynnlwknctd1h5yl.png" alt="owncloud users list"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Those three users were created and used. If you want to double check use them to login to ownCloud, you should see the uploaded file.&lt;/p&gt;
&lt;h2&gt;
  
  
  delete users with on_stop
&lt;/h2&gt;

&lt;p&gt;The only thing left is to clean up after us. Obviously we can simply kill the docker container, delete it and start it fresh with no users, but wouldn't it be nice to delete the users after stopping the test?&lt;/p&gt;

&lt;p&gt;Let's use &lt;code&gt;on_stop&lt;/code&gt; to clean up! It is run when the TaskSet is stopped.&lt;/p&gt;

&lt;p&gt;Just add a simple small method to the &lt;code&gt;UserBehaviour&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def on_stop(self):
    self.client.delete(
        "/ocs/v2.php/cloud/users/" + self.userName,
        auth=(self.adminUserName, self.adminUserName)
    )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember to delete the users from ownCloud before rerunning the script (or just do &lt;code&gt;docker kill owncloud; docker rm owncloud&lt;/code&gt; and start it again)&lt;/p&gt;

&lt;p&gt;Now when you start the test and stop it again, you will see &lt;code&gt;DELETE&lt;/code&gt; requests in the list, one per hatched locust user.&lt;br&gt;
But what's that? The &lt;code&gt;DELETE&lt;/code&gt; requests fail with &lt;code&gt;HTTPError('401 Client Error: Unauthorized for url: http://localhost:8080/ocs/v2.php/cloud/users/locust0',)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Digging deeper (e.g. with WireShark) shows that the requests not only had the correct Authorization header sent, but also some cookies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DELETE /ocs/v2.php/cloud/users/locust0 HTTP/1.1
Host: localhost:8080
User-Agent: python-requests/2.22.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Cookie: cookie_test=test; oc1j5vb7hdm0=q2mv4lb5f2b37ti3etn8s1e0f1; oc_sessionPassphrase=y2u2sfTFfk8xx4cyIQZbycNvit4q0ZcKr4nHiiA7vSrGN%2BOZI30Ruvf3B5NyZrAxwtDNGz1wI7F6Yb2gjGsn%2FCnZ8Xpw3U8qRur1NrNcpJv%2Bm9egvmUiflwp3j7Rd3IG
Content-Length: 0
Authorization: Basic YWRtaW46YWRtaW4=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;locust got those cookies from the first &lt;code&gt;GET&lt;/code&gt; request we have sent as the specific ownCloud user, and it has kept them for all future requests. Generally that is a good thing, but ownCloud now ignores the Authorization header and uses the cookies to authenticate. So we effectively authenticate as the specific ownCloud user e.g. &lt;code&gt;locust0&lt;/code&gt; and that user has no privilege to delete itself.&lt;/p&gt;

&lt;p&gt;I could not find a way to clear the session, so we need a new one.&lt;br&gt;
For that change the &lt;code&gt;on_stop&lt;/code&gt; function to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def on_stop(self):
    from locust.clients import HttpSession
    self.admin_client = HttpSession(base_url=self.client.base_url)
    self.admin_client.delete(
        "/ocs/v2.php/cloud/users/" + self.userName,
        auth=(self.adminUserName, self.adminUserName)
    )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we import the locust &lt;code&gt;HttpSession&lt;/code&gt; class and use it to create a new session, with no cookies in our way.&lt;/p&gt;

&lt;p&gt;And here we go, when starting and stopping the tests we have successful &lt;code&gt;DELETE&lt;/code&gt; requests. One per hatched locust user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo97krfzm4ana8o0fqpb4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo97krfzm4ana8o0fqpb4.png" alt="successfull user deletion"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  what's next?
&lt;/h2&gt;

&lt;p&gt;We have now some basic tests, now it's time to look closer into the metrics and try to understand the meaning of all the numbers locust throws at us.&lt;/p&gt;

</description>
      <category>qa</category>
      <category>performance</category>
      <category>python</category>
      <category>testautomation</category>
    </item>
  </channel>
</rss>
