Blog

  • Home /
  • Blog /
  • Prevent Domain Knowledge From Sneaking Into Solitary Tests

Prevent Domain Knowledge From Sneaking Into Solitary Tests

September 30, 2020

Previously we discussed why solitary tests should be easy to read. Sometimes, the readability of solitary tests is affected by those developers who overcomplicate or overengineer things. Well intentioned no doubt, but in the end quite harmful nonetheless. Complex solitary tests can cause some serious headaches for other members of the team.

One example of this is an issue that I see popping up from time to time. It’s the case where domain logic sneaks into the implementation of solitary tests. This seems to occur most often in solitary tests that exercise algorithms or business logic in domain objects.

Let’s have a look at an example to see this in action.

public class SolarPanelInstallation
{
    public IEnumerable<SolarPanel> SolarPanels { get; }

    public SolarPanelInstallation(IEnumerable<SolarPanel> solarPanels)
    {
        SolarPanels = solarPanels;
    }

    public Watts CalculateTheoreticalCapacity()
    {
        return SolarPanels.Aggregate(Watts.Of(0), (accumulator, solarPanel) 
            => accumulator + solarPanel.Capacity);
    }
}

public class SolarPanel
{
    public Watts Capacity { get; }

    public SolarPanel(Watts capacity)
    {
        Capacity = capacity;
    }
}

public readonly struct Watts
{
    public int Value { get; }

    private Watts(int value)
    {
        Value = value;
    }

    public static Watts Of(int value)
    {
        return new Watts(value);
    }

    public static Watts operator +(Watts a, Watts b)
    {
        return Of(a.Value + b.Value);
    }
    
    public override string ToString()
    {
        return $"{Value} Watts";
    }
}

Here we’ve entered the realm of generating green energy using solar panels. A solar panel installation can be comprised of one or multiple solar panels. Each solar panel has its own capacity which is expressed in watts. The SolarPanelInstallation class provides a method that calculates the theoretical capacity of the entire installation.

Let’s have a look at the test code.

[Specification]
public class When_calculating_the_theoretical_capacity_of_a_solar_panels_installation
{
    [Establish]
    public void Context()
    {
        var solarPanels = new[]
        {
            new SolarPanel(Watts.Of(368)), 
            new SolarPanel(Watts.Of(368)), 
            new SolarPanel(Watts.Of(278)) 
        };
        
        _sut = new SolarPanelInstallation(solarPanels);
    }
    
    [Because]
    public void Of()
    {
        _theoreticalCapacity = _sut.CalculateTheoreticalCapacity();
    }    
    
    [Observation]
    public void Then_it_should_yield_the_total_capacity_of_all_solar_panels_of_the_installation()
    {
        var expectedCapacity = _sut.SolarPanels
            .Select(solarPanel => solarPanel.Capacity.Value)
            .Sum();
        
        _theoreticalCapacity.Should_be_equal_to(Watts.Of(expectedCapacity));
    }

    private SolarPanelInstallation _sut;
    private Watts _theoreticalCapacity;
}

Notice how the value of the expectedCapacity variable is being calculated. This is very similar to the calculation in the CalculateTheoreticalCapacity method of the SolarPanelInstallation class. Although the way their respective implementation calculates the capacity is slightly different, we can conclude that in this example the test code contains the same knowledge as the production code. Sometimes, I even encounter tests where the developer just “borrowed” from the production code directly.

This is also a nice example where state verification tests are too tightly coupled to the production code. So when the implementation of the CalculateTheoreticalCapacity method is refactored, chances are quite high that the test code needs to be modified as well.

It’s also more difficult to read this test and figure out what the expected value should be. For an easy example like this it doesn’t require that much additional brain cycles. However, with more complex algorithms or business logic, developers often execute the test in debug mode just to figure out what the expected value should be. How’s that for readability?

Let’s have a look at an improved version of this test.

[Specification]
public class When_calculating_the_theoretical_capacity_of_a_solar_panels_installation
{
    [Establish]
    public void Context()
    {
        var solarPanels = new[]
        {
            new SolarPanel(Watts.Of(368)), 
            new SolarPanel(Watts.Of(368)), 
            new SolarPanel(Watts.Of(278)) 
        };
        
        _sut = new SolarPanelInstallation(solarPanels);
    }
    
    [Because]
    public void Of()
    {
        _theoreticalCapacity = _sut.CalculateTheoreticalCapacity();
    }    
    
    [Observation]
    public void Then_it_should_yield_the_total_capacity_of_all_solar_panels_of_the_installation()
    {
        _theoreticalCapacity.Should_be_equal_to(Watts.Of(1014));
    }

    private SolarPanelInstallation _sut;
    private Watts _theoreticalCapacity;
}

Here we just provided the value that we expect to be the result of the calculation. That’s it! No more duplicate domain knowledge, no more tight coupling of the test and no more debugging. The expected value is just right there. Simplicity can be a beautiful thing.

Domain knowledge that sneaks into your tests is something to be avoided. Be very mindful about this. Don’t use the Subject Under Test itself for determining the outcome of a test.

If you and your team want to learn more about how to write maintainable unit tests and get the most out of TDD practices, make sure to have look at our trainings and workshops or checkout the books section. Feel free to reach out at infonull@nullprincipal-itnull.be.

Profile picture of Jan Van Ryswyck

Jan Van Ryswyck

Thank you for visiting my blog. I’m a professional software developer since Y2K. A blogger since Y2K+5. Provider of training and coaching in XP practices. Curator of the Awesome Talks list. Past organizer of the European Virtual ALT.NET meetings. Thinking and learning about all kinds of technologies since forever.

Comments

About

Thank you for visiting my website. I’m a professional software developer since Y2K. A blogger since Y2K+5. Author of written words. Provider of training and coaching in XP practices. Curator of the Awesome Talks list. Past organizer of the European Virtual ALT.NET meetings. Thinking and learning about all kinds of technologies since forever.

Contact information

(+32) 496 38 00 82

infonull@nullprincipal-itnull.be