My life in TDD: Love and... love?

My life in TDD: Love and... love?

Test-driven Development (TDD): You write an empty suite, then you write a test for that suite, then you fail a test, then you refactor until test passes. Done. Here is emoji version of that statement:

⌨️ => ? => 🛑 =>(⌨️ => ?) => ✅

That's it? Not really.

Back in my university years, we haven't even studied test frameworks. Actually, one of my tutors denied tests being useful (don't ask who). Life was pretty easy back then: you write a code which works, then your work is done. Of course, refactoring was quite hard, since changing of any line of code could not be proved to be breaking anything... but it definitely did. Many times.

Life without tests was pretty much like: you read requirements, you generate idea in your head, you sit down and non-stop write that 50+ liner function which throws a bunch of exceptions (or sometimes just printed stack-trace and returned NULL 😱), plug it into your main app, and - done!

But when I started to work, it appeared to be that this kind of approach is a pure disaster in a medium to large projects. Actually it is a disaster everywhere, no matter what.

Imagine that you need to create that feature which uses functionality of different other features. Many dependencies, use-cases, data-types and so on. Even implementation of this kind of functionality will take ages, not talking about quality or even considering all edge-cases. It doesn't just bring a complexity, it brings that ghostly evil... silent failure. Yes.

Let's go through my imaginary examples (God, I love imaginary examples):

Your project represents an accounting software which makes calculations. Your task is to write a feature called getAlwaysFour to apply some very simple formula on output from input:

//Kotlin of course. What else?

fun getAlwaysFour(definitelyTwo: Int): Int{
   return definitelyTwo * 2 
} 

One last thing: your business says that definitelyTwo variable will be always 2.

Next day you implement a function, and everyone is happy, reports are amazing and you will be definitely a tech lead by the next year. Sweet!

But then another report comes, and numbers are incorrect! And the reason is that your featured function returns 6 instead of 2! Ah! How come?

Well, this is not a surprise in parallel to TDD universe. There was test.

Test represents a contract, SLA for a functionality. Maybe even insurance. By writing a test you explicitly declare a static behaviour of your function. In above example no issues could happen if there was a test checking function output on every build/change.

Okay, okay, okay. Let's use a magic TIME MACHINE and go back in the time! 🕰

You just received a requirement for getAlwaysFour feature. But now you are aware of TDD. How would you start?

You would write first an empty, shell function. Like this:

fun getAlwaysFour(definitelyTwo: Int){}

Then you would write a test:

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class UnitTest {     
	@Testfun getAlwaysFourTest() { 
		//Given
		val expected = 4
		//When
		val actual = SomeObject.getAlwaysFour(2)
		//Then
		assertThat(actual).isEqualTo(expected)
	} 
}

Then your run a test ▶️ to find out that...

kotlin.Unit is not equal to 4! Fail!

Of course! Since your function returns a Unit, which is a definition of... mmm.. nothing.

And then you implement a function to pass a test, whoa:

fun getAlwaysFour(definitelyTwo: Int){
	return 4
}

And run a test again ▶️...

Look, look! It is green now!

4 is equal to 4. Congratulations!

Amazing. Just amazing. In our example assume that given argument is always 2, but if you need to refactor a function, it will not work. So returning 4 is a kinda permanent logic fix

¯\_(ツ)_/¯

But let's play by rules and do a good functional refactoring:

fun getAlwaysFour(definitelyTwo: Int){
	return definitelyTwo * 2
}

And run a test again ▶️...

And again success. Surprise.

But, of course, if we get something different to 2 => we are screwed.

In this case we would be aware of uncertainty and heavy dependancy of function over the passed variable. Boom@

And if you need to refactor, with a good TDD implementation any change will cause your tests to fail, which is totally normal (even awesome). We are aware of something being changed, no ghostly silent fails. Not anymore!

I recommend to try TDD to anyone, and if you are sure that it doesn't work for you => let's have a coffee 😉

PS. Huuuuge thanks to OCTO who inspired me for a craftsmanship and better practices ❤️

0 Comments
Loading...