Practical Unit Tests for Games

A little while ago I wrote an answer to a unit testing related question here on reddit which seemed to resonate with many. One of the points I made there was that testing, more than anything else, improves code design. Now it’s all good and well stating theories like that but an example goes a long way to show how this process might unfold. Here is a situation I ran into a couple of days ago which I think is worth sharing.

The Scenario

In Startup Freak I have a happiness system, whereby an employee’s happiness is affected by several different factors. Currently these are:

  • Interaction with other employees
  • Salary
  • Office amenities
  • Random events

My initial implementation looked something like this (pseudo code here):

public class HappinessCalculator
    public HappinessCalculator(Company company, IJobMarket jobMarket, ...)
    { ... }

    public UpdateHappiness(Employee employee)
        var multiplier = 1.0
            + GetInteractionFactor(employee)
            + GetSalaryFactor(employee)
            + GetOfficeFactor(employee)
            + GetEventFactor(employee);

        employee.SetHappiness(employee.Happiness * multiplier);

    double GetInteractionFactor(Employee employee) { ... }
    double GetSalaryFactor(Employee employee) { ... }
    double GetOfficeFactor(Employee employee) { ... }
    double GetEventFactor(Employee employee) { ... }

Now at this point I’m thinking this design is pretty good. I have extracted the logic of calculating happiness into its own class rather than polluting the Employee class. There is only a single public method which makes it clean and easy to use. Also the HappinessCalculatorhas all its required dependencies (things it needs to calculate happiness) injected into it via the constructor. Great.

But then I faced a problem.

The Problem

While I was playing the game I noticed the happiness of the employees wasn’t really behaving as I wanted it to. They seemed to always increase in happiness, but then randomly have a sharp decline. I decided instead of poking randomly at my equations, it was worthwhile writing some tests to make sure the various components of happiness work correctly.

You can probably already guess the problem here. It’s really hard to test the HappinessCalculatorclass. The UpdateHappiness()method combines the results of several private methods which makes it very difficult to isolate each factor separately. Even if I did manage to write tests that confirm the effect of each component (by stubbing the dependencies injected into the constructor), these tests would be very brittle and depend too much on the implementation details of this class.

Now here is what I’m talking about when I say tests improve code design. In order to make this class testable, I refactored it into a shape that I think is much cleaner, understandable, and extendable, regardless of whether or not I keep the tests at all.

The Refactor

Clearly I needed to test each happiness factor separately. I extracted an interface called IHappinessFactorCalculatorwith a single GetFactor()method and split up the implementations. Here is an example:

public class SalaryHappinessCalculator : IHappinessFactorCalculator
    readonly ICalendar _calendar;
    readonly IJobMarket _jobMarket;

    public SalaryHappinessCalculator(ICalendar calendar, IJobMarket jobMarket)
        _calendar = calendar;
        _jobMarket = jobMarket;

    public override double GetFactor(Employee employee)
    { ... }

Similarly I wrote InteractionsHappinessCalculatorOfficeHappinessCalculator, and EventsHappinessCalculator.  Note that each of these has its own dependencies injected. What’s really neat is that I can now scan the assembly for all implementations of IHappinessFactorCalculatorand register them with my DI Container, meaning that if I add a new implementation of the interface it will automatically get picked up. Finally the top level HappinessCalculatorclass simply aggregates the results from all factor calculators:

public class HappinessCalculator
    readonly IEnumerable<IHappinessFactorCalculator> _happinessFactorCalculators;

    public HappinessCalculator(IEnumerable<IHappinessFactorCalculator> happinessFactorCalculators)
        _happinessFactorCalculators = happinessFactorCalculators;

    public void UpdateHappiness(Employee employee)
        var multiplier = 1.0;
        foreach (var calculator in _happinessFactorCalculators)
            multiplier += calculator.GetFactor(employee);

        employee.SetHappiness(employee.Happiness * multiplier);

Writing the tests is now very simple. I wrote a few tests for each factor, and also wrote a test that ensure that the super class aggregates the results correctly.

The Tests

As an example, here is what one of my tests for SalaryHappinessCalculatorlooks like. Note that I’m using NUnit for testing, and NSubstitute for mocking/stubbing dependencies. I’m also using an Object Mother pattern to get some pre-made test data.

public class SalaryHappinessCalculatorTests
    IJobMarket _jobMarket;
    ICalendar _calendar;
    SalaryHappinessCalculator _calculator;

    public void Setup()
        _jobMarket = Substitute.For<IJobMarket>();
        _calendar = Substitute.For<ICalendar>();
        _calculator = new SalaryHappinessCalculator(_calendar, _jobMarket);

    public void GivenAnEmployee_WhenTheirSalaryIsLowerThanMarketRate_ThenANegativeHappinessFactorIsReturned()
        var employee = ObjectMother.Employees.DeveloperDebra;
        _jobMarket.GetExpectedSalary(employee.PrimarySkill, employee.SecondarySkill, 0).Returns(employee.Salary + 10000);

        var factor = _calculator.GetFactor(employee);

        Assert.That(factor, Is.LessThan(0));

Here, I’m mocking the IJobMarket service to say that the expected salary for Debra should be $10,000 higher than what it currently is. I then check that the calculator returns a negative happiness factor in this situation.

Anyway I hope that’s helpful and gives you an idea of why and where a test might be useful in a game, and how it can inform code design decisions.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s