Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 :

  • You change the LogCompleteFileName constant value from L:\LOGS\MyApp.log to \\Traces\App208.txt and you get two files : \\traces\App208.txt for the traces and \\traces\App208.txt.log for the logs...
  • You change TransactionTimeout from 2 to 4 minutes and you still get a timeout after 2 minutes (after spending the day, you find out that you also have to change the timeout of the DBMS and the timeout of the transaction manager...).
  • You replace SleepTimeInMinutes from 1 to 10 and you see no change (after an hour or so, you find out that the constant's name was misleading : the granularity is not the minute but the millisecond...).
  • Even more subtle: you change CompanyName from, say Yahoo to Microsoft but automated mail alerts are still sent to [email protected]...

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 :)

like image 378
Sylvain Rodrigue Avatar asked Jul 06 '09 19:07

Sylvain Rodrigue


2 Answers

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; }
    }
}
like image 129
Jamie Ide Avatar answered Oct 14 '22 15:10

Jamie Ide


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.

like image 42
Esko Luontola Avatar answered Oct 14 '22 14:10

Esko Luontola