2

Say I have a class

public class Foo extends Bar
{
   public Response post()
   {
      return authorize();
   }

   // override some additional methods from Bar class
}

and another class

public class Bar
{
  public Response get()
  {
    return authorize();
  }

  public Response post()
  {
    return authorize();
  }

  public Response authorize()
  {
    // let's assume this method is "complicated" and has 200+ lines of code... lots of diff code paths etc
  }

}

So I was thinking of a few different approaches in terms of unit testing

  1. I directly call authorize and test every single possible code path. Then I write 1 happy path unit test for the get and post methods for Foo and Bar for completeness.

  2. I directly call get and post methods of both classes, and decide to only write tests for all combinations against one method only.

  3. I completely ignore my get and post methods and assume that as long as I fully test my authorize method I'm good.

Am I complicating this? What's the best practice here.

Alex Len
  • 43
  • 3
  • 1
    I noticed this question was closed as it is considered "opinion-based". I think it is a question asking whether there are systematic testing techniques for inheritance. Yes there are, see the answers. I propose to re-open the question (and voted as such). – avandeursen May 16 '21 at 20:49

2 Answers2

1

You could consider extracting the authorize method into its own class, say Authorizer, and test that independently of Foo and Bar. Your tests for Bar.get, Bar.post, and Foo.post can mock out the Authorizer and verify that the get and post logic is correct.

This is essentially your first option, but with the "test every single possible code path" part encapsulated in a dedicated class.

A nice side effect of this approach is that you have also separated your concerns. The Authorizer only cares about authorization, and your Foo and Bar classes can concentrate on their Foo-behavior and Bar-behavior. This makes your code easier to maintain in the long run.

Cameron Skinner
  • 51,692
  • 2
  • 65
  • 86
  • thats a really neat approach. But my child class overrides some methods that are called in the `authorize` method to provide a class-specific implementation – Alex Len May 07 '21 at 13:46
  • That makes it harder :) Might still be worth refactoring things. If you can use composition rather then inheritance then you might be able to decouple your logic. For example, if `authorize` needs to call `getWidget`, then you might be able to factor that out to a `WidgetProvider` and provide one to the `Authorizer`. If `Foo` and `Bar` also need widgets, wire the same `WidgetProvider` in to those classes as well. – Cameron Skinner May 08 '21 at 00:25
1

It might also help to consider this as a problem of testing conformance to the Liskov Substitution Principle (LSP).

The superclass Bar offers a contract. Any subclass of Bar must at least offer the same contract -- but it may require a little less (weaker preconditions, works under more circumstances) from its callers, and may promise a little more (stronger postconditions, do more work) to its callers.

From your test strategy 2, it seems that you can indeed create instances of Bar. This means you can create a class BarTest, which rigorously tests Bar, given an object of class Bar.

Now because of LSP, that very same test suite BarTest, must also pass on any subclass of Bar, hence also on Foo. One way to achieve this is to create a FooTest class that simply extends BarTest, and then runs the same tests on an object of class Foo (for example by overriding a factory method that actually creates the object under test).

Then, if Foo offers more functionality, you can add unit tests for this added functionality as well, in the same FooTest class.

In terms of your three options:

  1. Test every single path in authorize and offer happy path for get and post: Write the test cases in BarTest, and FooTest will simply inherit them.

  2. The inheritance would take care of testing both methods (get, post) in both contexts (Bar, Foo)

  3. If things are simple, ignoring may be a good tradeoff. If the inheritance relation is more complicated, a systematic approach would help.

All this comes from Binder's Polymorphic Server Test pattern, and McGregor's PACT -- Parallel Architecture for Component Testing. See also my earlier answer to a question about testing interface implementations.

avandeursen
  • 8,458
  • 3
  • 41
  • 51