Testing C# solutions

We use NUnit 3 as a testing framework for C# coding exercises.

Hello, World!

Before we go into details, let's see how you can use NUnit to test the most basic C# task: writing a function that returns "Hello, World!".

This is what a student might write:

public class Hello
{
    public static string HelloMessage()
    {
        return "Hello, World!";
    }
}

And this is an example of what you could write to evaluate the student's code:

using Coding.Exercise;
using NUnit.Framework;

namespace Coding.Exercise.UnitTests
{
    [TestFixture]
    public class TestHello
    {
        [Test]
        public void TestHelloMessage()
        {
            Assert.That(Hello.HelloMessage(),
                        Is.EqualTo("Hello, World!"),
                        "Your function must return: Hello, World!");
        }
    }
}

Let's examine the above evaluation code.

First, we must include 2 namespaces:

  • Coding.Exercise, which is the student's code.
  • NUnit.Framework, which is the testing framework we use.

Although optional, it's a good practice to write your testing code inside a namespace, which you can name however you want. We named it Coding.Exercise.UnitTests here.

[TestFixture]

Then we need to define a suite of unit tests as a public class with the TestFixture attribute.

This groups together multiple unit tests and allows implementing common functionality that you want executed before and after each test.

[Test]

Inside a [TestFixture] class, you must define public methods with the Test attribute. These are the only methods that will be executed when running your evaluation code. You don't need to implement a Main() function.

Assert

Assertions are the statements that actually verify that the student's code behaves as expected. The most common pattern is to call student's methods and assert that their return value is the one that you expect. If it is not, then the test will fail and you have the opportunity to provide a custom message to the student for each failed assertion, so that she can improve.

NUnit 3 introduced a new assertion model and they encourage using only this one. It's called the Constraint Model. The most common way that you will use it is:

Assert.That(valueThatYouWantToVerify, constraint, feedbackMessage);

or the more generic

Assert.That(booleanCondition, feedbackMessage);

which allows you to pass in any boolean condition that you compute to test student code.

In our previous example we used the EqualConstraint, but you can find the full list of constraints here.

Standard output

In the previous example we had the students return Hello, World!, but actually a more common task is to have them print the same string to the standard output.

public class Hello
{
    public static void SayHello()
    {
        System.Console.WriteLine("Hello, World!");
    }
}

How do we test this specific function?

using System;
using System.IO;
using Coding.Exercise;
using NUnit.Framework;

namespace Coding.Exercise.UnitTests
{
    [TestFixture]
    public class TestHello
    {
        [Test]
        public void TestSayHello()
        {
            using (StringWriter sw = new StringWriter())
            {
                // Save a reference to the standard output.
                TextWriter stdout = Console.Out;

                // Redirect standard output to variable.
                Console.SetOut(sw);

                // Call student's code
                Hello.SayHello();

                // Restore the original standard output.
                Console.SetOut(stdout);

                // Assert
                Assert.That(sw.ToString(), Is.EqualTo("Hello, World!\n"));
            }
        }
    }
}

You can use the above pattern in your own evaluation code whenever you need to verify what students are printing to standard output. We are basically redirecting standard output to a variable that we can use to verify after running the student's code. Just don't forget to restore the standard output!

Technical specs

We use .NET Core on a Ubuntu machine to run the code.

Example 1: FizzBuzz

Given:

  • An integer number n

Your task is to:

  • Write a function that returns an array with the numbers from 1 to n with the following restrictions:
    • for multiples of 3 store "Fizz" instead of the number
    • for multiples of 5 store "Buzz" instead of the number
    • for numbers which are multiples of both 3 and 5 store "FizzBuzz"

Example:

fizzbuzz(15) == {
    "1", "2", "Fizz", "4", "Buzz",
    "Fizz", "7", "8", "Fizz", "Buzz",
    "11", "Fizz", "13", "14", "FizzBuzz"
}

Here's how a student might solve the problem:

using System;
using System.Linq;
using System.Collections.Generic;

namespace Coding.Exercise
{
    public class FizzBuzz
    {
        public static List<string> Compute(int n)
        {
            return Enumerable.Range(1, n)
                .Select(a => String.Format("{0}{1}",
                                           a % 3 == 0 ? "Fizz" : string.Empty,
                                           a % 5 == 0 ? "Buzz" : string.Empty))
                .Select((b, i) => String.IsNullOrEmpty(b) ? (i + 1).ToString() : b)
                .ToList();
        }
    }
}

Here's how you might test the student's solution:

using System.Collections.Generic;
using Coding.Exercise;
using NUnit.Framework;

namespace Coding.Exercise.UnitTests
{
    [TestFixture]
    public class TestFizzBuzz
    {
        [Test]
        public void ReturnOne()
        {
            Assert.That(FizzBuzz.Compute(1),
                        Is.EqualTo(new List<string> {"1"}));
        }

        [Test]
        public void ReturnFifteen()
        {
            int n = 15;
            List<string> result = FizzBuzz.Compute(n);
            List<string> expected = new List<string> {
                "1", "2", "Fizz", "4", "Buzz",
                "Fizz", "7", "8", "Fizz", "Buzz",
                "11", "Fizz", "13", "14", "FizzBuzz"
            };

            Assert.That(result.Count,
                        Is.EqualTo(expected.Count),
                        string.Format("Your function should return {0} objects for n = {1}", n, n));

            for (int i = 0; i < n; i++)
            {
                Assert.That(result[i],
                            Is.EqualTo(expected[i]),
                            string.Format("Your function should convert the value {0} to {1} instead of {2}",
                                          i + 1, expected[i], result[i]));
            }
        }
    }
}

Example 2: Title Case

Given:

  • A sentence with words separated by spaces

Your task is to:

  • Write a function that returns a copy of this sentence where all the words:
    • start with a capital letter
    • the rest of the letters are lower case

Example:

SentenceUtils.ToTitleCase("This is SO  MUCH  FUN! ") == "This Is So  Much  Fun! "

Here's how a student might solve the problem:

using System;
using System.Text;

namespace Coding.Exercise
{
    public class SentenceUtils
    {
        public static string ToTitleCase(string sentence)
        {
            if (sentence == null || sentence.Length == 0)
            {
                return sentence;
            }

            StringBuilder result = new StringBuilder(sentence);
            result[0] = char.ToUpper(result[0]);
            for (int i = 1; i < result.Length; i++)
            {
                result[i] = char.IsWhiteSpace(result[i - 1]) ? char.ToUpper(result[i]) : char.ToLower(result[i]);
            }

            return result.ToString();
        }
    }
}

Here's how you might test the student's solution:

using Coding.Exercise;
using NUnit.Framework;

namespace Coding.Exercise.UnitTests
{
    [TestFixture]
    public class TestSentenceUtils
    {
        [Test]
        public void Character()
        {
            Assert.That(SentenceUtils.ToTitleCase("a"),
                        Is.EqualTo("A"),
                        "Your function should convert a single character.");
            Assert.That(SentenceUtils.ToTitleCase("A"),
                        Is.EqualTo("A"),
                        "Your function should convert a single character.");
        }

        [Test]
        public void Word()
        {
            Assert.That(SentenceUtils.ToTitleCase("abc"),
                        Is.EqualTo("Abc"),
                        "Your function should convert a single word.");
            Assert.That(SentenceUtils.ToTitleCase("ABC"),
                        Is.EqualTo("Abc"),
                        "Your function should convert a single word.");
            Assert.That(SentenceUtils.ToTitleCase("aBC"),
                        Is.EqualTo("Abc"),
                        "Your function should convert a single word.");
            Assert.That(SentenceUtils.ToTitleCase("Abc"),
                        Is.EqualTo("Abc"),
                        "Your function should convert a single word.");
        }

        [Test]
        public void Sentence()
        {
            Assert.That(SentenceUtils.ToTitleCase("abc def ghi"),
                        Is.EqualTo("Abc Def Ghi"),
                        "Your function should convert a sentence.");
        }

        [Test]
        public void MultipleWhitespace()
        {
            Assert.That(SentenceUtils.ToTitleCase("  abc  def ghi "),
                        Is.EqualTo("  Abc  Def Ghi "),
                        "Your function should keep multiple whitespaces.");
        }
    }
}

results matching ""

    No results matching ""