BDD with Cucumber, and some word-play

During a recent technical interview, I was asked to write:

  1. a snippet of code to test whether a string was a palindrome
  2. the type of tests/test cases/test technology that I would use to confirm correct behaviour of my code

The interviewer had a German accent. Before I could rein myself in, I found I had already written “tot”, “Ebbe”, and “Otto” on the whiteboard (all exceptionally simple words as evidenced by their less than modest Scrabble scores of and 3, 8, 4 respectively; my German is still very elementary). Somewhat amused and given the opportunity to venture off a script that was unquestionably going to be just another rote question technical interview, to my surprise, and the surprise of the two other people in the room, he then browsed some website on his iPhone prior to writing this up on the whiteboard.

Eine güldne, gute Tugend: Lüge nie!

Argh! Suddenly I realised I was about to be exposed as a lightweight. Why hadn’t I just stuck to my mother tongue [Muttersprache (f), in case you’re interested, and I am] and used simple English words or phrases such as:

Hannah

Madam

Was it a car or a cat I saw

Now, I realise I must contend with stripping out punctuation, and accommodating for diacritics, and goodness knows what other surprises we are waiting for me during this interview. Get me out of here!

To soldier on, the C# for this type of test is not too difficult. My first stab at writing a concise application that demonstrates some minimal palindrome/palindromic phrase test logic is shown below:

using System;
using System.Linq;
namespace ConsoleApplication2287
{
   public class Palindrome
   {
      public static void Main()
      {
         Func<String, String> alphanumeric = (s) => new String(s.Where((c, b) => Char.IsLetterOrDigit(c)).ToArray());
         foreach (var s in new [] { "Hannah",
                                      "Madam",
                                      "Was it a car or a cat I saw",
                                      "Eine güldne, gute Tugend: Lüge nie!",
                                      "NOT A PALINDROME"
                                    }
                 )
                 {
                    Boolean isPalindrome = alphanumeric(s).Equals(alphanumeric(new string(alphanumeric(s).Reverse().ToArray())), StringComparison.OrdinalIgnoreCase);
                    Console.WriteLine($"'{s}' is a palindrome: {isPalindrome}");
                 }
     }
  }
}

How should this code be tested? Well as it is written, clearly, the test is by command-line execution, but if the logic were to be encapsulated within a usable class with public methods and properties etc, well during the interview I could have made a few throw away comments about  NUnit or MSTest (and there are many others each with their niche benefit or targeting a specific technology, but this subject is just too tedious for me).

During a recent project, I was exposed to SpecFlow Cucumber that can, depending on your perspective, be used to test your implemented code behaviour. More importantly, in my view, is that the technology enables the (business) requirements to be defined “by example”, even collaboratively, in something close to a natural language. This just has to be the right way forward.

Here is “code” not too dissimilar to what I wrote during the technical interview.

# language: en
Feature: Palindrome

Scenario Outline: Confirm palindrome code behaviour
Given the word being tested as a palindrome is "<Palindrome or palindrome phrase>"
Then then the palindrome test should be "<IsPalindrome>"

Examples: English
| Palindrome or palindrome phrase     | IsPalindrome |
| Hanna                               | Yes          |
| Was that a car or a cat I saw       | Yes          |
| Madam                               | Yes          |

Examples: Deutsche
| Palindrome or palindrome phrase     | IsPalindrome |
| Eine güldne, gute Tugend: Lüge nie! | Yes          |
| tot                                 | Yes          |
| Ebbe                                | Yes          |

Examples: Should fail
| Palindrome or palindrome phrase     | IsPalindrome |
| NOT A PALINDROME                    | No           |

Isn’t this “code” so clear?

Will Business Analysts understand it?

Will the end-users feeding in the requirements understand it?

Will the developer understand it?

If the palidrome examples are deficient in terms of test cases, can you easily add more?

Will the automated tests understand the code too, and fail or pass accordingly?

The answer to these questions is yes, yes, yes, yes, yes, and yes.

At some point, it will be necessary to “wire up” this code, with a bit of C# to invoke my palindrome tests, and pass/fail the test according to the example data. How is this done? In Visual Studio of course. The SpecFlow Visual Studio plugin makes this easy too as shown in the screenshot below.

The autogenerated C# code that results from generating the step definitions is shown below:

using System;
using TechTalk.SpecFlow;
namespace ApplicationTests
{
  [Binding]
  public class PalindromeSteps
  {
     [Given(@"the word being tested as a palindrome is ""(.*)""")]
     public void GivenTheWordBeingTestedAsAPalindromeIs(string p0)
     {
         ScenarioContext.Current.Pending();
     }

     [Then(@"then the palindrome test should be ""(.*)""")]
     public void ThenThenThePalindromeTestShouldBe(string p0)
     {
         ScenarioContext.Current.Pending();
     }
  }
}

All I have to do now is plumb in a call to my Palindrome class, assert (as in NUnit.Framework.Assert perhaps?) whether the test should pass or fail, and link up into my automated test suite. Will my tests pass with the example C# I wrote above where the key method is Char.IsLetterOrDigit(..), especially given the diacritics on three letters in German (ä, ö, ü), and considering the eszett (ß) too? The answer I gave during my technical interview can be summed up as I am not sure, but the point is that the test data and language can be easily changed, and not as scope or requirements creep, and the code refactored if/as necessary. Furthermore this can all be done quite quickly. Put another way, the “test” code and the discussion was actually defining the requirements, outside those initially specified, and before (in ideal situations) I had even written my first line of C#.

Sometimes, in German where you don’t have a German keyboard at hand, it is common practice to substitute two letters for one, eg. Müller is written Mueller, or Düsseldorf is written Duesseldorf. If this substituted word or phrase when reversed reads the same, then it would be considered a palindrome by my ‘first stab’ C# palindrome code. Put another way, there’s a potential bug here. I cannot think of a word like this in German, and I have also asked three native German speakers if they can think of an example and they too cannot. During the technical interview, this was also discussed, and we couldn’t think of an example then either, but again the collaborative nature of the process meant the details are defined clearly in a native common language that all parties, including the compiler, understand. To be clear, this requirements discussion and clarification is a good thing!

What more do you want from SpecFlow? How could you improve this technology? I am so satisfied with the technology as-is that I do not know the answer to these questions.

ps: Some weeks back I published a blog article using different word play, where I wrote Put another way, if it’s 1:45 in London, in Auckland and Wellington it’s a quarter to two too“. Alas, in German, the translation loses the word play with “Anders gesagt, wenn es 1:45 in London ist, ist es auch Viertel vor 2 in Auckland und in Wellington“. In the case of palindromes though, German with its diacritics and complexities I hadn’t initially thought of wins hands down! Furthermore as discussed in the prior paragraph, even now I still don’t know whether my ‘first stab’ palindrome C# code is bugged, and if it is, how I would go about fixing it.

— Published by Mike, 10:46 13 March 2017

Leave a Reply