Pages

Thursday, August 18, 2016

Catching Up on Coding - TDD Part 1

I’ve been a software developer for twenty years, but am behind in some skills. This series chronicles my self-improvement.

There are some pretty high-tech tools out there, but nothing that matches the inspiration provided in the 70s TV show “Search” (originally “Probe”). I don’t have Burgess Meredith, a Probe Scanner or a catchy theme song to help me out, so, like Hugh O’Brian, I’ll have to rely on my wits and Test Driven Development (TDD).

Previously, on CUOC: The Syllabus.
Next Episode: TDD Part 2

 

Teaser

I’m not truly new to TDD. I wrote a (not very good) unit testing framework for Visual Basic 6 back in 2000. I’ve used TDD in professional projects. But now it’s time to make it a habit. To do that, I need a crash coarse, then retrofit my EduAgent code (applying “Test Eventually Development”) before writing new code.

Act 1 – The Elemental Education

Let’s give primary credit where it’s due. I read James Bender’s blog 30 Days of TDD. Bender’s series is very good overall. He does—frustratingly--have sloppy grammar and punctuation errors in every post. But his information is otherwise clear and solid. In fact, his description of SOLID is one of the best I’ve read. Thanks, James!

But first things first: what problem was TDD trying to solve?

Programmers, being human, don’t like to test our work. Most of us find it as hard as writers self-editing or composers checking for wrong notes. We like QA departments because they find our (careless) mistakes for us. We pretend to hate QA, because we pretend we’re egotistical Greek gods incapable of flaw.

Unit testing frameworks allowed testing the public interface of our classes. But that wasn’t enough. Kent Beck encouraged an upside-down approach to writing code, where we write a failing test first, then make it pass. This naturally builds up a library of tests that can be run quickly, reducing the chance of new features breaking the application, and building developer confidence. It also turned out that creating easily testable methods led to generally more maintainable code. Thus, some people revised TDD to mean test-driven design.

What are the elements of TDD? What’s needed to build testable code?

Elements

  • Test framework such as NUnit and xUnit.net
  • Test runner, for executing tests and viewing results.
  • Dependency Inversion (depend on abstractions) and Dependency Injection (decouple dependencies). Both are covered pretty well in this article.
  • Mocking (control results from external resources, such as databases)
  • Refactoring (improving code maintainability without changing the public interface)

Act 2 – Some Code, a Test

Here’s a simple example of TDD, creating code that needs to be tested, and problems with it. I’m using xUnit.Net because I’ve always liked it. I won’t go into how to set up your environment; you can learn that at Getting Started with xUnit.net. However, I do have three tips for using the VS Test Runner (which you open via Test > Windows > Test Explorer).

  • Tear off the Test Explorer into its own window, so that you can easily resize and see all the tests.
    image
  • Group the test results by class
    image
  • In your unit test project, add an app.config file with the following setting. This causes the result to display only the method name instead of the fully qualified name.
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <appSettings>
        <add key="xunit.methodDisplay" value="method"/>
      </appSettings>
    </configuration>
    

Our contrived application helps teachers grade coursework. We’re writing a class dealing with the coursework, and we’re consuming a third-party service—GradeMark—that returns a grade. One challenge, however, is we don’t control the grade that GradeMark returns given a set of scores. It depends on however the school has configured it. So, today 90% or higher could be an “A,” but tomorrow 93% or higher could be an “A.”

Our CourseWork class will have two methods. One that returns a percentage, and the other that returns a grade message. The grade in the message will come from the GradeMark “service,” and we’ll eventually have to mock that service.

First, in CourseWork, create a GetPercent function that doesn’t work. We want a failing test.

    public class CourseWork
    {
        public decimal GetPercent(decimal actual, decimal maximum)
        {
            throw new NotImplementedException();
        }
    }

In the unit tests project, create the test. Notice the naming convention I’m using along with nested classes; this will help make the test results readable. There are lots of naming conventions out there. Consistency and clarity are the big deals.

Our first test checks that we get back a percent in a particular format. We want “a hundred percent” to return as 100.00.

    public class BasicTdd
    {
        public class CourseWork_GetPercent_Should
        {
            [Fact]
            public void Return100PercentAs100PointZero()
            {
                var cw = new CourseWork();
                decimal percent = cw.GetPercent(100, 100);
                Assert.Equal(100m, percent);
            }
        }
    }

Run it and it fails.

image

Write enough code for the test to pass.

        public decimal GetPercent(decimal actual, decimal maximum)
        {
            decimal result = (actual / maximum) * 100m;
            return result;
        }

Run the test and it passes.

image

Our next test will check if the result is rounded. This does not depend on the first test; they could be run in reverse order.

    public class BasicTdd
    {
        public class CourseWork_GetPercent_Should
        {
            [Fact]
            public void RoundPercentToTwoDecimalPlaces()
            {
                var cw = new CourseWork();
                var percent = cw.GetPercent(1, 7);
                var roundedPercent = Math.Round(percent, 2);
                Assert.Equal(roundedPercent, percent);
            }
        }
    }

Run it, and it fails.

image

Update the code to make it pass.

        public decimal GetPercent(decimal actual, decimal maximum)
        {
            decimal result = Math.Round((actual / maximum) * 100m,2);
            return result;
        }

image

That’s enough to demonstrate a basic test. Now, on to something that will require mocking.

Act 3 – Mocu-mental Grades

Let’s create a class that represents our fictional third-party service, GradeMark. Remember, it returns a grade when supplied a percent, but we can’t depend on what that grade will be. For our dummy service, we won’t even use the percent parameter.

    public class GradeMarkService
    {
        public string GetGrade(decimal percent)
        {
            var rnd = new Random();
            int code = rnd.Next(65, 70);
            return ((char)code).ToString();
        }
    }

We’re just returning a random letter A-D (no failures in our courses!). Back in our CourseWork class, we’ll start a function that returns a grade, and write a test for it that will fail. Here’s the test.

        public class CourseWork_GetGradeMessage_Should
        {
            [Fact]
            public void ReturnTheCorrectlyFormattedMessage()
            {
                var cw = new CourseWork();
                string msg = cw.GetGradeMessage(4, 15); //these values don't matter
                string expected = "The grade is B";
                Assert.Equal(expected, msg);
            }
        }

What we’re testing is getting the expected message text. We’re not testing if GetPercent works, and we’re not testing if the GradeMark service works. When we run the test, it fails, so we write this code to try to make it pass. Some people would say that we shouldn’t bother with the GradeMark service yet, but let’s assume we’re at that point.

        public string GetGradeMessage(decimal actual, decimal maximum)
        {
            var service = new GradeMarkService();
            return "The grade is " + service.GetGrade(GetPercent(actual, maximum));
        }

We got lucky; it failed. But, remember, we can’t guarantee what letter grade will be returned. It could have passed. Run the tests enough times and it will pass.

image

Our GetGradeMessage function has a dependency on the GradeMark service, and is tightly coupled to it. We need to loosely couple that dependency by injecting it. We could do that at the method level, like this:

        public string GetGradeMessage(decimal actual, decimal maximum, GradeMarkService gradeService)
        {
            return "The grade is " + gradeService.GetGrade(GetPercent(actual, maximum));
        }

But, typically, a service like this is consumed by the class. So, we’ll inject it in the class’s constructor.

    public class CourseWork
    {
        GradeMarkService _gradeService;

        public CourseWork(GradeMarkService gradeService)
        {
            _gradeService = gradeService;
        }

        public string GetGradeMessage(decimal actual, decimal maximum)
        {
            return "The grade is " + _gradeService.GetGrade(GetPercent(actual, maximum));
        }

Great, but that doesn’t really help us, does it? We still need a GradeMarkService to pass in during our test, and control the letter grade it returns. Using some mocking frameworks, it’s possible to mock a legacy object. But in this case, we’re hoping for one of two things from our vendor to make our lives easier.

An Interface

Ideally, our vendor has programmed to an interface, using the Dependency Inversion Principle. We’ll simulate that in our fictional service class.

    public interface IGrader
    {
        string GetGrade(decimal percent);
    }

    public class GradeMarkService: IGrader
    {
        public string GetGrade(decimal percent)
        {
            var rnd = new Random();
            int code = rnd.Next(65, 70);
            return ((char)code).ToString();
        }
    }

Next, we update our class to inject the interface we’re testing, instead of the concrete instance.

public class CourseWork
{ IGrader _gradeService; public CourseWork(IGrader gradeService) { _gradeService = gradeService; }

Our class is now ready for testing. We don’t need the GradeMarkService. We just need our own mock class implementing the required interface. Back in our test project, we’ll create that.

    public class GradeMarkServiceMock: IGrader
    {
        public string GetGrade(decimal percent)
        {
            return "B";
        }
    }

And, update our tests to accept the mock object.

            [Fact]
            public void Return100PercentAs100PointZero()
            {
                var cw = new CourseWork(new GradeMarkServiceMock());
                decimal percent = cw.GetPercent(1, 1);
                Assert.Equal(100m, percent);
            }
            [Fact]
            public void RoundPercentToTwoDecimalPlaces()
            {
                var cw = new CourseWork(new GradeMarkServiceMock());
                var percent = cw.GetPercent(1, 7);
                var roundedPercent = Math.Round(percent, 2);
                Assert.Equal(roundedPercent, percent);
            }
        }

        public class CourseWork_GetGradeMessage_Should
        {
            [Fact]
            public void ReturnTheCorrectlyFormattedMessage()
            {
                var cw = new CourseWork(new GradeMarkServiceMock());
                string msg = cw.GetGradeMessage(4, 15); //these values don't matter
                string expected = "The grade is B";
                Assert.Equal(expected, msg);
            }
        }

When we run our tests a few times, the GetGradeMessage test passes consistently.

An overridable class

Our vendor could also have made the GetGrade method overridable. In that case, we’d create a mock object, override GetGrade, and always return “B”. But with a significant object, there could be many problems doing that. It’s best if we can program against interfaces, because that way  we don’t have to use the original classes at all.

What’s a real-world example? What about a shipping service that our application uses to calculate shipping rates? Several problems could exist in consuming that service instead of mocking it:

  • It could be very slow to use that service in our tests.
  • It could return inconsistent results that affect our tests.
  • We might get charged every time we use the service.

Databases are another example. Testing against a database is difficult because you want the data to always start in a consistent state. Database tests are slow. They are better left for integration tests.

Refactoring

The last step in TDD is making the code better, and also making the tests better. For example, we get an instance of the CourseWork class repeatedly. We can declare that as a class field. xUnit creates a new instance of the class for each test, so it will will always be a new instance of CourseWork.

    public class BasicTdd
    {
        public class CourseWork_GetPercent_Should
        {
            public CourseWork _cw = new CourseWork(new GradeMarkServiceMock());

            [Fact]
            public void Return100PercentAs100PointZero()
            {
                decimal percent = _cw.GetPercent(1, 1);
                Assert.Equal(100m, percent);
            }
            [Fact]
            public void RoundPercentToTwoDecimalPlaces()
            {
                var percent = _cw.GetPercent(1, 7);
                var roundedPercent = Math.Round(percent, 2);
                Assert.Equal(roundedPercent, percent);
            }
        }

        public class CourseWork_GetGradeMessage_Should
        {
            public CourseWork _cw = new CourseWork(new GradeMarkServiceMock());

            [Fact]
            public void ReturnTheCorrectlyFormattedMessage()
            {
                string msg = _cw.GetGradeMessage(4, 15); //these values don't matter
                string expected = "The grade is B";
                Assert.Equal(expected, msg);
            }
        }
    }

Of course, rerun the tests and make sure they pass!

Tag

Test-driven development isn’t natural at first. But it’s got a rat-hits-food-lever pleasure I can’t deny. Kind of like having Probe Control’s Angel Tompkins whispering in my ear.

References

30 Days of TDD

Getting Started with xUnit.net

xUnit: display only method name

xUnit: Shared Context Between Tests

7 Popular Unit Test Naming Conventions

Naming standards for unit tests

An Absolute Beginner's Tutorial on Dependency Inversion Principle

No comments:

Post a Comment