testing abstract class

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

testing abstract class

Howard Oh
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?


Reply | Threaded
Open this post in threaded view
|

Re: testing abstract class

Chris Uppal-3
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


Reply | Threaded
Open this post in threaded view
|

Re: testing abstract class

Fernando Rodríguez
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


Reply | Threaded
Open this post in threaded view
|

Re: testing abstract class

Chris Uppal-3
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


Reply | Threaded
Open this post in threaded view
|

Re: testing abstract class

Fernando Rodríguez
Hello Chris,

> Just a little trick; quite neat, but not the only way to get the same
> effect.

Got it, thanks. :-)


Reply | Threaded
Open this post in threaded view
|

Re: testing abstract class

Randy A. Ynchausti-4
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


Reply | Threaded
Open this post in threaded view
|

Re: testing abstract class

Tim M
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


Reply | Threaded
Open this post in threaded view
|

Re: testing abstract class

Chris Uppal-3
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


Reply | Threaded
Open this post in threaded view
|

Re: testing abstract class

Howard Oh
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


Reply | Threaded
Open this post in threaded view
|

Re: testing abstract class

Howard Oh
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