Solving the diamond kata with property-based testing series

  1. How to get started with Property-based Testing in C#
  2. Input generators in property-based tests with FsCheck
  3. First and Last line content
  4. Height equals Width
  5. Outside space symmetry
  6. Symmetry around the vertical axis
  7. Symmetry around the horizontal axis
  8. No padding for input letter row
  9. Letters order

All code samples are available on github


Intro

Lately, we talked a lot about the symmetry properties of our diamond. Today, I want to go somewhere else entirely. We are going to inspect a little more the behavior around the input letter.

No padding for input letter row

In the diamond kata, for any given letter in input, the row containing that letter will not have any padding spaces.

e.g.
Diamond-kata-Input-Row-contains-No-Spaces

C# Tests

[Property(Arbitrary = new[] {typeof(LetterGenerator)})]
public Property InputLetterRowContainsNoOutsidePaddingSpaces(char c)
{
   var inputLetterRow = Diamond.Generate(c).ToArray()
       .First(x => GetCharInRow(x) == c);
   return (inputLetterRow[0] != ' ' && inputLetterRow[^1] != ' ').ToProperty();
}

private static char GetCharInRow(string row)
{
    return row.First(x => x != ' ');
}

Here we used some built-in methods of the .NET library, which makes these tests simple to read and short to write. Note the use of the new C# 8.0 index operators ^1 to select the last element of the array.

  1. Diamond.Generate(c).ToArray() generates the output diamond and transform it into an array of rows
  2. .First(x => GetCharInRow(x) == c) selects the row containing the input letter
  3. GetCharInRow is a simple helper function that returns the first char of a row
  4. inputLetterRow[0] != ' ' && inputLetterRow[^1] != ' ' selects the first and last char of the row and make sure they are not spaces
  5. .ToProperty() transforms the boolean expression into a property

If you are wondering what's [Property(Arbitrary = new[] { typeof(LetterGenerator) })] it's probably because you missed my previous post

Test run

If you run the test suite, you'll have a surprise.

Diamond-kata-failing-test

That new test fails! We got used to our dummy implementation that was passing all of our property-based tests so far. That had to happen sooner or later as we know that implementation was just a starting point. It's an excellent opportunity to refactor and re-run our test suite to validate the new implementation.

Refractor

Ok, so what's the most straightforward improvement we can make to pass all the tests?

private static IEnumerable<string> Generate(char c)
{
    yield return " A ";
    yield return $"{c} {c}";
    yield return " A ";
}

troll

It works! ...Almost. Here's the real power of property-based tests. As we are testing with random inputs, FSCheck will help us find edge cases that we may have overlooked. In this case, it found an issue when the input letter is 'A'.

Diamond-kata-failing-edge-case

Let's refine our refactor then.

private static IEnumerable<string> Generate(char c)
{
    if (c == 'A')
    {
        yield return "A";
    }
    else
    {
        yield return " A ";
        yield return $"{c} {c}";
        yield return " A ";
    }
}

Run the test suite again

Diamond-kata-success

Rock-on


Solving the diamond kata with property-based testing series

  1. How to get started with Property-based Testing in C#
  2. Input generators in property-based tests with FsCheck
  3. First and Last line content
  4. Height equals Width
  5. Outside space symmetry
  6. Symmetry around the vertical axis
  7. Symmetry around the horizontal axis
  8. No padding for input letter row
  9. Letters order

All code samples are available on github