Showing posts with label Test Driven Design (TDD). Show all posts
Showing posts with label Test Driven Design (TDD). Show all posts

Wednesday, January 7, 2009

Tests Drive Good Design

Test-first development leads to better design in the production code. Specifically, you get much lower coupling (both internally and externally), and you get much higher cohesion.

For those of you unfamiliar with those two terms, Coupling is tying one class to another. For instance, if ClassA relies on ClassB to do some work in such a way that a break in ClassB causes unit tests for ClassA to break, that's coupling. We try to limit coupling so that individual classes are isolated. We will always be coupling our classes to a degre (if java.lang.String breaks, everything breaks), but we try to keep it to a minimum. We want classes with low coupling because it is easier to isolate change.

Cohesion is how well the responsibilities within a class relate to each other. Highly cohesive classes tend to be small chunks that are easy to understand because they have a single responsibility. If you are tempted to answer the question, "What does this class do?" by using the word "and" a bunch, then you have a class with low cohesion. We want classes with high cohesion because they are easier to understand and thus easier to change.

When you write good unit tests, especially when you write them first, its easy to detect classes that are NOT highly cohesive and have low coupling. Tests become much more difficult to write when you stray from sound coding practices. If you weren't writing unit tests, you might be tempted to simply "new up" a dependency. Testing drives one to inject dependencies to create better seams and better class isolation.

The bottom line is that testable code is better code. It is easier to change, easier to understand, and overall easier to support.

Wednesday, May 28, 2008

Why Behavior Driven Development?

Two years ago, I became completely test-infected. I hate writing implementation code without writing the test first. Then, about six months ago, I was introduced to Behavior Driven Development (BDD). I liked what I saw, but I really didn't see any tools that I wanted to bring into our build system. The last thing that I wanted to introduce to the company was another test framework. JUnit was meeting our needs fairly well, and we were already using FitNesse on our team whereas the rest of the company was not. We didn't want to add another tool on top of that.

Then, a few months ago, I saw Scott Bellware give a talk on BDD and saw how he simply did BDD within a normal unit-testing framework and used .Net attributes to markup the tests with the BDD language. I liked what I saw. That night, I started a similar tool for JUnit called EasySpec. The tool still has some kinks to work out, but BDD has definitely brought some interesting insights into how we design our tests.

So, why BDD? At first, it was simply a trial of a style that seemed to do a good job of pulling out the Ubiquitous Language. Now that we have been using the techniques for about three months, I must say that I really like using BDD for creating software -- especially around the Domain Model.

BDD cleans up the language and gets the developers talking more about behaviors and less about implementation. Where, in the past, I might have been tempted to write a test with a method name like, "the_service_should_throw_an_exception_if_the_user_is_not_authenticated," I would now write that same test with a name like, "the_service_should_require_authenticated_users." Internally, the test would still be implemented in the same fashion, with probably the same code. However, language is important. It's important for the developers to think in the domain rather than hiding in the implementation layer. If I'm interested in the details of how the service requires authenticated users, then I can look at the details of the test (which should still be cleanly written) and see that in fact, the service will throw an exception if it somehow is handed an unauthenticated user.

This is merely one example. Keep a watch here for more information about BDD and why I'm hooked. Also, I should be putting some polish into EasySpec in the next week or two so that it's more user-friendly. If you're already using JUnit, and you want to try out BDD, take a look at EasySpec.

Friday, March 14, 2008

EasySpec in the works

I have started a new project named EasySpec for doing Behavior Driven Development in Java and Groovy. There is plenty of functionality that I want to add soon, but the core of it is available (as a .jar). The project page on GoogleCode can be found at http://code.google.com/p/easyspec/.

Our team has been using EasySpec for guiding our behavioral specifications for the last three months. I am hooked. If you want specific examples, leave some feedback here. I should be finishing up the current feature development within the next day or so. Then, I will be finishing some examples to be posted by the end of next week.

Monday, January 14, 2008

Mocking Groovy Objects with EasyMock in Java

I mentioned before that I needed to do some unit testing of a class that I was targeting in Groovy with come unit tests in Java. In Java, I am using EasyMock to mock out the collaborators. I am dealing with one collaborator, an interface, that looks like this (Groovy):


interface FileSystem {
...
def uploadFile(inputStream, destinationPath)
...
}

For the purposes of this unit test, I didn't care what got returned, so I setup the expectation like this (Java):

desintationFileSystem.uploadFile(streamForFile1, "someDir\\someOtherDir\\file1.jpg");

Running the test, I was greeted with this message:

java.lang.IllegalStateException: missing behavior definition
for the preceeding method call
uploadFile(EasyMock for class java.io.FileInputStream,
"someDir\someOtherDir\file1.jpg")
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:27)
...

That's when I remembered something fundamental to groovy... the 'def' type means variable return type. For Java, that translates into Object. To make the test run, I either needed to explicitly change the return type on the interface to be void, or simply setup the EasyMock expectation to return a value. Since I didn't want to change the interface, I chose to specify the return value in the expectation.

Friday, January 11, 2008

Why Do You Use TDD?

After about a year and a half of writing tests before implementation, I'm not sure I will ever go back. Most people call this "Test-Driven Development." However, given the benefits that come from working this way, I prefer the term, "Test-Driven Design."

I use TDD for three reasons - (1) Test-Driven Design forces more loosely coupled code which is much more malleable, (2) I can detect code smells much faster when classes become difficult to test, and (3) it is easier to know when I'm done writing code.

The tests are a nice bonus product of designing code this way, but they are, in fact, not as important to me as the loose coupling and general good design that comes out the other end. When writing tests becomes too difficult, noisy, or lengthy, then something is wrong with the design. Since tests are in place, refactoring to a better design is easier and generally faster. So, this too aids in improving the design of the implementation.

Do the tests get in the way sometimes? Yes. However, this is often a test smell. Perhaps the tests have too much intimate knowledge of the implementation.

If you have not tried writing tests first, I suggest giving it a go on a simple project. Better yet, find someone that is already experienced in TDD and do some pair programming.

Now, go forth and write tests...

Tuesday, October 30, 2007

Programmers Anonymous: Confessions of a Terrible Software Developer

Marc has posted his "Confessions of a Terrible Programmer." The overriding tone of the post can be summed up in the following pseudo-Zen quotes:


You will never become a Great Programmer until you acknowledge that you will always be a Terrible Programmer.

and,

You will remain a Great Programmer for only as long as you acknowledge that you are still a Terrible Programmer.

Marc does a very good job of stating how he overcomes his "terribleness" to provide working software. According to Marc, his solutions are doing a good job of hiding the fact that he is a terrible developer. However, I believe that agile practices present different solutions to these problems. In broad terms, Marc favors failing fast where I favor multiple levels of testing, Test-Driven Design, and the fast feedback loops provided by good test coverage coupled with Continuous Integration.

To address more specifics, I have provided a summary of Marc's solutions along with where I think agile solves these in a different way.

  1. Marc says he favors strong typing to prevent problems. Having done most of my work in static languages (Java, C#), and some in dynamic language (Groovy), at this point I prefer solid unit test coverage (100% with excuses). Test-first design helps with this too. Once I have good test coverage, those tests are unearthing the same problems that the compiler would. With the dynamic languages, I find the same errors a bit later, but I get the benefit of code that I find to be much easier to read.

  2. Marc favors programming assertions. Marc is a paranoid programmer. He will assert that something is not null even when he controls both sides of the interface - just in case he might change something later. I personally find that assertions and paranoid programming in general fall under the related headings of YAGNI and nosiy code. Instead, I prefer solid unit testing and a tester that knows how to unearth the edge cases. Write unit tests that assert that the service in question does not return null to box in the behavior.

  3. Marc says he will, "ruthlessly try to break [his] own code." It appears that Marc is trying to accomplish through developer testing what should be done by an actual tester. While I agree that developers should be generating tests that give great code coverage, it is a waste of time to make them switch hats and become a tester for their own code. Hire a tester. They think differently from developers. Their concerns are different.

  4. Marc favors code reviews. I favor pair programming. Both provide feedback, but I want my feedback while I'm "in the zone." I want my feedback immediately. I don't want you to sit back and wait for me to find my own bugs. If you see something, tell me. Tell me as soon as it looks like I've finished typing or as soon as it looks like I'm looking for the bug. This way, I can fix the problem without having to make the context switch to come back to it later. Plus, the quality of the review is better since the other developer should be equally engaged in the generation of the code while it is being generated.


To be honest, our team is not able to follow all of these guidelines at the moment. Our biggest problem is not having a dedicated software tester embedded with the team. We ARE having to generate the types of tests that a dedicated tester should be doing. And, we are missing some things that a tester would catch much earlier in the development process. I feel that this missing component of our team IS hurting our velocity.

Am I a terrible programmer? Yes. If you find me stating otherwise, please redirect me to some of my own code - something that I wrote yesterday should do just fine.

What are you doing to hide the fact that you're a terrible developer? I would love to hear.

Monday, August 13, 2007

Throw Away Code Must Be Thrown Away

From time to time, it’s advantageous to take off my TDD hat a fling a small bit of code. I’ve found this is the quickest way to gain a bit of confidence in working with libraries that I haven’t touched before. This allows me to be sure that I know how to interface with the functionality that I need. After a quick proof-of-concept, I’ll know what parameters and classes are needed to get what I want.

My own personal problem with this has been developing the discipline to remove the code from the system once I’ve figured out what I’m after. Why do I think I need to discipline myself to do this? Simply put, my worst code is always the code that wasn’t written “test-first.” The spikes that I pull over into production code often cause problems when trying to get them under test coverage.

Conversely, I’ve found that following Test Driven Development yields code that is easier to test and frankly, better designed. TDD prevents a great deal of speculative development and over design. Therefore, it’s much better to step back from the initial spike and start over by writing tests.

I have found it much easier to prevent the spikes from getting attached to the project by putting them in a completely separate class. Name it “class ThrowAway” to help yourself remember.

Bottom line: Throw-away code must be thrown away.