Sylvain Rodrigue
Sylvain Rodrigue

Reputation: 4963

TDD : Any pattern for constant testing?

Constants are beautiful people - they can hold in a unique place a value that is used everywhere in your code. Changing that value requires only one simple modification.

Life is cool.

Well, this is the promise. Reality is sometime different :

Creating a constant is a contract. You are telling your readers that whenever they change the value, it will still works the way they think it should be.

Nothing less.

Of course, you need to test that you are not misleading your readers. You have to make sure that the implied contract is right.

How do you achieve that with TDD? I'm just stuck with that. The only way I can test a change for a constant (!) value is to make that constant an application setting... Should I have to conclude that the const keyword should be avoided when I think that the value can and will change?

How are you testing your (so called) constants using TDD?

Many thanks in advance :)

Upvotes: 13

Views: 4717

Answers (8)

Phil Parker
Phil Parker

Reputation: 1647

Some of the examples are configuration items rather than constants. In this case - even if you use constants in the short term they should be injected into where they are used and that code tested as normal. If the value can't be changed at runtime then you could also use an example test around the value as described below.

If you are testing something that is truly a constant (e.g const FEET_PER_METRE = 3.28084) then just have an example test that wraps both the calculation and the value (the data from this test should come from an independent source rather than just calculating it yourself from your declared constant).

Assert.AreEqual<decimal>(29.5276, converter.metresToFeet(9))

This way the test serves two purposes:

1) If it turns out your example and your constant were wrong then you can update your test with a correct example, get a failing test and then fix the constant to go green.

2) It provides regression safety. If someone was to accidentally change the constant (e.g. changed the wrong value or fat-fingered the value whilst editing something else in the file) then they would get a failing test to warn them.

Upvotes: 0

ShooShoSha
ShooShoSha

Reputation: 974

If you are making a Unit Test, I don't believe there is anything wrong with something like this:

[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod]
public void DefaultValue_Equals_8()
{
     Assert.AreEqual<int>(8, MyNamespace.MyClass.DefaultValue);
}

that tests against:

namespace MyNamespace
{
    public class MyClass
    {
        public const int DefaultValue = 8;
    }
}

This will detect if the constant was changed.

The other part of your problem is actually using the constant consistently. If you do not use it everywhere necessary in an expected manner, then you are not relying on constants rather you are relying on calculations (or magic numbers).

Upvotes: 0

Esko Luontola
Esko Luontola

Reputation: 73625

There are two kinds of constants:

1) Constants for Convenience/Readability

When writing code using TDD, each line of production code should exists because first there was a failing test that required that code to be written. And as you refactor the code, some magic values will get promoted into constants. Some of these might also be good as application settings, but for convenience (less code) they have been configured in code instead of an external configuration file.

In that case, the way that I write tests, both production and test code will use the same constants. The tests will specify that the constants are used as expected. But the tests won't repeat the value of a constant, such as in "assert MAX_ITEMS == 4", because that would be duplicated code. Instead, the tests will check that some behaviour uses the constants correctly.

For example, here is an example application (written by me) which will print a Longcat of specified length. As you can see, the Longcat is defined as a series of constants:

public class Longcat {

    // Source: http://encyclopediadramatica.com/Longcat

    public static final String HEAD_LINES = "" +
            "    /\\___/\\         \n" +
            "   /       \\         \n" +
            "  |  #    # |         \n" +
            "  \\     @   |        \n" +
            "   \\   _|_ /         \n" +
            "   /       \\______   \n" +
            "  / _______ ___   \\  \n" +
            "  |_____   \\   \\__/ \n" +
            "   |    \\__/         \n";
    public static final String BODY_LINE = "" +
            "   |       |          \n";
    public static final String FEET_LINES = "" +
            "   /        \\        \n" +
            "  /   ____   \\       \n" +
            "  |  /    \\  |       \n" +
            "  | |      | |        \n" +
            " /  |      |  \\      \n" +
            " \\__/      \\__/     \n";
...

The tests verify that the constants are used correctly, but they do not duplicate the value of the constant. If I change the value of a constant, all the tests will automatically use the new value. (And whether the Longcat ASCII art looks right, it needs to be verified manually. Although you could even automate that with an acceptance test, which would be recommendable for a larger project.)

    public void test__Longcat_with_body_size_2() {
        Longcat longcat = factory.createLongcat(2);
        assertEquals(Longcat.BODY_LINE + Longcat.BODY_LINE, longcat.getBody());
    }

    public void test__Fully_assembled_longcat() {
        Longcat longcat = factory.createLongcat(2);
        assertEquals(Longcat.HEAD_LINES + longcat.getBody() + Longcat.FEET_LINES, longcat.toString());
    }

2) Universal Constants

The same application has also some constants which are never expected to be changed, such as the ratio between meters and feet. Those values can/should be hard coded into the test:

    public void test__Identity_conversion() {
        int feet1 = 10000;
        int feet2 = FEET.from(feet1, FEET);
        assertEquals(feet1, feet2);
    }

    public void test__Convert_feet_to_meters() {
        int feet = 10000;
        int meters = METERS.from(feet, FEET);
        assertEquals(3048, meters);
    }

    public void test__Convert_meters_to_feet() {
        int meters = 3048;
        int feet = FEET.from(meters, METERS);
        assertEquals(10000, feet);
    }

And the production code looks like:

public enum LengthUnit {

    METERS("m", 1.0), FEET("ft", 0.3048), PETRONAS("petronas", 451.9), LINES("lines", 0.009);

    private final String name;
    private final double lengthInMeters;
...

    public int from(int length, LengthUnit unit) {
        return (int) (length * unit.lengthInMeters / this.lengthInMeters);
    }
}

Notice that I did not write any tests for the height of the Petronas Twin Towers, because that information is declarative (and declarative data rarely gets broken) and I had already written tests for the conversion logic (see above). If more similar constants are added (Eiffel Tower, Empire State Building etc.), they will be automatically located by the application and will work as expected.

Upvotes: 5

fortran
fortran

Reputation: 76057

That's because all those things aren't constants... Things that actually are:

  • G (6.67300 × 10-11 m3 kg-1 s-2)
  • c (299 792 458 m / s)
  • pi (3.1415926535897931)
  • L (6.0221415 × 1023 mol-1)
  • ...

If any of those things ever changes, don't worry, your application crashing will be the last that matters xD

Upvotes: 3

Johnno Nolan
Johnno Nolan

Reputation: 29659

A couple of things.

Firstly TDD and the emergent design that TDD promotes is the one of separating your responsibilities, applying DRY and dependency injection.

To unit test a const is easy and perhaps a little pointless.

But to test another unit's evalutation of that const is in my opinion not a unit test. It is an integration test. Testing the const value in other units would be coverd by a mock or stub.

Secondly your examples are varied:

You log example is only using 1 file. It is just that 2 exist. if the requirement was for only one file to exist then you could manufacture a test for that.

Transaction timeout testing should be picked up by an integration test. It should show your original test is not where the problem is.

Change of CompanyName is fine because it relates to the company name. Domain name should and could be a different const.

As others have mentioned passing a configuration class around my be helpful to mock/stub when testing other classes.

Upvotes: 1

Jamie Ide
Jamie Ide

Reputation: 49261

The only way I can test a change for a constant (!) value is to make that constant an application setting

All of the uses you listed in the question sound like application settings, not constants, to me. A constant is a value that is, well, constant, such as:

const decimal LITERS_PER_HOGSHEAD = 238.480942392;

Edited to add: Hopefully this is more helpful than my flippant answer. I usually create an AppSettings class. Some of the properties in this class are pulled from the config file, some are settings that I don't expect to change, and some could be constants.

public class AppSettings
{
    public const decimal GILLS_PER_HOMER = 1859.771248601;

    public string HelpdeskPhone
    {
        get { // pulled from config and cached at startup }
    }

    public int MaxNumberOfItemsInAComboBox
    {
        get { return 3; }
    }
}

Upvotes: 13

Joseph
Joseph

Reputation: 25513

It sounds to me like you're using constants mostly to illustrate configuration settings. This is ideal for the ConfigurationManager, but this can be difficult to use for testing as well.

I would recommend the following usage:

const String SomeValue = "TESTCONSTANT";

static class ConfigurationSettings
{
    static String SomeProperty
    {
        get
        {
           var result = SomeValue;
           if (ConfigurationManager.AppSettings["SOMEKEY"] != null)
               result = ConfigurationManager.AppSettings["SOMEKEY"];
           return result;
        }
    }
}

Upvotes: 0

Ot&#225;vio D&#233;cio
Ot&#225;vio D&#233;cio

Reputation: 74250

From what I read of your question this has nothing to do with TDD. The usage you describe is not of a true constant but rather of a configuration value, so in these cases you shouldn't be using the const modifier.

Upvotes: 3

Related Questions