As an object-oriented software-developer you know what happens when you override a method: the super-implementation will not be executed unless you call it explicitly. But what about annotations on a class or method? Are they inherited to a sub-class, or do I have to repeat all annotations in the override? Sometimes the annotation concepts seem to collide with OO-concepts.
Annotations, as used in Java, are not part of object-oriented thinking. It is a separate programming-language with its own rules. An annotation can not extend another annotation, but it can be annotated by another, and thus aggregate semantic.
What was done through naming-conventions in old times, nowadays is done through annotations. They were introduced to let weave in aspects. Developer-made annotations work through reflection at runtime, mostly without compiler check, what makes them dangerous.
Let's look at unit-testing with JUnit. First it built on naming-conventions (every test method name had to start with "test"), today it works through annotations (every test method has to carry a @Test
annotation). By the way, naming-conventions are a mess, because mostly just the author knows them:-) So let's see how messy JUnit annotations are.
Following shows a unit-test and its super-class, the super-class containing default-implementations, the sub-class overriding some of them. The @Before
annotation teaches the JUnit-4 runner to execute any such annotated method before each test (-method), @After
methods would be executed afterwards. The actual test in TestJUnitAnnotations
source below is the @Test test()
method.
So, what would you guess the output is when run with JUnit-4?
1 | import org.junit.After; |
I defined six annotated methods, three @Before
and three @After
. Two of them conform to the old naming-convention setUp()
and tearDown()
, to see whether they are preferred in any way by JUnit-4. Then I used the Java case-sensitivity in setup()
and setUp()
to confuse JUnit.
1 | import org.junit.After; |
The extension class overrides just four of the super-class methods, to see whether super-class methods are actually called before sub-class methods. Two of them carry no annotation, to see whether repeating annotations is necessary for JUnit-4, or overriding is enough. I left out any @Override
annotation to see whether this plays a role.
JUnit guarantees that the @Before
methods in super-class will be executed before those in a sub-class, unless they have been overridden. No rules about the execution order of several @Before
methods exist.
Here is the output of TestJUnitAnnotations
when run with JUnit-4:
AbstractTestJUnitAnnotations: @Before setup()
TestJUnitAnnotations: @Before @Override setUp()
TestJUnitAnnotations: @Before @Override init()
TestJUnitAnnotations: @Test test()
TestJUnitAnnotations: @After @Override tearDown()
AbstractTestJUnitAnnotations: @After teardown()
TestJUnitAnnotations: @After @Override exit()
When I say "method" in the following, I mean methods annotated by @Before
.
@After
methods will be executed after those in sub-class.) @After
method may be executed after a super-method.) setUp()
and tearDown()
don't play a role any more.@Override
annotation doesn't play a role here (but anyway you should use it!).@Before
or @After
annotation in the sub-class override.The last sentence is true for JUnit-4, but could be different for other libraries, maybe forcing you to duplicate any annotation in a sub-class. You need knowledge about the library that defines the annotations to understand what happens. Inheritance should be indicated by the Java built-in @Inherited
annotation on @interface Before
, but wasn't done in this case.
Also the fact that "Overridden methods in super-class will not be executed unless called explicitly" is true for JUnit-4 but could be different for other libraries. Yes, annotations make your software a sucking expert world!
The shown test would not work with JUnit-5. There you need @BeforeEach
instead of @Before
. The test would compile, but the annotations would simply be ignored at runtime. Happy rewriting!
Spring is another framework based on reflection and annotations. You can not understand a Spring Boot application without intimate knowledge of Spring annotations. Object-oriented knowledge about Java is not sufficient when annotations take over execution control.
As annotations work through reflection at runtime, they could turn Java into a script language. You need a 100% test coverage for such software. JavaScript/ES6 programmers may understand what I mean.
Java was called "Smalltalk for the masses". In Smalltalk, unlike in Java, type checks happen at runtime, not at compile time, like it is in script languages. Surely one of the reasons why Smalltalk is not used any more. Will Java go the same way?
ɔ⃝ Fritz Ritzberger, 2020-07-04