views:

274

answers:

6

For TDD you have to

  1. Create a test that fail
  2. Do the simplest thing that could possible work to pass the test
  3. Add more variants of the test and repeat
  4. Refactor when a pattern emerge

With this approach you're supposing to cover all the cases ( that comes to my mind at least) but I'm wonder if am I being too strict here and if it is possible to "think ahead" some scenarios instead of simple discover them.

For instance, I'm processing a file and if it doesn't conform to a certain format I am to throw an InvalidFormatException

So my first test was:

@Test 
void testFormat(){
    // empty doesn't do anything nor throw anything
    processor.validate("empty.txt"); 
    try {
        processor.validate("invalid.txt");
        assert false: "Should have thrown InvalidFormatException";
    } catch( InvalidFormatException ife ) {
        assert "Invalid format".equals( ife.getMessage() );
    }
 }

I run it and it fails because it doesn't throw an exception.

So the next thing that comes to my mind is: "Do the simplest thing that could possible work", so I :

public void validate( String fileName ) throws InvalidFormatException {
    if(fileName.equals("invalid.txt") {
        throw new InvalidFormatException("Invalid format");
    }
}

Doh!! ( although the real code is a bit more complicated, I found my self doing something like this several times )

I know that I have to eventually add another file name and other test that would make this approach impractical and that would force me to refactor to something that makes sense ( which if I understood correctly is the point of TDD, to discover the patterns the usage unveils ) but:

Q: am I taking too literal the "Do the simplest thing..." stuff?

+2  A: 

Lots of comments:

  • If validation of "empty.txt" throws an exception, you don't catch it.

  • Don't Repeat Yourself. You should have a single test function that decides if validation does or does not throw the exception. Then call that function twice, with two different expected results.

  • I don't see any signs of a unit-testing framework. Maybe I'm missing them? But just using assert won't scale to larger systems. When you get a result from validation, you should have a way to announce to a testing framework that a given test, with a given name, succeeded or failed.

  • I'm alarmed at the idea that checking a file name (as opposed to contents) constitutes "validation". In my mind, that's a little too simple.

Regarding your basic question, I think you would benefit from a broader idea of what the simplest thing is. I'm also not a fundamentalist TDDer, and I'd be fine with allowing you to "think ahead" a little bit. This means thinking ahead to this afternoon or tomorrow morning, not thinking ahead to next week.

Norman Ramsey
1.- empty.txt it doesn't that was my -1 base case, and I did nothing :P 2.- Got it. 3.- I'm using TestNG. 4.- In mine too, but that's what I did for *"simplest thing that ..."* ( I was thinking ahead the next second :I ) Thank Norman +1
OscarRyz
+2  A: 

You missed point #0 in your list: know what to do. You say you are processing a file for validation purposes. Once you have specified what "validation" means (hint: do this before writing any code), you might have a better idea of how to a) write tests that, well, test the specification as implemented, and b) write the simplest thing.

If, e.g., validation is "must be XML", your test case is just some non-xml-conformant string, and your implementation is using an XML library and (if necessary) transform its exceptions into those specified for your "validation" feature.

Arne
+1 Funny enough, that's what I consider the first principle of programming : http://stackoverflow.com/questions/159176/what-do-you-consider-the-1st-principles-of-programming/159571#159571
OscarRyz
+3  A: 

Of course your interpretation of the rule is too literal. It should probably sound like "Do the simplest potentially useful thing..."

Also, I think that when writing implementation you should forget the body of the test which you are trying to satisfy. You should remember only the name of the test (which should tell you about what it tests). In this way you will be forced to write the code generic enough to be useful.

Rotsor
+7  A: 

I think your approach is fine, if you're comfortable with it. You didn't waste time writing a silly case and solving it in a silly way - you wrote a serious test for real desired functionality and made it pass in - as you say - the simplest way that could possibly work. Now - and into the future, as you add more and more real functionality - you're ensuring that your code has the desired behavior of throwing the correct exception on one particular badly-formatted file. What comes next is to make that behavior real - and you can drive that by writing more tests. When it becomes simpler to write the correct code than to fake it again, that's when you'll write the correct code. That assessment varies among programmers - and of course some would decide that time is when the first failing test is written.

You're using very small steps, and that's the most comfortable approach for me and some other TDDers. If you're more comfortable with larger steps, that's fine, too - but know you can always fall back on a finer-grained process on those occasions when the big steps trip you up.

Carl Manaster
Very pragmatic as always. If you were to blog about testing, you would have at least one subsriber.
Lieven
+1  A: 

Like a method should do one thing only, one test should test one thing (behavior) only. To address the example given, I'd write two tests, for instance, test_no_exception_for_empty_file and test_exception_for_invalid_file. The second could indeed be several tests - one per sort of invalidity.

The third step of the TDD process shall be interpreted as "add a new variant of the test", not "add a new variant to the test". Indeed, a unit test shall be atomic (test one thing only) and generally follows the triple A pattern: Arrange - Act - Assert. And it's very important to verify the test fails first, to ensure it is really testing something.

I would also separate the responsibility of reading the file and validating its content. That way, the test can pass a buffer to the validate() function, and the tests do not have to read files. Usually unit tests do not access to the filesystem cause this slow them down.

philippe
+2  A: 

I too am a TDD newbie struggling with this question. While researching, I found this blog post by Roy Osherove that was the first and only concrete and tangible definition of "the simplest thing that could possibly work" that I have found (and even Roy admitted it was just a start).

In a nutshell, Roy says:

Look at the code you just wrote in your production code and ask yourself the following:

“Can I implement the same solution in a way that is ..”

  1. “.. More hard-coded ..”
  2. “.. Closer to the beginning of the method I wrote it in.. “
  3. “.. Less indented (in as less “scopes” as possible like ifs, loops, try-catch) ..”
  4. “.. shorter (literally less characters to write) yet still readable ..”

“… and still make all the tests pass?”

If the answer to one of these is “yes” then do that, and see all the tests still passing.

SCFrench