Creating unit tests with rich input and output

Any time that I jump on a new project one of my first stops is to look at the DevOps process. I’m a firm believer that the ability to release code rapidly and reliably is foundational to developing a good product. Even years ago, when I first got out of school I had the faint inkling that people were willing to accept bugs in software so long as they were fixed quickly.

Wouldn’t it be nice, though, if we could have both quality and an ability to rapidly react to changing requirements? Well just like with the Slap Chop you can have it all!

Image result for slap chop
You can have it all

The Slap Chop of the development world is testing. Tests ensure that code changes don’t break seemingly unrelated code and that the code performs as it should under a variety of conditions.

A Million Different Ways to Test

There are many different testing techniques. Some test the very small: unit tests. Others test the very large: end to end acceptance tests. Yet others test things repeatedly and rapidly to the point of failure: load testing.

An application may make use of all of these to ensure that the product delivered to the end-user does what it is supposed to. By integrating these tests into the DevOps pipeline we can get quality and velocity.

Figuring out the right mixture of test types can be difficult but one of the more popular approaches is to follow a testing pyramid. More unit tests at the base and then smaller and smaller numbers of more involved tests.

Image result for testing pyramid
Testing pyramid showing more unit tests than end to end tests

Unit tests are the foundational element in the testing pyramid. In theory, codebases should have more of those than any other kind. Fortunately unit tests area also the fastest to run. Because of their speed, we often want to move more computationally intensive tests from higher in the stack down by converting them to unit tests.

The ability to do complex data manipulation on large object graphs is one of the best features of developing with LINQ. It can, however, be a pain to set up the object models to do these tests.

Large Object Graphs

Let’s imagine that we have a data model which looks like this:

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string MiddleName { get; set; }
        public List<Education> Education { get; set; }
        public Address Address { get; set; }
    }

    public class Education
    {

        public string SchoolName { get; set; }
        public string DegreeName { get; set; }
        public int GPA { get; set; }
    }

    public class Address
    {
        public string Number { get; set; }
        public string Street1 { get; set; }
        public string Street2 { get; set; }
        public string City { get; set; }
        public string County { get; set; }
        public string Country { get; set; }
        public string PostalCode { get; set; }
    }

On this object graph, we have some complex manipulation code like

decimal ManipulatePerson(List<Person> people)
{
  var heightTotal = people.Where(x => (x.HeightInCm > 200 &amp;&amp; 
                                       x.FirstName.Length < 5)
                                 || x.Education.Count(y => y.GPA == 4) == 2)
                           .OrderByDescending(x => x.Address.Country)
                           .Take(3).Sum(x => x.HeightInCm);
  return heightTotal;
 }

Obviously setting up a test case for this code would be a real pain. You’d have to build out multiple entries in the graph each one with specific properties. Then you’d need to do it multiple times to make sure it works with various values.

OzCode Export

Chances are you have data in a database somewhere that already satisfies the conditions in this test case. One of the many features of OzCode is the ability to export an object from the debugger into a variety of formats.

Using the export command from the debugger

Inside of the export window, you can change the depth of what is exported.

Increasing the depth of the export

Now you can take the generated structure and plug it directly into your tests. This allows you to turn what might have been a more integration style test into a unit test.

Approval Tests

On the flip side of the testing coin is that it can be hard to verify that a complex piece of collection manipulation is doing the right thing. Certainly testing that fields you expect to change have changed is easy but when presented with a complex object it may be difficult to confirm that there weren’t unexpected side effects.

Let’s look at an example. Here is a method which increases the GPA of a list of people

public void IncreaseGPA(List<Person> people, decimal toIncreaseBy)
{
    foreach (var person in people)
        foreach (var education in person.Education)
            education.GPA += toIncreaseBy;
}

We can build some testing code for this like so (note how we used a model exported from OzCode)

[Fact]
void Increaser_should_increase()
{
    //arrange
    var people = new List<Person>();
    people.Add(new Person
    {
        FirstName = "Melissa",
        LastName = "Pack",
        MiddleName = "Wazoobie",
        Education = new List<Education>{
            new Education{
                DegreeName = "Welding",
                SchoolName = "Bob's Big School of Welding",
                GPA = 4.0M
            },
            new Education
            {
                DegreeName = "Fine Arts",
                SchoolName = "Juilliard",
                GPA = 3.7M
            }
        },
        Address = new Address
        {
            Number ="12b",
            City = "Denver",
            Street1 = "Main St.",
            Country = "USA"
        }
    });
    var increaser = new GPAIncreaser();
    //act
    increaser.IncreaseGPA(people, 1);
    //assert
    people.First().Education[0].GPA.Should().Be(5.0M);
    people.First().Education[1].GPA.Should().Be(4.7M);
}

That test passes nicely! We can be confident that our code is working, right?

Well what if we change the method to be

public void IncreaseGPA(List<Person> people, decimal toIncreaseBy)
{
    foreach (var person in people)
    {
        person.FirstName = "Big Nose";
        foreach (var education in person.Education)
            education.GPA += toIncreaseBy;
    }
}

The tests still pass but you’re soon going to be getting a call from a lot of people who are upset that their name has been changed to “Big Nose”. Instead, we can make use of ApprovalTests to store a known good state for our test. This can be done by adding

Approvals.Verify(JsonConvert.SerializeObject(people,Formatting.Indented));

to our assertions. (You’ll also need the namespaces ApprovalTests and ApprovalTests.Reporters) The first time through this will write out a file, UnitTest1.Increaser_should_increase.approved.txt, to our project directory which we can then source control. This will be checked now on every test run.

If we go make the Big Nose change again the test will now fail because the output doesn’t match the expected. This is a very useful approach for ensuring we don’t introduce unintended side effects.

Wrapping Up

The combination of saving off complex models from either a database or some other source and using approval tests give a powerful way to test complex objects. Exporting objects from OzCode is just one of the many features worth checking out at http://oz-code.com/features