How to unit test abstract classes: extend with stubs?
Asked Answered
P

14

520

I was wondering how to unit test abstract classes, and classes that extend abstract classes.

Should I test the abstract class by extending it, stubbing out the abstract methods, and then test all the concrete methods? Then only test the methods I override, and test the abstract methods in the unit tests for objects that extend my abstract class?

Should I have an abstract test case that can be used to test the methods of the abstract class, and extend this class in my test case for objects that extend the abstract class?

Note that my abstract class has some concrete methods.

Percipient answered 28/10, 2008 at 13:25 Comment(2)
It's best not to unit test abstract classes directly: enterprisecraftsmanship.com/posts/…Verbify
What if you have like 20 or 30 methods that will have exact same implementation in all subclasses? You should still duplicate tests in all of them? That doesn't make much senseZsazsa
L
521

There are two ways in which abstract base classes are used.

  1. You are specializing your abstract object, but all clients will use the derived class through its base interface.

  2. You are using an abstract base class to factor out duplication within objects in your design, and clients use the concrete implementations through their own interfaces.!


Solution For 1 - Strategy Pattern

Option1

If you have the first situation, then you actually have an interface defined by the virtual methods in the abstract class that your derived classes are implementing.

You should consider making this a real interface, changing your abstract class to be concrete, and take an instance of this interface in its constructor. Your derived classes then become implementations of this new interface.

IMotor

This means you can now test your previously abstract class using a mock instance of the new interface, and each new implementation through the now public interface. Everything is simple and testable.


Solution For 2

If you have the second situation, then your abstract class is working as a helper class.

AbstractHelper

Take a look at the functionality it contains. See if any of it can be pushed onto the objects that are being manipulated to minimize this duplication. If you still have anything left, look at making it a helper class that your concrete implementation take in their constructor and remove their base class.

Motor Helper

This again leads to concrete classes that are simple and easily testable.


As a Rule

Favor complex network of simple objects over a simple network of complex objects.

The key to extensible testable code is small building blocks and independent wiring.


Updated : How to handle mixtures of both?

It is possible to have a base class performing both of these roles... ie: it has a public interface, and has protected helper methods. If this is the case, then you can factor out the helper methods into one class (scenario2) and convert the inheritance tree into a strategy pattern.

If you find you have some methods your base class implements directly and other are virtual, then you can still convert the inheritance tree into a strategy pattern, but I would also take it as a good indicator that the responsibilities are not correctly aligned, and may need refactoring.


Update 2 : Abstract Classes as a stepping stone (2014/06/12)

I had a situation the other day where I used abstract, so I'd like to explore why.

We have a standard format for our configuration files. This particular tool has 3 configuration files all in that format. I wanted a strongly typed class for each setting file so, through dependency injection, a class could ask for the settings it cared about.

I implemented this by having an abstract base class that knows how to parse the settings files formats and derived classes that exposed those same methods, but encapsulated the location of the settings file.

I could have written a "SettingsFileParser" that the 3 classes wrapped, and then delegated through to the base class to expose the data access methods. I chose not to do this yet as it would lead to 3 derived classes with more delegation code in them than anything else.

However... as this code evolves and the consumers of each of these settings classes become clearer. Each settings users will ask for some settings and transform them in some way (as settings are text they may wrap them in objects of convert them to numbers etc.). As this happens I will start to extract this logic into data manipulation methods and push them back onto the strongly typed settings classes. This will lead to a higher level interface for each set of settings, that is eventually no longer aware it's dealing with 'settings'.

At this point the strongly typed settings classes will no longer need the "getter" methods that expose the underlying 'settings' implementation.

At that point I would no longer want their public interface to include the settings accessor methods; so I will change this class to encapsulate a settings parser class instead of derive from it.

The Abstract class is therefore: a way for me to avoid delegation code at the moment, and a marker in the code to remind me to change the design later. I may never get to it, so it may live a good while... only the code can tell.

I find this to be true with any rule... like "no static methods" or "no private methods". They indicate a smell in the code... and that's good. It keeps you looking for the abstraction that you have missed... and lets you carry on providing value to your customer in the mean time.

I imagine rules like this one defining a landscape, where maintainable code lives in the valleys. As you add new behaviour, it's like rain landing on your code. Initially you put it wherever it lands.. then you refactor to allow the forces of good design to push the behaviour around until it all ends up in the valleys.

Libertinism answered 1/6, 2010 at 7:1 Comment(31)
This is a great answer. Much better than the top rated. But then I guess only those who really want to write testable code would appreciate it.. :)Cu
I can't get over how good an answer this actually is. It's totally changed my way of thinking about abstract classes. Thanks Nigel.Cu
In your second scenario, where you inject the 'helper' class, do you then expose the common methods via composition?Kershaw
I've expanded the answer to try to cover your case. Does this make sense? Also.. if you are using dependency injection, then you may find that once you have the derived classes converted to strategies you may find that you could be injecting that interface directly into your caller so it doesn't needs the base class to act as a pass through.Libertinism
Oh no.. another tenet I'm gong to have to rethink! Thanks (both sarcastically for now, and non-sarcastically for once I have assimilated it and feel like a better programmer)Wellinformed
I would love to see an example of the second scenario. I have a couple of these abstract classes that I think could benefit from that change, but I am not sure how to implement what you are talking about.Melinamelinda
Nice answer. Definitely something to think about... but doesn't what you are saying basically boil down to don't use abstract classes?Millard
Abstract classes are often better than interfaces when your code will be built upon by third parties - interfaces cannot be modified after the fact in most languages that have them. The proliferation of types generated by this approach can also harm versioning because it creates a bigger surface area for modification. Third parties, whose code you can't control, may be depending on what you see as internal details (e.g. motors) of what you think you're shipping (e.g. cars). I would say this approach is what Java and languages like it force you to use to get testability.Maiamaiah
Abstract classes are often better than interfaces when your code will be built upon by third parties - Why?Libertinism
Your code should be structured in layers. If you are exposing an API, it should be just that, a dedicated layer for 3rd parties to rely on. You don't want 3rd parties coupling their code to your internal design.Libertinism
+1 for the Rule alone, "Favor complex network of simple objects over a simple network of complex objects."Mcclelland
@NigelThorne: love the approach. Lots of meaning in few words. Does that means that you can get rid off abstract classes always? Are there still valid situations?Jairia
@SoMoS I was going to say that abstract classes might be useful for exposing an API so others could derive from them and have helper methods available, or to ensure callbacks happen in the correct order... but then when thinking though the alternatives I couldn't justify it. To enforce order, I should be implementing an interface that someone else calls into. For helper methods provide a helper object or enrich your domain objects. short answer... noLibertinism
Using your car analogy, what if you want to allow others to change more than the motor? Lets say I want to allow others to create 2 door and 4 door and 4 wheel drive and front wheel drive and rear wheel drive and leather seats, etc. I see how to do this with abstractions, but with a concrete "Car" implementation, I end up having to use something like ICarFeatures with really wierd name and not clear what its meaning is.Referee
@Referee - In reality you would have a chassee that your car parts would fit on. Not all car parts are interchangeable... You can't use a door to steer... so you have specific interfaces that your parts implement. Your chassee has places for parts to fit, if they implement the correct interface. Change seats.. fine as long as the attachment bolts are in the same place.Libertinism
"no private methods"? That's the first time I'm reading that. Is that really a thing?Isborne
Like all design advice.. It's a gradient. Private methods indicate your class has more than one concern. Wether that concern is big enough to pull out at the point you notice it is another question. You may decide to live with it for now, or not see a good place to move it to. Try to make private methods into static private methods.. That will make moving them later easier. Go through the list of parameters and see if the method would make sense as a public method on one of the parameters types.Libertinism
I feel like I'm missing something. Similar to @IgnacioSolerGarcia's question, are you arguing that in a general sense abstract classes are an indication of "young" or not very modular code?Aniline
@Aniline Abstract classes exist to solve a couple of design problem. Since I value testability in my code, I find abstract classes cause me to incur a cost I am not willing to pay. I am proposing alternative designs that are more testable. I find "testability" is a good stand-in metric for decoupled-ness.Libertinism
Regarding abstract classes: ironically I've always argued the exact opposite for the exact same reasons. It goes like this: if your class implements multiple interfaces, it is a smell that it may have multiple responsibilities and lack cohesion. Sometimes it is justified if interfaces represent different "roles" that the class has, but even those quite often end up being refactored in favor of composition as complexity grows. I thus always favor pure abstract classes over interfaces so that I am forced to justify the use of interfaces and keep cohesion in check. ...Raneeraney
... There are (less important but free) secondary advantages too, like backward-compatibility, which @BarryKelly alluded to (think transition phases for method renames using deprecation as a trivial scenario that interfaces in many languages cannot handle at all).Raneeraney
What if you have some default functionality which sometimes is overridden and sometimes is not. Is this not a perfect candidate for an abstract class with virtual members?Goines
@Goines Maybe. I'm willing to pay for testable code. I don't have your situation. I look at "why they have common default implementations?". Is there some relationship between these classes? Is there two sets of methods that vary independently? Two responsibilities in the class? Are the ones you leave default a separate responsibility? I can't tell without seeing the code. Good luck.Libertinism
Look how much complexity has been added. Sure, you should write testable code, but you should never make the implementation code more complex for the sake of it. Maybe the testing framework lacks some features that would aid in these kind of scenarios. I totally disagree with TDD in general , that's why this kind of approach sounds like design smell in my opinion. The design should not be driven so much by tests.Aerotherapeutics
Also for the sake of simplicity, ICar interface is not needed in scenario 1. It's enough to set some abstract methods in the abstract class. There are plenty scenarios when an abstract class needs to establish the contract. Your dismissal of abstract classes is wrong and sure in theory , in some scenarios may work. But I prefer to keep things simplea dnc reate build stubs/mocks to test abstract classes and not introduce additional complexity for the sake of having thinner test code (it's like the chicken and the egg story). Testing should not interfeer too much with design.Aerotherapeutics
@GeoC. It's great to hear the other viewpoint expressed so clearly. Where you see complexity I see simplicity and visa versa. We are both right because the best code is the code that works best for your team. My preferences is be standing further down the "single responsibility" scale than you feel is reasonable. I'm okay with that. Do what works for you. We both win by having the discussion.Libertinism
Inspring post, thank you. But the advice to completely avoid abstract classes goes to far for me. For example, that would prohibit use of the Template method pattern.Beech
Thanks for the feedback. Instead of Template method I would define an interface with the template methods. Make the base class Concrete. Injecting an implementation of that interface to define the actions. Which is better? Depends on your perspective and values. With any design rule I recommend experimenting with it, taking a code-base and going way-too-far to see where I end up. I usually find the middle-ground isn't where I thought it was.Libertinism
Whether you use a strategy or inheritance should not be dictated by implementation convenience. It should express/describe the way things are used and handled in the business domain. Sooner or later, when your implementation diverges structurally from the business model, you'll get in trouble. Even when the business domain model is obviously unnecessarily complex, or illogical, it's a good thing to replicate it in code, because you'll get your requirements in the context of that complexity and lack of logic, and implementing them on top of a divergent model will sooner or later become painful.Spool
Inheritance is a concept introduced in some OO languages. OO inheritance doesn't match anything in the real world. In the real world you will find interfaces and implementation of that interface. You point about modelling your domain closely is absolutely correct. You choose not to use GOTO even though it's in the language. I choose not to use inheritance as it has never lead to a better more maintainable design. I'd love to be proved wrong. I love to learn.Libertinism
This is really a non-answer and I don't agree with the idea that you just shouldn't use abstract classes. I use them fairly often and I rarely have problems. When I do I use a more appropriate solution.Cordoba
M
286

Write a Mock object and use them just for testing. They usually are very very very minimal (inherit from the abstract class) and not more.Then, in your Unit Test you can call the abstract method you want to test.

You should test abstract class that contain some logic like all other classes you have.

Mosra answered 28/10, 2008 at 13:50 Comment(11)
Damn, I have to say this is the first time I've ever agreed with the idea of using a mock.Chandrachandragupta
Daok: Could you please provide a simple example? I don't know, why I should use a Mock instead of a minimal class. Or did I misunderstand you? Thanks in advance!Firman
Mocks simply let you avoid having to write a stub implementation of the abstract base class, in this use they are far and away the most sensible choice assuming you are happy to take the dependency on a mocking framworkBohemianism
Just trying to work this out...so you create the mock as a subclass. Is the mock generated on the fly? If so, how do you reach inside to test all the abstract's innards? If the mock is hard-coded, does any testcode go inside the mock's declaration to make the abstract do a work-out. Just wondering. I tend to do what Mnementh does.Curator
You need two classes, a mock and test. The mock class extends only the abstract methods of the Abstract class under test. Those methods can be no-op, return null, etc. as they will not be tested. The test class tests only the non-abstract public API (ie the Interface implemented by the Abstract class). For any class that extends the Abstract class you'll need additional test classes because the abstract methods were not covered.Sac
This one is a really nice example on how to do it with mockito stackoverflow.com/questions/1087339/…Autotransformer
It is possible obviously to do this.. but to truly test any derived class you are going to be testing this base functionality over and over.. which leads you to have an abstract test fixture so you can factor out this duplication in testing. This all smells! I strongly recommend taking another look at why you are using abstract classes in the first place and see if something else would work better.Libertinism
@Nigel Thorne, not saying your wrong or anything, just that i'm miffed by your comment. that being said, if i were using an abstract class to factor out duplication within objects in your design, and clients use the concrete implementations through their own interfaces, how is that bad design? in my usage, i have something like 20 classes that extend my abstract, which has concrete methods that are used by all extending classes. how is this code smell? what would be a "better" way to do this?Yours
@Yours There are lots of ways to write code that the compiler understand. Being an experienced coded you already filter the set of possible designs to discard designs that have high coupling or are confusing or unmaintainable. The designs that pass your learnt filters are classed 'good designs'. I have one more criteria that I judge designs by. Can I easly test it?Libertinism
But then how do you test the concrete classes which inherit the abstract class? Test the protected methods? Surely you want to test the public methods only...Dalenedalenna
much better answer than the top rated one with dummies style graphics.. I would call this answer non-code-invasiveHomocercal
N
12

What I do for abstract classes and interfaces is the following: I write a test, that uses the object as it is concrete. But the variable of type X (X is the abstract class) is not set in the test. This test-class is not added to the test-suite, but subclasses of it, that have a setup-method that set the variable to a concrete implementation of X. That way I don't duplicate the test-code. The subclasses of the not used test can add more test-methods if needed.

Nonperishable answered 28/10, 2008 at 13:47 Comment(2)
doesn't this cause casting issues in the subclass? if X has method a and Y inherits X but has a method b too. When you subclass your test class don't you have to cast your abstract variable to a Y to perform tests on b?Corneliacornelian
That duplicates testing effort, when your test tests logic that's located in the base class.Spool
H
11

To make an unit test specifically on the abstract class, you should derive it for testing purpose, test base.method() results and intended behaviour when inheriting.

You test a method by calling it so test an abstract class by implementing it...

Hypaethral answered 28/10, 2008 at 13:39 Comment(0)
S
9

If your abstract class contains concrete functionality that has business value, then I will usually test it directly by creating a test double that stubs out the abstract data, or by using a mocking framework to do this for me. Which one I choose depends a lot on whether I need to write test-specific implementations of the abstract methods or not.

The most common scenario in which I need to do this is when I'm using the Template Method pattern, such as when I'm building some sort of extensible framework that will be used by a 3rd party. In this case, the abstract class is what defines the algorithm that I want to test, so it makes more sense to test the abstract base than a specific implementation.

However, I think it's important that these tests should focus on the concrete implementations of real business logic only; you shouldn't unit test implementation details of the abstract class because you'll end up with brittle tests.

Shontashoo answered 28/10, 2008 at 14:19 Comment(0)
I
6

one way is to write an abstract test case that corresponds to your abstract class, then write concrete test cases that subclass your abstract test case. do this for each concrete subclass of your original abstract class (i.e. your test case hierarchy mirrors your class hierarchy). see Test an interface in the junit recipies book: http://safari.informit.com/9781932394238/ch02lev1sec6. https://www.manning.com/books/junit-recipes or https://www.amazon.com/JUnit-Recipes-Practical-Methods-Programmer/dp/1932394230 if you don't have a safari account.

also see Testcase Superclass in xUnit patterns: http://xunitpatterns.com/Testcase%20Superclass.html

Ingrowing answered 4/12, 2008 at 7:41 Comment(0)
S
4

I would argue against "abstract" tests. I think a test is a concrete idea and doesn't have an abstraction. If you have common elements, put them in helper methods or classes for everyone to use.

As for testing an abstract test class, make sure you ask yourself what it is you're testing. There are several approaches, and you should find out what works in your scenario. Are you trying to test out a new method in your subclass? Then have your tests only interact with that method. Are you testing the methods in your base class? Then probably have a separate fixture only for that class, and test each method individually with as many tests as necessary.

Scherzando answered 28/10, 2008 at 13:35 Comment(3)
I didn't want to retest code I had already tested thats why I was going down the abstract test case road. I am trying to test all the concrete methods in my abstract class in one place.Percipient
I disagree with extracting common elements to helper classes, at least in some (many?) cases. If an abstract class contains some concrete functionality I think it's perfectly acceptable to unit test that functionality directly.Shontashoo
And if you have tens of methods with concerete implementations then just copy-paste it in all of your concrete implementations? This makes no senseZsazsa
H
4

This is the pattern I usually follow when setting up a harness for testing an abstract class:

public abstract class MyBase{
  /*...*/
  public abstract void VoidMethod(object param1);
  public abstract object MethodWithReturn(object param1);
  /*,,,*/
}

And the version I use under test:

public class MyBaseHarness : MyBase{
  /*...*/
  public Action<object> VoidMethodFunction;
  public override void VoidMethod(object param1){
    VoidMethodFunction(param1);
  }
  public Func<object, object> MethodWithReturnFunction;
  public override object MethodWithReturn(object param1){
    return MethodWihtReturnFunction(param1);
  }
  /*,,,*/
}

If the abstract methods are called when I don't expect it, the tests fail. When arranging the tests, I can easily stub out the abstract methods with lambdas that perform asserts, throw exceptions, return different values, etc.

Halfhardy answered 28/10, 2008 at 14:3 Comment(0)
M
3

If the concrete methods invoke any of the abstract methods that strategy won't work, and you'd want to test each child class behavior separately. Otherwise, extending it and stubbing the abstract methods as you've described should be fine, again provided the abstract class concrete methods are decoupled from child classes.

Misunderstanding answered 28/10, 2008 at 13:36 Comment(0)
N
2

I suppose you could want to test the base functionality of an abstract class... But you'd probably be best off by extending the class without overriding any methods, and make minimum-effort mocking for the abstract methods.

Nictitate answered 28/10, 2008 at 13:36 Comment(0)
J
2

One of the main motivations for using an abstract class is to enable polymorphism within your application -- i.e: you can substitute a different version at runtime. In fact, this is very much the same thing as using an interface except the abstract class provides some common plumbing, often referred to as a Template pattern.

From a unit testing perspective, there are two things to consider:

  1. Interaction of your abstract class with it related classes. Using a mock testing framework is ideal for this scenario as it shows that your abstract class plays well with others.

  2. Functionality of derived classes. If you have custom logic that you've written for your derived classes, you should test those classes in isolation.

edit: RhinoMocks is an awesome mock testing framework that can generate mock objects at runtime by dynamically deriving from your class. This approach can save you countless hours of hand-coding derived classes.

Joel answered 28/10, 2008 at 14:9 Comment(0)
A
2

First if abstract class contained some concrete method i think you should do this considered this example

 public abstract class A 

 {

    public boolean method 1
    {
        // concrete method which we have to test.

    }


 }


 class B extends class A

 {

      @override
      public boolean method 1
      {
        // override same method as above.

      }


 } 


  class Test_A 

  {

    private static B b;  // reference object of the class B

    @Before
    public void init()

      {

      b = new B ();    

      }

     @Test
     public void Test_method 1

       {

       b.method 1; // use some assertion statements.

       }

   }
Applegate answered 16/5, 2015 at 6:32 Comment(0)
A
1

Following @patrick-desjardins answer, I implemented abstract and it's implementation class along with @Test as follows:

Abstract class - ABC.java

import java.util.ArrayList;
import java.util.List;

public abstract class ABC {

    abstract String sayHello();

    public List<String> getList() {
        final List<String> defaultList = new ArrayList<>();
        defaultList.add("abstract class");
        return defaultList;
    }
}

As Abstract classes cannot be instantiated, but they can be subclassed, concrete class DEF.java, is as follows:

public class DEF extends ABC {

    @Override
    public String sayHello() {
        return "Hello!";
    }
}

@Test class to test both abstract as well as non-abstract method:

import org.junit.Before;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.contains;
import java.util.Collection;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;

import org.junit.Test;

public class DEFTest {

    private DEF def;

    @Before
    public void setup() {
        def = new DEF();
    }

    @Test
    public void add(){
        String result = def.sayHello();
        assertThat(result, is(equalTo("Hello!")));
    }

    @Test
    public void getList(){
        List<String> result = def.getList();
        assertThat((Collection<String>) result, is(not(empty())));
        assertThat(result, contains("abstract class"));
    }
}
Animosity answered 23/1, 2017 at 16:6 Comment(0)
G
1

If an abstract class is appropriate for your implementation, test (as suggested above) a derived concrete class. Your assumptions are correct.

To avoid future confusion, be aware that this concrete test class is not a mock, but a fake.

In strict terms, a mock is defined by the following characteristics:

  • A mock is used in place of each and every dependency of the subject class being tested.
  • A mock is a pseudo-implementation of an interface (you may recall that as a general rule, dependencies should be declared as interfaces; testability is one primary reason for this)
  • Behaviors of the mock's interface members -- whether methods or properties -- are supplied at test-time (again, by use of a mocking framework). This way, you avoid coupling of the implementation being tested with the implementation of its dependencies (which should all have their own discrete tests).
Gleesome answered 24/1, 2018 at 20:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.