views:

325

answers:

6

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

+10  A: 

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; }
    }
}
Jamie Ide
I think that this could be only the way to go, finally. But from time to time, I create constants for the sake of readability or just because I think that the value will not change in a foreseeable future but it may! Say : MaxNumberOfItemsInAComboBox... I am reluctant to create an application setting for such values. But I have to make sure my app will still work if someone change it... Thanks for your answer :)
Sylvain
+1 for the constant names. And the answer too... but GILLS_PER_HOMER pushed it over the edge.
ojrac
Hey, any chance to know what Gill_Per_Homer means ? Sorry, i'm french, like a bunch of stackoverflower :)
Sylvain
I was looking for a silly unit conversion for my example and found this great site: http://www.unitconversion.org/. Gill and Homer are two archaic volume units of measurement. Thankfully, the French have given us the metric system to replace this mess.
Jamie Ide
Hey super cool, I just learn something important.All the best :)
Sylvain
But about code you are 10 0000 km behind. Any suggestion ? :)
Sylvain
"Bunch of stackoverFLOWER". Poetic.
WW
+3  A: 

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.

Otávio Décio
Ok, but what about the "true constants"? How can we make sure that someone somewhere can change them without having to test everything? If he or she have to test everything, should we have use a constant? Without constant, the message is clear: guy, you are not in luck! You have to search all the source code and to test everything. Creating a constant is a contract, how do you make sure it will be OK? Thanks for your answer :)
Sylvain
If you use TDD, testing everything is a trivial press of a button. A constant should not exist in the first place, unless there was first a failing test that required that constant to be created. If nothing breaks when you run the tests after changing the constant, everything works as specified by the tests.
Esko Luontola
"True Constant"? PI? It can never change. The tests for constants or config parameters are trivial. The design issue is what you're having trouble with -- folks did not design things to use the configuration parameters the way you think they should have been used. That's not TDD. That's design.
S.Lott
S.Lott, you are right. But I still have to test everything every time I change a constant! I'm a software maintainer (actually) and I just lost 8 hours today trying to figure out why changing a simple constant's value didn't change anything! My point is : the next time I will write a constant (or an application parameter), how can I get sure that my fellow will not have to lost it's day if he want to change it's value ?TDD is super cool, DRY is fantastic, but all in all, being understandable to our fellow is the ultimate goal.
Sylvain
Esko, can you answer the same question at first level? This is my fav now.
Sylvain
A: 

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;
        }
    }
}
Joseph
+1  A: 

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.

John Nolan
I really appreciate your comment. But I had really bad times (every other days) changing constant values... In my opinion, if you cannot make sure your constants can be changed, you better use literals! I will then be much more doubtful and I will inspect all the code.I have passed so much time just to try to figure out why changing a constant did not have the expected effect... Thanks for your answer.
Sylvain
+2  A: 

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

fortran
Too cool to ignore :)
Sylvain
I knew that somebody would notice! xD
fortran
+3  A: 

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.

Esko Luontola