Testing Interface contract

5 views
Skip to first unread message

Damokles

unread,
Nov 8, 2009, 4:03:34 PM11/8/09
to Easyb Users
Testing with easyb look really simple, but what about testing the
interface contract?
What I mean is: I write my stories/scenarios for the interface. When I
execute the test I
need to somehow inject the actual class under test. If I have only one
class that implements
this interface i can do it directly in the code but what about if I
have four classes that implement
the interface? How do I tell easyb to run the story with four
different classes?

Damokles

unread,
Nov 10, 2009, 9:01:33 PM11/10/09
to Easyb Users
Maybe I should elaborate further.
Lets say we want to define stories for ArrayList testing the list
behavior.
So we write the stories for ArrayList but then we want to test
LinkedList too, so what do we do? Copy the stories from ArrayList and
replace new ArrayList() with new LinkedList()? Wouldn't it make more
sense to write the stories for the List interface since it is the
behavioral contract? Now we just need to apply the stories for the
different List implementations.

Andy

unread,
Nov 10, 2009, 10:01:44 PM11/10/09
to Easyb Users
There's a few way to do this - easyb stories support pretty much
everything you can do in normal objects; hence, you can loop easily.
So in your case, if you'd like to test two different implementations
of an interface, you could do this:

[new ArrayList(), new LinkedList()].each { list ->

scenario "using the list interface", {
given "a list impl", {
lst = list
}
when "something is put inside", {
lst << "test"
}
then "it should support obtaining items", {
lst[0].shouldBe "test"
}
}
}

This'll execute 2 scenarios. Note, you'll need to actually assign the
parameter name to any parent closure in a script -- using "it" won't
work as there are multiple sub closures (you'll end up w/NPEs). Having
done this example, I would probably look into some sort of factory
pattern depending on how flexible you'd like to make the plug-and-play-
ness of implementation type testing.
Message has been deleted

Jeffrey Erikson

unread,
Nov 11, 2009, 7:44:28 AM11/11/09
to easyb...@googlegroups.com
I'm a relative newbie to both this group and easyb, so take the
following for what it's worth:

My first thought was similar to what Andy posted: iterate through a
collection of different List implementations. The more I thought
about it, though, the more I realized that this might be a place where
easyb might be pointing out a potential weakness in our design.

Assuming that we have an interface with a number of classes that
implement that interface, I would think that if the behavior in the
implementations is the same (i.e. the scenarios test *exactly* the
same thing for each implementation with *exactly* the same outcomes),
then we might want to refactor that behavior into an abstract class
from which those implementations now inherit. We can then write a
story to test the behavior of that abstract class, with separate
stories (sequels?) for the classes that inherit from that abstract
class. At that point, if a class inherits behavior from an abstract
class, then you only need to test the behavior in the story for the
abstract class and not really worry about which implementation you're
using, provided it doesn't actually override the behavior in the
abstract class. If a class *does* override the behavior in the
abstract class, then that behavior would be tested in the story for
the inheriting class, along with any other behavior it provides.

Taking List as an example: ArrayList and LinkedList don't directly
implement List (i.e. they don't provide the definitions for all the
required methods themselves). Instead, most of their behavior is the
same and is actually inherited from AbstractList which implements the
List interface. The only public abstract method in AbstractList is
get(). So I would propose that we might have a single story that
tests the behavior of AbstractList, then following stories that test
the specific behavior of get() in ArrayList and LinkedList (which
actually lies a bit further down in the inheritance tree so might test
other things as well).

I wrote in a blog post a few days ago, that easyb not only helps the
customers/stakeholders understand what's being tested, but also helps
me see how the pieces of my projects work together. I think this is
one more place where easyb is a great aid in seeing how we might
design our projects, too. Could we say BDD is also "behavior driven
design" (but then, I would hope most design "behavior-driven")?

je

Andrew Glover

unread,
Nov 11, 2009, 9:55:20 AM11/11/09
to easyb...@googlegroups.com
yeah, well put -- other options for going down this route are using features like shared stories and to an extent, fixtures too.

On Wed, Nov 11, 2009 at 8:55 AM, Jeffrey Erikson <jeffrey...@gmail.com> wrote:

[I've had some network issues so forgive me if this posts twice]



--
Sincerely,

Andrew Glover
703.577.0830 (cell)

Damokles

unread,
Nov 11, 2009, 9:49:20 AM11/11/09
to Easyb Users
Thanks Andy I didn't realize you could add code around the stories.
Could I somehow have just the List.story and then have an
ArrayList.story that calls List.story and passes a factory closure in?
Maybe call it List.template since it can't be run without an
Implementation.

Jeffrey, I don't think that it makes much sense to write the test for
the abstract class, if you have an interface, because you can't test
it since it's abstract and relies on the implemetation of the
extending class. If you look more closely you see that there is more
not implemented in AbstractList then get. Take a look at the
UnsupportertedOperationExceptions, and you'll see that there is much
more missing. I still think the interface ist the right place for the
stories, as it defines the contract for the implementing classes. I
sure hope this applies for your interface related tests "(i.e. the
scenarios test *exactly* the same thing for each implementation with
*exactly* the same outcomes)", otherwise your implementation would
violate the Liskov substitution principle.

Andrew Glover

unread,
Nov 11, 2009, 10:02:35 AM11/11/09
to easyb...@googlegroups.com
On Wed, Nov 11, 2009 at 9:49 AM, Damokles <lord_d...@gmx.net> wrote:

Thanks Andy I didn't realize you could add code around the stories.
Could I somehow have just the List.story and then have an
ArrayList.story that calls List.story and passes a factory closure in?
Maybe call it List.template since it can't be run without an
Implementation.

At this point, the answer is no -- stories are single entities and if you'd like to combine them in some way, you'd need to create an actual import-able object to handle this for you. The idea of including other stories is definitely on the chopping block though as it has come up on more than one occasion.
In some respects, this is the same issue with JUnit (except you can do inheritance to realize it); that is, without using inheritance, it's not exactly possible to "call" another separate set of tests. TestNG has a wonderful feature in this case though -- it supports test dependencies (something, back in the day at least, the JUnit crowd explicitly frowned upon as JUnit has always been a "unit" testing framework) -- I've always been a big fan of TestNG (I'm using it still!) and a story dependency notion, if implemented in easyb would probably look similar.  


Jeffrey, I don't think that it makes much sense to write the test for
the abstract class, if you have an interface, because you can't test
it since it's abstract and relies on the implemetation of the
extending class. If you look more closely you see that there is more
not implemented in AbstractList then get. Take a look at the
UnsupportertedOperationExceptions, and you'll see that there is much
more missing. I still think the interface ist the right place for the
stories, as it defines the contract for the implementing classes. I
sure hope this applies for your interface related tests "(i.e. the
scenarios test *exactly* the same thing for each implementation with
*exactly* the same outcomes)", otherwise your implementation would
violate the Liskov substitution principle.


Jeffrey Erikson

unread,
Nov 11, 2009, 10:33:40 AM11/11/09
to Easyb Users
On Nov 11, 9:49 am, Damokles <lord_damok...@gmx.net> wrote:
>
> Jeffrey, I don't think that it makes much sense to write the test for
> the abstract class, if you have an interface, because you can't test
> it since it's abstract and relies on the implemetation of the
> extending class.

Sure, it's testable . . . you just need to stub out an internal test
class with the abstract methods implemented with "throw new
UnsupportedOperationException()" or something along those lines. At
that point, you can test the behavior that the abstract class *does*
implement.

> If you look more closely you see that there is more
> not implemented in AbstractList then get.

Remember, I only said that get() was the only method marked "public
abstract" . . . even so . . .

> Take a look at the
> UnsupportertedOperationExceptions, and you'll see that there is much
> more missing. I still think the interface ist the right place for the
> stories, as it defines the contract for the implementing classes. I
> sure hope this applies for your interface related tests "(i.e. the
> scenarios test *exactly* the same thing for each implementation with
> *exactly* the same outcomes)", otherwise your implementation would
> violate the Liskov substitution principle.

Taking List as an example again: you'll notice that there are a number
of methods that have "(optional operation)" in the List interface
javadoc (the UnsupportedOperationExceptions you mention). If a
"contract" has "optional" in it, then you're potentially not going to
get the same behavior out of classes that implement the same
interface. That's behavior that would be specific to the sub-class,
and should (and probably can) *only* be tested for that sub-class. If
you were to have an "interface test" and you were testing an optional
method, how would you define the outcome for two implementations where
one implemented real behavior in the method and one didn't? If the
Liskov substitution principle says (as you seem to be implying above)
that we need to have the same outcomes for the same method defined in
an interface, then I'd say that Java's List contract and
implementations provide the potential to violate that principle. In
other words, one List implementation might implement the optional clear
() method with real behavior, for example, and one might just throw
the UnsupportedOperationException; another might just do nothing
without saying a word. They all fulfill the "contract", but they all
result in radically different outcomes and none of them can really be
substituted for another if we're depending on clear() actually doing
something useful.

je
Reply all
Reply to author
Forward
0 new messages