Should we reuse constants between code and specs?

An interesting question arose during one of code reviews in our team: if there’s a constant 1 in the production code, should it be reused in the spec or should we use a literal value?

The one side of the argument was that using the literal value is a duplication. When this value changes, we have to update it both in the code and specs – therefore the constant should be reused.

The other side of argument was that the specs should work as a “double verification” mechanism, and thus avoid code reuse – otherwise, they will give false positives when the value of a constant is wrong.

Both arguments seem legitimate. Which approach should we choose, then?

The answer, as usual, is: it depends.

We can distinguish two different use cases for constants, that require opposite strategies:

1. The constant’s value is important, but the fact that it is a constant is an implementation detail.

Consider a function that converts a value provided in meters to text. Values lower than one meter should be additionally converted to centimeters, e.g. a value 10.27 should be formatted as “10.27m” and a value 0.27 should be formatted as “27cm”.

The naive implementation could look like this 2:

function toText(meters) {
    if (meters >= 1) {
        return '' + meters + UNITS.meter;
    } else {
        return '' + (meters * 100) + UNITS.centimeter;
    }
};

UNITS = {
    meter: 'm',
    centimeter: 'cm'
};

How should we specify it?

Like this:

expect(toText(0.27)).toEqual('27cm');

Or like this:

expect(toText(0.27)).toEqual('27' + UNITS.centimeter);

The '27' + UNITS.centimeter case may seem less brittle: if we want to change the format from “27cm” to e.g. “27 centimeters”, it will work transparently, while the '27cm' spec will have to be updated.

However, changing the format from “cm” to “centimeters” is a change to the requirements. It is perfectly OK to have to update specification when requirements change!

On the other hand, consider what happens when we accidentally introduce a bug in the constant:

UNITS = {
    meter: 'm',
    centimeter: 'blah'
};

The literal '27cm' spec will catch this. The spec that reuses the constant will not – it will give us a false positive.

Or consider the following refactoring:

function toText(meters) {
    if (meters >= 1) {
        return '' + meters + UNITS[0];
    } else {
        return '' + (meters * 100) + UNITS[1];
    }
};

UNITS = ['m', 'cm'];
};

Changing constant from object to array is an implementation detail, it doesn’t change requirements, so it shouldn’t require any changes to our specs. However, in the case that reuses the constant, the spec will break.

Therefore, if using a constant is an implementation detail, we should NOT reuse it in specs.

2. The constant’s value is irrelevant, but the fact that it is a (particular type of) constant is important.

Consider a different example – a function that checks if a card suit is “black” or “red”.

An implementation could look like this 2:

function isBlack(suit) {
    if (suit == SUITS.spade || suit == SUITS.club) {
        return true;
    } else {
        return false;
    }
};

SUITS = {
    spade: 'spade',
    heart: 'heart',
    diamond: 'diamond',
    club: 'club'
};

The question is the same as before.

Should we specify it like this:

expect(isBlack('spade')).toBeTruthy();

Or like this:

expect(isBlack(SUITS.spade)).toBeTruthy();

Although it may be not obvious at first glance, this situation is completely different from the previous example. Now we don’t care about the value of the constant.

We could imagine refactoring like this:

function isBlack(suit) {
    // ...
};

SUITS = {
    spade: 1,
    heart: 2,
    diamond: 3,
    club: 4
};

Changing values of constants from strings to integers (or to objects or anything else) is an implementation detail, and doesn’t matter from the requirements point of view. Yet in the isBlack('spade') case, the spec will break.

The isBlack(SUITS.spade) case is resistant to the constants’ value changes and will still work correctly.

What’s more, as opposed to the first example, we don’t have to worry about false positives in the case of an error in the constant’s value.

Even if we make a mistake like this:

SUITS = {
    spade: 'BLAH',
    heart: 'heart',
    diamond: 'diamond',
    club: 'club'
};

Both the code and spec will still work correctly, so we don’t have to guard ourselves against it.

Therefore, if a constant is a fundamental part of the model design, we SHOULD reuse it in specs.

Or even better, avoid the constant at all…

In the two above examples, we assumed that there has to be a constant, and focused only on what to do with it in our specs. However, in most cases, we could make our specs and code even more robust, by hiding or avoiding the constant completely.

In the first example, we could hide the constant inside the function, like this:

function toText(meters) {
    var UNITS = {/*...*/};

    // ...
};

This way, the constant wouldn’t be accessible in the specs at all, what would free us from our doubts.

In the second example, we could replace the constant with a more object-oriented structure, that would allow us to write clean specs like this:

var spade = Suits.spade();
expect(spade.isBlack()).toBeTruthy();

However, if you really need a constant in your code, the guidelines presented in this post should help you write more robust specs.

How often do you happen to use constants in your code? How do you write specs for such code? Please share your opinion in the comments below!


  1. I use the term “constant” in a very broad sense. Various programming languages offer several different constructs that may be considered as a “constant” in the context of this discussion. The guidelines presented in this post apply equally well to “real” constants (either global or class level), enums, static variables or even “pseudo-constants” implemented by the pure naming convention. 
  2. WARNING: The examples I use are a really bad antipattern of how to implement enums in JS! I do it in such a way only for the sake of the simplicity of the examples. Never use something like this in the production code! 
Advertisements

What do you think?

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

WordPress.com Logo

You are commenting using your WordPress.com 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 )

Google+ photo

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

Connecting to %s