How to unit test abstract classes: extend with stubs?

后端 未结 14 1685
有刺的猬
有刺的猬 2020-11-27 08:37

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 abstr

相关标签:
14条回答
  • 2020-11-27 09:12

    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.

    0 讨论(0)
  • 2020-11-27 09:12

    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"));
        }
    }
    
    0 讨论(0)
  • 2020-11-27 09:15

    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).
    0 讨论(0)
  • 2020-11-27 09:16

    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.

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

    0 讨论(0)
  • 2020-11-27 09:17

    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.

    0 讨论(0)
  • 2020-11-27 09:18

    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.

    0 讨论(0)
提交回复
热议问题