Today, I’m going to tell you about my quest to discover tests. I think that many people walk the same path I walked, struggling with the same obstacles and debating over similar problems.

There is value in that. We all learn best from our own mistakes, and when lost, you often find interesting places. I’m sharing my story, because many of us stop in one of these places, only because we don’t know there is something more ahead of us. I want to point you in a new interesting direction.

The Start

In my early days of learning computer programming, I was writing no tests at all. It wasn’t a conscious choice, I simply didn’t know the concept of automated testing existed. Sadly, hardly any book on programming languages had shown that you can write tests, so a rookie like me had never seen one.

Back then it was a challenge to understand how my code runs. Having tests wouldn’t help anyway. I would not understand them. I was writing code line by line, running it often to see if it did what I had hoped for.
I think this is a natural stage. Even now, I’m still in this mode whenever I learn a new language. But beware of staying there for too long.

At some point you know how software works. You can write or read 20 lines of code and reason about it. You write bigger things and you start finding yourself spending a lot of time running your code with different inputs and making sure it works…

This is the place where you probably no longer run your code to see what it does, but to make sure it works. First achievement unlocked. You have discovered manual testing.

Humble Beginnings

Testing manually is fine. The problem with it is that it takes time. It’s one of these things that can make you spend a whole day doing a small task.

In engineering quite often you can save time by making the computer do your job. But… I failed to notice that for a very, very long time. I guess it’s just easy to keep doing the things you’ve used to do, especially when you’re enjoying your hobby, and you’re in high school, programming in your free time.

At some point my software had portions that I recognized as separate units that should work on their own, but because I didn’t know how to manually exercise these units, I was writing code like this:

int main() {
    std::cout << Value(4) + Value("3") << std::endl;
    std::cout << Value(4) + Value("foo") << std::endl;
    std::cout << Value("4") + Value("3") << std::endl;
    std::cout << Value("4") + Value("foo") << std::endl;
    std::cout << Value("3.4") + Value("4") << std::endl;
    // ~80 lines of similar code follows
} 

This is not a test. It is easy to turn it into one, but I wasn’t trying to. I was going to compile it, run it, read the output and verify that it is ok. Or not. Then I would fix it, compile it, run it, read… And so on, for many hours.

I was happy. Back then I thought that if it takes a lot of time it must be hard, and I felt good about myself, being able to do these hard things.

If you have ever written code similar to the one above, but you’re not sure how to write actual tests, then you’re ready to ask google “how to write unit tests in “.

Invest a day or two in learning this, it’s worth it. At first it will feel artificial and superfluous. Treat it like an exercise and just learn how to run a single function in a test. Once you have this skill, opportunities to use it will come in abundance.

Let There Be a Unit Test

If my memory serves me well, I think I’d first seen a unit test a few years later, at an IT conference. And soon I was hearing about them pretty often. Documentation of frameworks started to show how to test code using these frameworks. Newly written books included chapters about unit testing.

I was happy with my habit of manually testing code but everywhere you could hear that testing is good and having tests is a good practice. I was always a fan of idiomatic code and good practices so I jumped in.

I started to write tests with the understanding that thanks to it, my code would have fewer bugs.
Today I often meet or hear people who are at this stage. It’s not a bad place to be. You no longer test your functions manually, your tests are actually tests, that give you a YES/NO answer without any need for further inspection, like this upgrade of the previous example:

def test_that_value_prefers_numeric_types_during_addition():
    assert Value(4) + Value("3") == Value(7)
    assert Value(4) + Value("foo") == Value("4foo")
    assert Value("4") + Value("3") == Value(7)
    assert Value("4") + Value("foo") == Value("4foo") 

You now hit one button, wait a little, and a green message says things are good. No more manual testing. Productivity finally increased. Bugs are gone.

From now on, the code does not change much, but there are hundreds of opinions on what and when to test. Since there are tons of people screaming about TDD and how you should test everything, I always find it interesting when somebody says otherwise. Here are some reads I’ve enjoyed. Explore the references too. And if you’re interested in my opinion about anything, let me know.

Test Automation Snake Oil by James Bach
Classic Testing Mistakes by Brian Marick
Python Testing by Brian Okken.

Another small hint is: don’t bother with the language. So far I have not seen a programming language where testing would be fundamentally different, so no matter what you use, any book will do.

The Eureka Moment

At my first job, I became a developer in a huge system written in C++. I’ve never seen code so big. And guess what? These guys had tests.

Sometimes I’d made a change, run the test suite and discovered one case started to fail. Tests were really preventing bugs! The prophecy came true.

One day I’ve found a book titled Working Effectively with Legacy Code. I read it and recommended it to a colleague, who in exchange suggested I read Refactoring.

These two books have profoundly changed my understanding of tests.

I used to think that tests are a bit like a proof of my code’s correctness, that complicated functions should have a lot of tests, and that code coverage should aim at 100%. In short, the more tests, the better.
But I’ve never really asked myself why tests help with correctness and so I’ve never really understood them.

Where Bugs are Born

My current appreciation of tests comes from my understanding of how bugs are introduced into the software.

While software is small and fresh, you, its author, can fully comprehend it in your head and you’re unlikely to have bugs. Perhaps some small, honest mistakes that will quickly be fixed during your manual testing. Tests won’t help you here because they’ll be more complicated than the main code itself.

But it is when you don’t understand or know the whole code you work with, that bugs are introduced into the software. Your “evil” coworker wrote a function that does not check inputs, because he’s used to the style where the caller of a function does that. You’re used to the style where functions check their inputs and you call it without doing your part. For you, it’s obvious you did the right thing, you don’t consider it a special case and you don’t test that. And we get a defect.

You don’t need two people for this to happen. Write enough code, let some time pass, and once you forget the details, you’ll be your own evil coworker.

Once you start working on the code that you don’t understand (or remember), any change can introduce bugs.

Productivity growth

This leads to the essence of my current understanding of tests.

I write tests not to check if my code is correct, but to make sure nobody breaks it. I write tests so that my code stays correct.

My tests not only increase my productivity. They influence the productivity of the whole team. A more productive team produces better quality software, because everybody has enough time to do their best.

It is All Good

I don’t want to leave you with the impression that there is a good and bad way to write tests, and that unless you know-it-all your tests are the bad ones.

Testing is highly connected with productivity. Writing tests takes time. A lot of it. If you know what you’re doing and why, you’ll know whether this time is worth it and if the project’s lifetime will let you gain from that investment.

My appreciation of tests has changed, but the more experience and understanding I had, the more choices I got.

I’m still doing all of the things I’ve mentioned. They’re all good. I write code with no tests when I don’t know what I’m doing. I design for testability in a Test Driven manner. I write tests so that I don’t need to check my code manually. And I write tests so that I know nobody breaks my stuff and I don’t need to retest it before every release.

Thank you for staying with me until the end.

If this inspired you to try new things, please say hi in the comments. And if you can point me in a new direction, please do!

Posted by

Tomek Rydzyński

Share this article