2 ms·
TDD on a 'Hello World'
- mikekchar 8y agoI'll be frank. I hate arguments like this: reductio ad absurdum. However, let's try and save this blog post. Why, exactly, have we run into a problem trying to test drive "Hello, world"? First, let's ask the question: what is the important output of "Hello, world"? What are we trying to accomplish. Usually it is a program to see if the compiler is working. It is, in itself, an acceptance test. We don't TDD acceptance tests generally. However, let's say that we want to write a program that outputs a string literal to STDOUT for some reason. Is the string literal itself an important part of the functioning of the program? If I change the literal, will the program function badly? This actually gets to the heart of "unit test" versus "acceptance test". With an acceptance test we want to write tests that match our implementation with business requirements. However, with unit tests our goals are much more humble. We wish to alert ourselves when modification to the code has changed the behaviour of the program. We do not actually need to assert that the behaviour matches the business requirements (that's what acceptance tests are for!) This solves one of the dilemmas in the original article: No, there is no need to make "Hello, world" more general. And we do not actually need to test that it outputs "Hello, world". Instead, we need to have an acceptance test (which might be a manual test) that shows that it outputs "Hello, world" and we need unit tests to show that changes to our implementation do not change behaviour. In other words, for a unit test, we should concentrate on whether or not we have written something to the stream. We may even want to check to see if that something is the string literal (which we could put in a constant for easy access). However, there is no need to assert that the string literal is, in fact, "Hello, World". I really can't stress enough the importance of this distinction in writing good unit tests. So I need a library that has a string literal as a constant and a function that takes a stream and prints the string literal to it. Then I need a test that sets up the stream, runs the function and reads the contents of the stream. This is not any more complicated than the problem deserves. Do we need to test that main, run the code? I think this is a very interesting question. Again, we need to go back to the difference in purpose between an acceptance test and a unit test. If we can keep the code in main down to a single line, then perhaps we can simply have an acceptance test (possibly manual) that tests that the program starts up properly and unit tests for the behaviour of the program. If main starts to have more than one behaviour (startup), then we will have to refactor it. What happens when we do want the program to print something other than a string literal? That's when we start adding complexity to the program. If you find that you are adding unnecessary complexity to your program in order to write unit tests, usually it's a good sign that you are approaching your unit testing in a less than optimal fashion. There are cases where it is extremely tricky to write good unit tests (my favourite example is trying to test a function that outputs an infinite series -- like the other favourite "TDD doesn't work" example, Fizz Buzz). I hope some people find this interesting/useful. Like I said, I generally hate arguments like the one the article presents and I usually try not to respond to them, but I was at a loose end tonight. Edit: Nonsensical wording.
- gmiller123456 8y agoI'm sure everyone who's heard of TDD has played through similar scenarios in their head. But even the most evangelized TDD proponents almost always end their talks with addressing the fact that some code can't or shouldn't be tested. And I think the fact that they wait until the end to say things like that turns a lot of people off of TDD. I have used TDD to develop libraries and services, and it works really well. You have to write code to test your work anyway, so it's not really a lot of extra work. And writing the tests first really helps (me) think more about how the user will use my library/service. I, in no way, think that EVERYONE should ALWAYS use it, in the way that a lot of people have preached. But it is frequently a good tool for the job, and I think a lot of people have abandoned the idea because of the way a few bad actors have presented it.