mercredi 26 juillet 2017

Function to retrieve the top-most annotation from the call stack

I would like to define an abstract base class that defines generic reusable test cases that I want to implement over and over again when testing various APIs. The idea is to define the tests once, and then I can just inherit from the generic test in order to get the test definitions, and all my test code has to do is implement the specifics of how to execute the tests. That way I never miss good test cases, I am not having to re-invent the wheel, nor making changes in dozens of places when I think up another good test case to test.

In essence, I'm doing parameterized testing, where the parameters are defined in the base class. The expected result is also defined in the base class. However, there are times when the expected result needs to be overridden. For example, an ordinary string field might allow any characters, whereas an object name field may (or in my case DOES) limit which characters are legal. For example, a String field allows "?" in the value, a Name field does not.

I'm trying to figure out a clean way of defining the parameter ONCE, in the base class, and defining the expected result only once but with the option of overriding it in the implementing class where the context requires it.

The problem I have is that when you override a method, you have to replace its implementation. You can't just replace a portion of it. I want to replace the expected outcome without overriding the parameter portion. Nor do I consider it a clean or desirable solution to break every test into two methods, one that provides the parameter and one that defines the expected behavior.

One option I am considering is using annotations to define the expected results. Then I can override the annotation, and then delegate the implementation to the base class implementation. As long as the base class uses the annotation to decide how to behave, it should work.

For example: (in pseudo-code, not real Java)

abstract class foo {

  @fubar(true)
  void test1() { doSomething( param1 ) }

  @fubar(true)
  void test2() { doSomething( param2 ) }

  @fubar(false)
  void test3() { doSomething( param3 ) }

  boolean isFubar( void ) { ... }  // generic annotation grabber

  void doSomething( p ) {
    if ( isFubar() ) 
      doThis( p ) 
    else 
      doThat( p );
  }

  // abstract methods to be defined in the implementing class
  void doThis( p );
  void doThat( p );
}

class bar extends foo {

  // change the expected result for test2 only
  @fubar(false)
  void test2() { super.test2(); }

  // define how to execute the tests
  void doThis(p) { ... }
  void doThat(p) { ... }

}

class baz extends bar {

  // only changes the semantics of test3, nothing else
  @fubar(true)
  void test3() { super.test3() }

}

Given this hierarchy, foo.test1(), bar.test1(), and baz.test1() all do exactly the same thing. Whereas foo.test2() does one thing, and bar.test2() and baz.test2() does something else. Similarly, foo.test3() and bar.test3() do one thing, but baz.test3() will be different.

Can I use annotations to accomplish this behavior? If so, what would isFubar look like? I've yet to see an example of this kind of reflection where the method name is not known.

As a side note, if there is a cleaner way to accomplish the intended behavior, I'd be happy to hear what it is.

Aucun commentaire:

Enregistrer un commentaire