?

Log in

No account? Create an account
current entries friends' entries archives about me Previous Previous Next Next
Technical Life - cellophane — LiveJournal
the story of an invisible girl
renniekins
renniekins
Technical Life
read 16 comments | talk to me!
Comments
specialagentm From: specialagentm Date: March 31st, 2008 04:44 am (UTC) (Link)
I'm not sure why TDD would have to be linked to any sort of heavyweight design. The one thing that interfaces (as a general concept -- separate what you want to do from who does it and how they do it) helps with TDD is that you can freely pull out the parts of the app you don't want to test in a unit test and use "mock" objects. Then you test the real code of one piece, and fake out the rest so that the real code doesn't care, and you can just interrogate/instrument that.

TDD is wonderful. It gives you a confidence in your code that really does help in speeding up development. If you pass the tests, at least you know within the scope of what you're testing, nothing broke. It's been very rare that I've seen a test break and found out it was the test that was wrong, not my code. Most of the time, running the JUnit tests and seeing all green gets me revved up to dig in and code some more.

Oh, by the way, your ideas of test systems filling in alternate versions -- some frameworks do support that. They'll write mock objects for you, or provide an easy way to produce those. I haven't found those useful to my work yet, but they're out there.
pstscrpt From: pstscrpt Date: April 1st, 2008 01:12 pm (UTC) (Link)
The one thing that interfaces... helps with TDD is that you can freely pull out the parts of the app you don't want to test in a unit test and use "mock" objects.
You say "helps" like there's another way to do it. Is there?
renniekins From: renniekins Date: April 1st, 2008 01:20 pm (UTC) (Link)
You can subclass something you want to mock, overriding methods. You can subclass the class being tested, overriding methods that call the class you don't want to touch.

Oh and EasyMock is a cool tool for building mocks out of interfaces very rapidly with little code.
pstscrpt From: pstscrpt Date: April 1st, 2008 01:36 pm (UTC) (Link)
That's the same idea, though, isn't it? And you'd still need to design your system to use factories everywhere to supply the mock versions when appropriate. Unless maybe the constructors looked for a test flag, but then you're having the main class server as a mock...

I dunno. TDD is probably worth all that when you get the hang of it (don't get me wrong, I totally understand the benefits). It just seems like the accepted best practices are getting to be seriously at odds with the fundamental natures of the languages. Maybe when you declare an object reference it should have to be typed as an interface and not a class. Maybe New() should be a static method on the class that can see what test you're running (annotations?) and doesn't have to return the type you said. Maybe first-class functions could be a cleaner way of providing test functionality than programming to interfaces...
specialagentm From: specialagentm Date: April 1st, 2008 01:57 pm (UTC) (Link)
One definition of subtypes in OO [1] is that you should be able to freely substitute a subtype for any instance of its supertype, and the caller should not know the difference. Different behaviors may result, but the message-passing, flow of control, and correctness is identical.

All that factories or dependency injection does is pull that up one more level and let you make a decision to, application-wide, swap in and out different implementations without having to change every single call to new(). Some very dynamic languages like Lisp or Ruby let you do this directly (I can, at runtime, redefine class Foo to be some other piece of code entirely). Less smart languages like Java need a little help, but you can write a factory class or static factory method pretty quickly, and a global search and replace to change:

Foo bar = new Foo();

to this:

Foo bar = FooFactory.fetchMeAFoo();

is pretty easy too.

So, yes... upfront work is needed. But this doesn't just drive TDD, it drives a lot of other pieces of flexibility that are often useful. For us, use of factories means we can write core code that doesn't care where it's running at, and how it gets to the data it needs -- in some cases, it has local database access. In other cases, it has to fetch the data over a Web Service to someone else who has DB access. In the TDD case, it's using fake in-memory data.

I'm not trying to sell anything, just trying to show the full picture of why these practices are followed. I'll admit that the way you implement these concepts could be more elegant, but the dirty bits are usually all quite well hidden once you've got it all setup.

[1] http://en.wikipedia.org/wiki/Liskov_substitution_principle
specialagentm From: specialagentm Date: April 1st, 2008 01:40 pm (UTC) (Link)
No, I can't imagine TDD without some sort of implementation-hiding, and using interfaces or writing TDD-centric subclasses seems like the simplest route.

If you can't change the run-time behaviors of the code chunk, you need to mock out some other piece of the architecture -- so if you can't isolate a database dependency (and use an in-memory data set or something), maybe what you can do is point to a local database that you can reload with fake data suitable to each test.

To me, interfaces / subclasses aren't overhead -- they fall out of the object design anyway, I already had lots of other reasons to want to have that sort of flexibility. I try not to go overhead, though -- usually, other aspects of agile (constant refactoring, pairing) helps me adjust the object model to hit that sweet spot of "just right".
read 16 comments | talk to me!