ChatGPT解决这个技术问题 Extra ChatGPT

How to unit test abstract classes: extend with stubs?

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.

It's best not to unit test abstract classes directly: enterprisecraftsmanship.com/posts/…
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 sense

a
awesoon

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

You are specializing your abstract object, but all clients will use the derived class through its base interface. 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

https://i.stack.imgur.com/Bu4Vy.png

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.

https://i.stack.imgur.com/wPPqA.png

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.

https://i.stack.imgur.com/ABrGO.png

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.

https://i.stack.imgur.com/eySjQ.png

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.


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.. :)
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.
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)
Nice answer. Definitely something to think about... but doesn't what you are saying basically boil down to don't use abstract classes?
+1 for the Rule alone, "Favor complex network of simple objects over a simple network of complex objects."
m
mezoid

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.


Damn, I have to say this is the first time I've ever agreed with the idea of using a mock.
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!
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.
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.
@liltitus27 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?
M
Mnementh

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.


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?
That duplicates testing effort, when your test tests logic that's located in the base class.
佚名

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...


S
Seth Petry-Johnson

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.


R
Ray Tayek

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


c
casademora

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.


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.
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.
And if you have tens of methods with concerete implementations then just copy-paste it in all of your concrete implementations? This makes no sense
佚名

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.


J
Jeb

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.


A
Ace

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.


b
bryanbcook

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:

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. 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.


s
shreeram banne

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.

       }

   }

b
banduki

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).


u
user2340612

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"));
    }
}