5

I am a junior software developer and I have been researching some of the practices in the industry to make myself better. I have been looking at unit testing briefly and I cannot see how the extra time writing a ton of unit tests is going to make my code better.

To put things into perspective:

  1. The projects I work on are small
  2. I am the only developer on the said project (usually)
  3. The projects are all bespoke applications

The thing I don't get the most is, how can a unit test tell me whether my calculate price function (which can depend on things like the day of the week and bank holidays etc, so assume 20-40 lines for that function) is correct? Would it not be quicker for me to write all the code and the sit through a debugging session to test every eventually of the code?

Any examples that are forms-based would be appreciated (examples I have seen from the MSDN videos are all MVC and a waste of time IMHO).

Stuart Blackler
  • 301
  • 2
  • 8
  • 1
    don't test units, test scenarios: Feature --> Stories --> Scenarios – Steven A. Lowe Jul 17 '11 at 16:54
  • 3
    @Steven, you've been saying this a whole lot on the site lately but it's not very helpful without a strong and clear definition of words like "feature" and "scenario". Most "units" in a good design exist to implement a single *feature*, and their members implement one or more *scenarios* depending on how the dependencies and arguments are set up. An automated test is an automated test; the rest is mostly semantic nonsense. – Aaronaught Jul 17 '11 at 21:48
  • @Aaronaught: sometimes I do feel like a parrot. I have no problem with a scalable definition of unit, but many developers seem to want to limit 'unit' to 'function' or 'method'; it is this self-limiting belief against which I rail. I have provided a lot of supporting explanation in other answers, did not want to dump it all back out in a comment if the OP was not interested. The 'unit' in question above is a single function, and though the OP lists some possible scenarios for using the function he does not seem to get the connection. Hence the soundbite. – Steven A. Lowe Jul 17 '11 at 22:01
  • @Steven: Perhaps a link then, to give some context. However, I think that in this case, the function essentially *is* a feature; calculating a price taking into all factors affecting that price is precisely the level of granularity at which a test *should* be written. There's no rule saying that unit tests have to test a single function or even a single class; likewise, there's no rule that a "TDD test" has to test *more* than one. – Aaronaught Jul 17 '11 at 22:07
  • @aaronaught: a function is not a feature; a function may implement a feature - to be pedantic ;-) otherwise i agree, there are no rules limiting what a unit test can test - but the common usage of the term 'unit' (and the wikipedia definition as i recall) implies unit = module, not unit = feature. chat? – Steven A. Lowe Jul 18 '11 at 01:50

5 Answers5

23

The thing I don't get the most is, how can a unit test tell me whether my calculate price function (which can depend on things like the day of the week and bank holidays etc, so assume 20-40 lines for that function) is correct? Would it not be quicker for me to write all the code and the sit through a debugging session to test every eventually of the code?

Let's make this the example. If you sit down and write that (let's call it) 30-line method, you're going to try to think of all the possibilities, write them into the method, then debug it, again trying to take all possibilities into consideration. If you've gone as far as checking for days of the week and got to bank holidays, and find a bug, then you need to change the method, and it would be easy to change it in such a way that it now works correctly for bank holidays, but not for weekends - but since you already checked for weekends and it worked, you might forget to re-test. That's just one scenario for bugs to creep into your product with your approach.

Another problem is that it can be easy to add code for conditions that never occur in fact, out of excessive caution. Any code you don't need adds complexity to your project, and complexity makes debugging and other maintenance tasks harder.

How does TDD protect you against these issues? You start by writing the simplest case, and the simplest code that will pass it. Say your default price is $7. I'll write this Java-ish, 'cause it's handy, but the approach works in any language:

public void testDefaultPrice() throws Exception {
    assertEquals(7, subject.getPrice());
}

public int getPrice() {
    return 7;
}

Simple as can be, right? Incomplete, but correct as far as we've gone.

Now we'll say the weekend price needs to be $9. But before we can get there, we need to know what days constitute weekend.

public void testWeekend() throws Exception {
    assertTrue(Schedule.isWeekend(Weekday.Sunday));
}

public boolean isWeekend(Weekday.Day day) {
    return true;
}

So far so good - and still very incomplete.

public void testWeekend() throws Exception {
    assertTrue(Schedule.isWeekend(Weekday.Sunday));
    assertTrue(Schedule.isWeekend(Weekday.Saturday));
    assertFalse(Schedule.isWeekend(Weekday.Monday));
}

public boolean isWeekend(Weekday.Day day) {
    return day == Weekday.Sunday || day == Weekday.Saturday;
}

Add as many assertions as you need to be confident you have driven the method to correctness.

Now back to our original class:

public void testDefaultPrice() throws Exception {
    assertEquals(7, subject.getPrice());
}

public void testWeekendPrice() throws Exception {
    subject.setWeekday(Weekday.Sunday);
    assertEquals(9, subject.getPrice());
}

public int getPrice() {
    if (Schedule.isWeekend(day))
        return 9;
    return 7;
}

And so it goes. Please notice, also, how we are test-driving the design of our code here, and how much better it is because of it. With your approach, most programmers would have built the weekend-testing code into the body of getPrice(), but that's the wrong place for it. Plenty of code might want to know whether a day is on the weekend; this way you have it in a single, well-tested place. This promotes reuse and enhances maintainability.

Carl Manaster
  • 4,173
  • 18
  • 31
  • Good job Carl - As I read it I felt like I was pair programming with an expert on the subject. – mcottle Jul 18 '11 at 02:21
  • Good work and easy to read. Some of your assertEquals statements are missing closing parentheses. – CLandry Jan 29 '13 at 19:15
  • 2
    I'd actually say that this is a pretty good example of the wrong approach to TDD. The _behaviour_ you're interested in is that `getPrice` returns 9 on Saturday or Sunday and 7 otherwise. Whether or not you put the logic in a `isWeekend` method is an implementation detail, which you've unnecessarily coupled your tests to. (If you want to move `isWeekend` to a polymorphic method on `Day`, then your tests will fail even though the behaviour is not broken.) You should test the behaviour of `getPrice` and extract an `isWeekend` method during the refactoring step, without writing new tests for it. – Benjamin Hodgson Mar 03 '14 at 07:54
11

"sit through a debugging session to test every eventually of the code?" might be quicker...

The first time.

When you have to come back and change something, you would have to have another session. Then two weeks later, they want something else changed and you will have to have another session.

After the first change, and sometimes even straight after initial development, the accumulated testing time is now way over the time it would have taken to write the initial unit tests and extend them with every change for that specific change. Unit tests can give you the confidence to change complex (not complicated!) code and know that you haven't broken any of the current functionality.

Marjan Venema
  • 8,151
  • 3
  • 32
  • 35
  • 2
    I don't even find it to be quicker the first time, when you consider that most unit tests will use mocks and fakes and other techniques to eliminate the dependency noise. In fact, if you've put any effort into a clean architecture/design, you will almost certainly be needing to test certain functionality before you even have a working application to debug. Unless you enjoy having to build throwaway apps and databases all the time in order to test your libraries, unit tests are are faster *every* time, including the first. The only thing faster (at first) is not testing at all. – Aaronaught Jul 17 '11 at 21:54
3
  1. It probably won't be quicker for you to use the debugger and step through each condition and track the inputs and outputs.
  2. What you gain is a test that you can continue to run in the future after any changes are made to your code. Say, you add a new condition to your calculate price function in the future and it happens to invert one of your other conditions that your software relies in - the failed unit test would be your saving grace. You wouldn't have to think about hopping into the debugger and testing each condition again.

IMHO it's really hard to assess value of unit tests in your position, especially because you are the only one working on your projects.

I would try a book out for more details on the benefits and techniques for testing:

http://www.amazon.com/Art-Unit-Testing-Examples-Net/dp/1933988274/ref=sr_1_1?ie=UTF8&qid=1310919437&sr=8-1

rkaregaran
  • 131
  • 2
0

Good that you asked :) So why TDD? Can you try answering the following questions to see if you can appreciate the reasons for TDD.

  1. The project is small. Does that mean, you know every bit of the logic behind the functionality that you have written and the reason why you have so coded?
  2. If your answer is yes, let us try taking it a step further by coupling the other fact that you stated - you're a lone developer in your project - USUALLY. Do you think you would always remember why you have a code logic in place every-time you look at it?
  3. Is your answer is yes, let us take it a little further. How long does it take for you to recollect the answer for the last question?

By this time, I hope you would get the picture. If you didn't, here is what it is - your test code would act as a supplementary that you can quickly refer, to find out why a particular logic is in place. This aids your productivity and would be a pleasant experience than the time-consuming and painstaking debugging sessions.

Apart from all these, the following are the other reasons as to why you practice TDD:

  1. TESTS COMPLIMENT THE CODE THAT YOU WRITE. It helps not just you at later point in time, but any developer who were to work on the project, to figure out the purpose of a particular piece of code.
  2. By writing tests you program defensively. There might be situations when you would want to re-factor code or add new functionality or fix some bug. It is the Test Suite that would (if written rightly) protect you from ill-fated bug that gets triggered by a condition, in production (which you would have for some reason not tested in you non-production environments). Its a nasty thing to get the bashings from all corners when something goes wrong in production and it usually does. How do we get it to ZERO occurrence as much as possible. GET DEFENSIVE. WRITE THE RIGHT SET OF TESTS.
  3. In the long run, defensive coding improves code quality and increases productivity.
  4. Tests are faster ways to routine manual testing based on some check list. Manual testing could be exploratory -testing unusual stuff. Your Unit Tests acts as Regression Suite to your Smoke Tests. You can refer the legendary person, Martin Fowler's Bliki on Unit Testing for more on this.
  5. If you do purist TDD - writing tests first and then the code, you will experience better understanding of what you are going to write and ensure that you write only so much code that is necessary. You'll also experience evolutionary design improvements that are driven by tests.
karthiks
  • 475
  • 2
  • 9
0

... my calculate price function (which can depend on things like the day of the week and bank holidays etc, so assume 20-40 lines for that function)

Given the volatile nature of the code (the pricing rules may be changed frequently - and so is the test), you want to make test cheap, but you should not skip testing altogether.

If more than one conditionals (if-then-else) can be true at any moment of time, focus on testing unexpected consequences of combinations of rules.

The purpose is to prevent your program from arriving at an absurd answer. While each pricing rule may be correct when judged alone, their combinations may produce unexpected results, such as applying a discount twice where it was forbidden, or turning a discount into an overcharge.

The way I would approach it is:

  • Make a table of "test cases" that you want to cover. The set of test cases should exercise every one of your price calculation rules, and also their combinations. Remove cases deemed repetitive or absurd.
  • For the remaining cases, try to calculate the price you would expect, by hand. You must exercise your best common sense at this point: if the pricing rules given to you by your manager arrives at an absurd answer, the rules might be wrong or that it is an unexpected consequence of some combinations of rules.
  • When you're done, run your test cases through your code. Print out the program output and see if there's any mistake.
  • If there is no mistake, convert your test program into a unit test by asserting the expected output values. This helps prevent bugs from future code changes.
rwong
  • 16,695
  • 3
  • 33
  • 81