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 :
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...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...).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...).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 :)
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; }
}
}
There are two kinds of constants:
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());
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With