Developers, please write tests!
In my previous article I’ve described how to write source code in a TDD way. This story is the next part of it. We want to focus on the next thing. To implement some logic behind the clock in a test-driven way.
We have luck because the build-in Java class System have a method called
System.currentTimeMillis()
which return the current time since… Ok, you know what I mean.
But we have a problem. In our test we want to check the same time. But when you call the method above it will return always a different time. For more clarification some code:
@Test
public void testCurrentTimeMillis_ShouldReturnCurrentTimeMillis() throws Exception {
long expectedTime = System.currentTimeMillis();
long clockTime = mMyClock.currentTimeMillis();
assertThat(expectedTime, is(equalTo(clockTime)));
}
Note:
The currentTimeMillis() method in the MyClock object will be discussed later. At the moment all you need to know is that they will return System.currentTimeMillis().
The code above results in
java.lang.AssertionError:
Expected: is <1455569450334L>
but: was <1455569450336L>
We got different values. We need also any art of mocking or something.
Now we can discuss about the MyClock#currentTimeMillies().
The method is just a simple wrapper about the method from the System object. Only for testing. And yes, this is absolutely ok! Take a look into the implementation
@VisibleForTesting
long currentTimeMillis() {
return System.currentTimeMillis();
}
Why is it special?
First — it is package visible. Why? Because we need to override it in our test. And package visibility is the smallest visible state to perform that.
Second — a mysterious annotation. This annotation make “nothing”. It’s only a indicator that the method is more visible to make the code testable.
So how should our code look so that our test went green?
private long mTimeMillis = System.currentTimeMillis();
@Before
public void setUp() throws Exception {
mMyClock = new MyClock() {
long currentTimeMillis() {
return mTimeMillis;
}
};
}@Test
public void testCurrentTimeMillis_ShouldReturnCurrentTimeMillis() throws Exception {
long clockTime = mMyClock.currentTimeMillis();
assertThat(mTimeMillis, is(equalTo(clockTime)));
}
Ok, cool. But that is a dump test! Yeah, I know. But lets write the next part. The “transform time to the right format” thing.
For this we need no new tests. We need to modify our existing ones. Do you remember this test
@Test
public void testGetCurrentName_ShouldReturnTimeInGermanFormat() throws Exception {
String germanTime = mMyClock.getCurrentTime(TimeFormat.GERMAN);
assertThat(germanTime, is(equalTo("14:45 Uhr")));
}
Now we need to modify this that they return the current real time instead of the static one:
@Test
public void testGetCurrentName_ShouldReturnTimeInGermanFormat() throws Exception {
String germanTime = mMyClock.getCurrentTime(TimeFormat.GERMAN);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm 'Uhr'");
String formattedTime = simpleDateFormat.format(new Date(mTimeMillis));
assertThat(germanTime, is(equalTo(formattedTime)));
}
This test uses now the current time. That’s cool. But our test is broken if you run it
java.lang.AssertionError:
Expected: is “18:50 Uhr”
but: was “14:45 Uhr”
So change the implementation to match the test.
public String getCurrentTime(TimeFormat format) {
switch (format) {
case GERMAN:
return getStringFormattedTimeWithPatternAndLocale("HH:mm 'Uhr'", Locale.GERMANY);
case AMERICAN:
return "2:45 PM";
case GERMAN_WITH_DATE:
return "14:45 Uhr, Freitag 12.02.2016";
case AMERICAN_WITH_DATE:
return "2:45 PM, Friday 02.12.2016";
default:
return "";
}
}@VisibleForTesting
long currentTimeMillis() {
return System.currentTimeMillis();
}private String getStringFormattedTimeWithPatternAndLocale(String pattern, Locale locale) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern, locale);
Date currentDate = new Date(currentTimeMillis());
return simpleDateFormat.format(currentDate);
}
Et voila — this test (and any other) is green. Cool. By the way, have you noticed that we have already implemented our implementation in our test case? This isn’t regular. So don’t be amazed.
The last thing is to change the other tests to the real time — and the implementation — too. The fully implementation of our source code can be found here. Now you can use our MyClock classes like this
MyClock clock = new MyClock();
String currentTimeInGermanFormat = clock.getCurrentTime(TimeFormat.GERMAN);
System.out.println(currentTimeInGermanFormat);
String currentTimeInAmericanFormat = clock.getCurrentTime(TimeFormat.AMERICAN);
System.out.println(currentTimeInAmericanFormat);
String currentTimeInGermanFormatAndDate = clock.getCurrentTime(TimeFormat.GERMAN_WITH_DATE);
System.out.println(currentTimeInGermanFormatAndDate);
String currentTimeInAmericanFormatAndDate = clock.getCurrentTime(TimeFormat.AMERICAN_WITH_DATE);
System.out.println(currentTimeInAmericanFormatAndDate);// Output: (actually ;))
// 19:35 Uhr
// 07:35 PM
// 19:35 Uhr, Donnerstag 18.02.2016
// 07:35 PM, Thursday 02.18.2016
Well, thats all. Wasn’t hard, right? And do you know what? There are more benefits as just a fully tested object:
* Your test is automatically a documentation of how to use an object
* You write nicer and cleaner API’s of your object
I think there is more, but I think these three things are so great to make tests with every class you write in the future.
So please, write tests!