underlap

I am Glyn Normington, a retired software developer, interested in helping others improve their programming skills.

Testing your code is a great way of avoiding bugs, but it's tempting to put off writing tests until you have something which works. At that point, it may be very difficult to test your code, especially to write unit tests, because you may not have written the code with testing in mind. One way to avoid this situation is to use Test Driven Development (TDD).

What is TDD?

TDD is a way of developing code by writing tests before the code to be tested. This way, you can be sure that your code can be tested. Also, you'll know that your tests are valid because they'll start off failing (a test which never fails is useless!).

How do I start doing TDD?

The basic process is easy: 1. Write a failing test 2. Make the test pass, together with any other tests 3. Refactor to make the code clean (described below) 4. Repeat as necessary.

A failing test might not even compile if it's calling some code which hasn't been written yet. That's ok.

This process is sometimes called “red, green, refactor” after the colours that some tools use to flag failing and passing tests.

Unit testing

A unit test tests some code in isolation (from other code, the internet, a database, etc.). Code composed of small modules is easier to test, if each module can be tested in isolation. It's then possible to drive unusual and error paths as well as the happy paths through the code.

The trick is deciding the size of “unit” to test, e.g. it could be a single module or a group of modules. If the unit size is larger, there's more scope for refactoring without needing to change the tests. If it's smaller, then moving code between modules and changing interfaces tends to require test rework.

Refactoring

The term refactoring is used in two different ways. The first kind of refactoring is to start with a piece of code and all its tests passing and then to make essentially arbitrary changes to the code, often in small steps, ensuring that the tests continue to pass.

So, starting with all the tests passing, the process is: 1. Change the code 2. Run the tests again 3. If some fail, fix the code or undo the change until all the tests pass 4. Repeat as necessary.

The second kind of refactoring is to make specific changes to the code which are known to preserve the behaviour. Sometimes this can be assisted by IDEs or editors with automatic “refactorings”, such as “extract method”, “rename variable”, and so forth. After doing this kind of refactoring, it's still worth checking all the tests still pass.

Modifying tests

Strictly speaking, if you modify tests, you should check they still catch the failures they were originally written to catch, but that's a real pain as you would have to temporarily break the code under test to provoke each modified test to fail. But without this, it's theoretically possible to mess up a test change and end up with a test which passes when it shouldn't. An extreme example of this would be to delete the code inside a test, which would obviously then pass, but be useless. The issue is that people might be tempted to hack their test code around on the assumption that running all the tests and seeing them pass is some sort of safety net when, in fact, it isn't.

The best way to avoid modifying tests excessively is by testing larger units. If you do need to modify tests, then doing a series of correctness-preserving refactorings reduces the risk of invalidating a test. But the rule of thumb is to be extra careful when modifying tests.

Do I need to follow TDD strictly?

It's quite a good discipline to follow the strict TDD approach for a while to get the hang of it. But after that, I think it's fine to be a bit more relaxed.

For instance, if you're trying to get a piece of code working, it may be more appropriate to code up a prototype as a “proof of concept” and then go back and develop some code using TDD now that you know roughly how the code will work. This approach is sometimes called “fire, aim, ready” (reversing the well-known phrase “ready, aim, fire”) meaning get something working first, then understand the problem better, then start development proper.

I have also used code coverage tools to make sure most, if not all, of my code is tested. You don't need to hit 100% coverage, but something like 80-90% is clearly a better sign of a good test suite than 20-30%.

Does TDD guarantee my code will be well designed?

No! Don't be caught by the trap of thinking TDD will necessarily give you a great design. It's a good way of avoiding untestable code, but it doesn't guarantee clean, understandable interfaces etc. Using TDD to get a good design is, as Rich Hickey once described, like driving a car along a road and bashing against the crash barriers (“guard rails” if you're American) as a way of getting to where you want to go.

Is TDD worth it on small, personal projects?

I've worked in teams, one doing pretty strict TDD, but on personal projects, I tend to unit test the stuff which I'm most likely to get wrong and with the most conditional logic. (I must confess I don't tend to invest in integration tests for personal projects as it's usually too much bother.)

More information

The classic route is to take a computer science, software engineering, web development, or similar university degree. But perhaps you are already working and don't want to go back to university. Or maybe you've done such a degree, but you're still finding it hard to land your first software development job. Read on.

The key skills you need to demonstrate in order to land a software development job are:

  • fast learning,
  • problem solving,
  • coding, and
  • ability to work in a team.

Learning and coding

It's worth building up a portfolio of coding projects (sometimes known as “side-projects”) in the area you want to apply for. (If you don't know what areas there are, take a look at the resources at the end of this article.) So, if you want to do web programming, you'd build your own web app. Similarly for mobile app development. If you choose some technologies you haven't used before, this will give you opportunity for some fast learning.

Store your portfolio somewhere it's easy to access such as github, bitbucket, codeberg, or similar.

Talking of which, a valuable piece of learning would be to work through a book or tutorial on the git distributed version control system. Make sure you understand how to commit changes, create and merge branches, and push to and pull from a remote git repository.

A general approach to fast learning is to ask yourself questions:

  • What's interesting here?
  • What don't I understand?
  • How could I explain this to someone else?

Read about Richard Feyman's technique for learning and teaching if you want some more ideas.

Problem solving

To practise problem solving, doing gradually harder and harder coding challenges, such as codewars, would be one way, although that's a bit artificial compared to solving problems in your own side-projects.

In particular, you need to experience getting stuck and then finding ways to solve the problem and get unstuck, because that's what software developers spend a lot of their time doing.

You might even want to read a general book on problem solving, such as Solve It!: The Mindset and Tools of Smart Problem Solvers.

One general approach to problem-solving us to narrow down the cause of a problem. Sometimes the symptoms of the problem give a clue as to where the cause of the problem is. Sometimes searching the internet for the symptoms and/or the piece of software causing the problem can turn up others who have experienced similar problems. From them, you can often pick up debugging techniques or sometimes even solutions.

On the other hand, if the problem is you can't figure out how to do something, then another general approach is to generate lots of potential solutions by using idea generation techniques such as brainstorming, forced relationships, and so on (see the resources below for more), and then to try the most promising solutions until you find a way forward.

One last problem-solving tip: take a break! Get some exercise, do something else, or simply take a coffee break. You'll be surprised how often “the penny drops” when you are doing something else.

(If you have done any mathematics in the past, it would be worth brushing up on predicate logic and set theory as these give some nice ways of thinking about programs and data. Also, doing exercises in these subjects is a good way of developing problem solving skills. If you don't have any mathematics, don't worry – many programmers get by with minimal knowledge of mathematics and pick up what they need on the job.)

Team work

Getting experience of working in a team can be tricky. You may be able to find a suitable open source project which you can contribute to. But any teamwork in another setting, such as sports or social activities, is a good start. Programming is far from a solo activity and learning to work well with others is really important.

Conclusion

Finally, to land a software job, you'll need to demonstrate a genuine desire to work in programming by devoting quite a bit of time to developing your skills – I guess maybe 10 hours a week or so. By honing your coding, learning, and problem-solving skills in your spare time, you'll also get a better feel for whether this is the kind of thing you want to do as a job.

Resources

#software #softwaredevelopment #softwareskills