how do you test an abstract class?
Abstract it maybe, it can be written to go illogical. For me, I make one concrete class to test the abstract one. Is there a better way? |
Howard Oh wrote:
> For me, I make one concrete class to test the abstract one. > Is there a better way? Why test an abstract class at all ? Since its only purpose is to have concrete subclasses, why not just test them instead ? The way I'd arrange it would be to have an abstract test class and one or more concrete subclasses of that test class corresponding to the concrete subclasses of the original class. The test cases for testing the shared functionality (the abstract class) would live in the abstract test class; and the subclass-specific tests would live in the test subclasses. Since the abstract test class /is/ abstract (the class object answers true to #isAbstract) the SUnlit framework won't attempt to run the shared tests unless you are actually testing one of the concrete subclasses. (That's a pattern I have used several times; for instance in the tests included with my 'ZKit' if you want to take a look at them.) BTW, my preferred implementation of MyAbstractTestClass class>>isAbstract is isAbstract ^ self = ##(self). which answers true for that class, but subclasses (unless they override it) will answer false. -- chris |
Hello Chris,
> > BTW, my preferred implementation of MyAbstractTestClass > class>>isAbstract is > > isAbstract > ^ self = ##(self). > which answers true for that class, but subclasses (unless they > override it) will answer false. I don'tget it, could you elaborate? Thanks |
Fernando,
> > isAbstract > > ^ self = ##(self). > > which answers true for that class, but subclasses (unless they > > override it) will answer false. > > I don'tget it, could you elaborate? At the risk of going into /too/ much detail... In a class-side method: self evaluates (as always) to the receiver. So in the class that defines the method: self evaluates to that class, but in a subclass that inherits (does not override) that method: self will evaluate to the subclass. However: ##(self) is a compile-time expression. It is evaluated by the compiler and the result is embedded directly into the method. If you had a method with: ##(2 + 3) the value 5 would be embedded into the method. In the case of ##(self) there isn't actually a "receiver" in the normal sense, but the compiler arranges (conveniently) that the name "self" is bound to the class that owns the method (this is true for both class-side and instance-side methods). So if in class A I have a class-side method: isAbstract ^ ##(self) = self. that will compare the class object A with the actual receiver of the message (perhaps a subclass of A). So the method will answer true if it is sent to A, but false if it is sent to any subclass of A. Just a little trick; quite neat, but not the only way to get the same effect. -- chris |
Hello Chris,
> Just a little trick; quite neat, but not the only way to get the same > effect. Got it, thanks. :-) |
In reply to this post by Howard Oh
Howard,
> how do you test an abstract class? > > Abstract it maybe, it can be written to go illogical. > > For me, I make one concrete class to test the abstract one. > Is there a better way? This is the way I do it also. Some may say that you should not test an abstract class at all. I agree to the point that all of the behaviors of the abstract base class are abstract also. However, this is usually not the case in practice. Implementing a completely abstract base class is equivalent to defining an interface. An interface (even one implementing as an abstract base class) does not need to be tested because there is no behavior defined. Therefore, assuming that there is some behavior implemented in the abstract base class, it is more efficient to unit-test that behavior of the abstract class once, than to duplicate the tests on all of the classes that inherit that behavior. Naturally, I also unit-test that behavior when it is overridden in the classes that inherit from the abstract base class. Regards, Randy |
In reply to this post by Howard Oh
I would say don't use an abstract class...
Sounds flippant - but the issue is, you end up writing identical tests for every subclass to make sure that nothing was broken (or even more painfully, have abstract tests that you also inherit which only leads to a soup of tests that are hard to follow) Sure you can test one subclass, but that doesn't guarantee that one of the other subclasses didn't accidently override some method and cause an error. So what are you to do? Well the better approach (becuase inheritance is actually far too overrated... Dave Thomas (Bedara) taught me this in my early Comp-Sci classes and it took me years to finally understand what he was talking about)... is to use composition instead! So really think about that behevior you are inheriting, can you put it in another class and then delegate to that other class to write your tests? Chances are, if its important enough for you to test then it cleaner to test it separately in isolation. Finally, your other classes (what were subclasses) can probably get ore interesting tests as well - e.g. did you call the now separated component to determine some value. This is where the MockObject papers come from. Tim |
TimM wrote:
> you end up writing identical tests > for every subclass to make sure that nothing was broken (or even more > painfully, have abstract tests that you also inherit which only leads > to a soup of tests that are hard to follow) Writing identical tests is obviously an idiotic thing to do, but using inheritance (of behaviour) to share test code between related tests works very well for me. No soup at all. (The pattern is useful for other situations too, it's not limited to testing concrete subclasses of an abstract parent -- e.g. if an object can be used in more than one mode.) It's important to realise that the /test/ isn't shared; it's the code /for/ the test that is shared. -- chris |
In reply to this post by Randy A. Ynchausti-4
I'm trying test on a concrete class created on test-time.
Like this... testTableCreation |testClass| testClass := #DbObjectForTesting. DbObject subclass: testClass instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' classInstanceVariableNames: ''. self assert: (Smalltalk includesKey: testClass asString). self assert: (Smalltalk at: testClass asString) blabal = true tearDown |testClass| testClass := #DbObjectForTesting. (Smalltalk at: testClass asString) removeFromSuper |
In reply to this post by Randy A. Ynchausti-4
I'm trying run-time concrete class creation and test on it.
Like this... testTableCreation |testClass| testClass := #DbObjectForTesting. DbObject subclass: testClass instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' classInstanceVariableNames: ''. self assert: (Smalltalk includesKey: testClass asString). self assert: (Smalltalk at: testClass asString) blabal = true tearDown |testClass| testClass := #DbObjectForTesting. (Smalltalk at: testClass asString) removeFromSuper |
Free forum by Nabble | Edit this page |