I used test-driven-development followed by some re-factoring and ended up with a set of classes using composition to progressively increase type-specificity. For example:
- EventDispatcher -> TypedEventDispatcher -> FooEventDispatcher
- EventDispatcher -> TypedEven开发者_Go百科tDispatcher -> BarEventDispatcher
So EventDispatcher dispatches generic events (String, Number, Object), TypedEventDispatcher only dispatches events based on objects, and FooEventDispatcher only dispatches Foo events, BarEventDispatcher dispatches Bar events and so on.
The tests for all classes have identical behaviour, the only difference is the object type which is tested against. I have found that when I want to add a new event type I end up with the same test code as for the other classes.
This amount of duplication is obviously wrong. The resulting tests are quite fragile, as when I add a new feature to TypedEventDispatcher, I need to add the same tests to any objects which use it.
In Java, .NET, or Haxe I would be to create a generic type and test against that, however I am using ActionScript which does not support generics which is why I need a new class for each type to maintain type-safety.
As far as I can see, my options are:
- Keep create the same tests for each new class.
- Ignore the tests for derived classes, just create the new classes and assume they work.
Option #1 appears to the most "correct", but also results in fragile tests. Option #2 makes sense in that base object (TypedEventDispatcher) is tested so the derived classes should still work.
So my question is, what is the TDD philosophy for motivating creating new classes based on composition?
Update: To ask the question in a different way, is it normal to end up with duplicate test code when deriving functionality from existing code?
Update:
BaseEventDispatcher test:
class BaseEventDispatcherTest {
protected function test_dispatchEvent(dispatcher:Object, event:*):void {
dispatcher.dispatch(event);
assertEventDispatched(event)
}
}
TypedEventDispatcher test:
class TypedEventDispatcherTest extends BaseEventDispatcherTest {
[Test]
public function dispatchEvent_TypedEvent_should_dispatch_event():void {
var event:TypedEvent = new TypedEvent();
test_dispatchEvent(dispatcher, event);
}
}
FooEventDispatcher test:
class FooEventDispatcherTest extends BaseEventDispatcherTest {
[Test]
public function dispatchEvent_FooEvent_should_dispatch_event():void {
var event:FooEvent = new FooEvent();
test_dispatchEvent(dispatcher, event);
}
}
You know you can have test classes inherit from other test classes?
That would probably work well for your situation.
From your comments, it sounds like you are trying to test methods instead of behavior, which is something that can lead to a lot of duplication in tests.
Rather than having a set of tests like this:
- test EventDispatcher's addEventListener
- test EventDispatcher's removeEventListener
- test EventDispatcher's displatchEvent
- test TypedEventDispatcher's addEventListener
- ...
I would have a set of tests like this:
- test that dispatchEvent notifies listeners that have been added
- test that dispatchEvent does not notifiy listeners that have been removed
- test that removing a listener that was never added is silently ignored
Those behavioral tests are clearly features of EventDispatcher, and I wouldn't feel the need of duplicating them for TypedEventDispatcher, FooEventDispatcher, etc.
But I would want to test the unique features of these other dispatchers, so I might have additional tests like this:
- test that FooEventDispatcher does not notify listeners of Bar events
- test that TypedEventDispatcher does not notify listeners of String events
- ...
(Even better if you could describe those test without mentioning the names of classes, since the tests should be driving the design or your classes, rather than the classes driving the design of your tests.)
Of course, the goal isn't to test every possible combination, but only to test the actual features that I need to implement my software, and leave the rest unwritten until needed.
May I ask what your motivation for different types of dispatchers is ? It seems kind of redundant to separate their functionality when generally dispatchers don't care about what they are sending as long as it is an event.
If you are creating the same test for the different classes then they probably share some functionality, which would mean you could put said functionality higher up in the heirarchy and test only when you override.
精彩评论