F**k TDD

F**k TDD

(not really and not literally)

Can you write some unit tests for those classes?

One of the first task that was assigned me when I started to code professionally, as a junior backend developer.

Of course, being that I was freshly out of a really useless university, I did not know at all what the hell this guy was on about, so I just replied.

Yes, sure.

Before going balls deep into a google search frenzy.

Oh! So you don't need to run your application all the time to actually see if one peripheral class method is working as it should. Nice!

I loved them from the start, tests.

We weren't really doing much at the beginning of this project, so I spent weeks writing loads of tests to get that unit test coverage up to an amazing 90%.

And I knew now that, according to some articles, my code was bulletproof.

Of course it wasn't because you never think about the integration, you just test small components in isolation and cannot possibly think of all the different interaction that piece of code can have.

And it would be silly to think that you can ever cover 100% of the cases, even if you coverage tool says otherwise.

integrationVSunit

Later on in my professional career I stumbled upon a nice job, where a nice guy organised a nice workshop with an amazing guru developer, who actually showed us what TDD is.

That was also mind-blowing, and felt really counter-intuitive.

Write your test first, then code

In theory is amazing, and it does work quite well, you don't need to add them in later, and they serve as feature documentation, if you write them well enough.

But they are so boring, and you never know where to slice.

Do you need to write a test if you are going to perform an operation over an array inside a method? Do you need to test that the built-in .includes actually does not lie?

Maybe not. If is a proxy to a native language functionality you don't need to test it, do you?

What if is a combo of proxies.

function methodToTest(haystack, needle) {
    if(haystack.includes(needle)){
        return haystack.remove(needle);
    }

    return haystack.add(needle);
}

I know is a stupid thing to do, but is a combo of native things, that are "tested" by everyday life code being on production.

You might say

Actually if you write a test and then switch this method for something else, you will have the test that covers that case.

test('methodToTest actually does his shit') {
    haystack = [];
    needle = 'banana';
    expect(methodToTest(haystack, needle)).to.be(['banana']);
}

but do you ever do that? do you ever need that? Maybe not. Maybe yes.

Can you not write the test to cover the case when you are refactoring after?

If we want to bring this line of thought a bit further, every piece software is a combo of native functions well tested, so you don't need to test anything.

That would be so cool if it was true.

But tests are important, and TDD really makes us more confident in what we are releasing. It is just really boring to do, especially in small side-projects, if you still have no clear idea of what everything will be doing eventually.

Things on PoC evolve so quickly that will be such a pain in the ass to go for a TDD approach on a side project, that is really not worth the effort at all.

They are really good on giant projects for giant companies, not at all for other things.

Another funny thing I find out about TDD after during that workshop was that TDD is like sex around teenagers, everyone talks about it, no one really does it.

If you ask anyone (in our field) if they do TDD, they will say "pfft, of course we do, lol".

But they never properly do as explained in the papers.

Which is sort of this:

  1. Acquire feature requirement
  2. Write test
  3. Run test
  4. Fails? Write the simplest code to make the test pass goto 3.
  5. Success? goto 1.

TDD is like religion, everyone read the same paper and went off to found their own branch of TDD. Me included.

I gave myself those rules about TDD:

Thou shalt not care about code coverage.

It is kind of a useful metric at times, especially if you can see the reports of it and find out what areas of your code are not covered at all.

But never go full nazi.

Thou shalt also write code without a test every now and then

Sometimes is nice to see if something just works, you can add the test later.

Thou shalt write them eventually

It is not ALWAYS or NEVER. If you don't write them now, eventually do so. Everything is testable if you write things properly.

Thou shalt write some integration tests as well

In one company I worked for, we had a small app, all properly TDD-written that was used across all of the projects, a feature flag lib.

Features configurations were loaded from a json file.

One day a guy committed and pushed a malformed json file, and the whole stack collapsed because all the apps had different errors related to being able to load a malformed json.

And since the lib was loaded at app bootstrap, nothing bootstrapped any more, even though the codebase itself had 100% test coverage and 0 bug reports.

Thou shalt not being a dick about TDD

Do not snob people who don't write tests, maybe don't know how to do it, or they never had the realisation.

Just show them how cool they are and how useful they can be, maybe with this post :D

Thou shalt fuck off if you think that TDD on Frontend Components is a thing that you need to do.

I don't need to explain this, if you don't agree, I do not give a flying fuck. Just fuck off. If you do TDD on your react app you are mentally unstable and should be taken care of.

VDD

In Vincenzo(Vikkio)-Driven-Development, I work this way:

  • Get a cool idea.
  • Write a PoC on whatever language you are enjoying at the moment.
  • Write the code trying to follow Dependency Injection technique.
  • Does it work? Write Tests
  • It doesn't work? Leave it to die on github and never write test.

This approach works really well for side projects and also if you are putting together a good PoC for your full-time company. It gives you a nice scaffold to write your test on, so your code can evolve after you proven that it works the way you wanted it to.

Example

in here last week I was speaking about Strummulu.

A Library that will make your life easier if you are working on a multiplayer browser game based on websockets.

And it went down like this:

  • I wrote a lib around a fullstack application.
  • I deployed the fullstack application and tested that worked fine. (commit)
  • I split the code into lib and example. (commit)
  • I started working on tests. (commit)
  • I find out how cool it was that I used dependency injection to start with so I could do this sort of things.

After I will be finished writing all the tests I want for this app, I think it will be ready to be release as a lib, and having those tests will be a reassurance of me if I need to evolve it into something else, because I know what I need to cover and what every change will break.

In conclusion

Of course I know that there are other ways, better ways of making sure your app is properly tested, BDD for example, a natural evolution of TDD in a way.

There the tests are not only a way better doc of the functionalities, but also, with some frameworks, can generate even code for you.

Great.

But of course same rules apply here.

Overall I think I just have one single rule in this biased, opinionated, annoying but beautiful world that is software development.

Never do TDD.

Where TDD stands for Trends Driven Development. Never take decisions based on trends or only purely on what everyone else tells you to do, things work differently for different people, and whatever are commandments for me, can be stupid suggestions for you.

But never ever, do things just because others are doing them, instead do them only if you think that is actually useful for you and if it fits in, in your own way to write code.