Overriding Java methods in Groovy for unit testing
2014-01-14 13:06
399 查看
Lately, I’ve been experimenting with writing unit tests in Groovy to test our Java code at Orbitz. Groovy provides many nice language features that have the potential to dramatically reduce the amount of test code that you have to write, and to decrease the
maintenance burden of that test code. Since it makes tests easier to write and maintain, hopefully more people will do it! :)
One of the features of Groovy that immediately caught my attention was the ability to specify a new implementation for a method of a given class at runtime. I was first introduced to such power by Ruby, and have been wanting it elsewhere ever since. I immediately
thought that this would be great for testing our legacy code, where stubbing methods is difficult, yet often necessary to avoid major refactoring work. Changes involving major refactoring take much more time and increase the possibility of introducing more
bugs. This codebase is in the middle of being swapped out with a new one, so a major refactoring isn’t really worth the effort (it would however be worth the effort in the new codebase). Currently, to stub out private methods buried in our codebase, we’ve
been widening the accessibility of the method to protected, and subclassing the class to override the functionality. I’m sure this seems hacky, but it’s really less risky/evil then some of the alternatives. The code under test in this case is back end code
that is not available outside of our team. Obviously, widening the scope on a method of a released API or library would be a different scenario entirely. So, I was really excited to think that Groovy could possibly help us with this nastiness.
Sound too good to be true? Well, it is.
This great feature is only available for Groovy objects! Java
objects need not apply. You can add methods to Java classes,
but you can’t change them. Bummer.
While scouring the web to see if there was any other way to do this, I stumbled upon JMockit, a library that helps with unit testing in Java. JMockit lets you do exactly what
I want to do; provide a new implementation for any method on
a class! Private methods, static methods…no sweat. JMockit to the rescue!
Right? Wrong.
With JMockit, the mock object containing the new implementation must not include any new methods that are not on the original class. The problem here is that all objects in Groovy contain several methods that are not on java.lang.Object. So, even though you
are not programmatically adding methods to the mock that aren’t on the original, since your object is automatically a GroovyObject, it has these new Groovy methods, whether you want them or not. And, specifically extending java.lang.Object doesn’t help either.
So, back to square one.
But, all hope is not lost. Sure, it sucks that we’re reduced back to widening the accessibility of a method so it can be stubbed out in a subclass, but at least doing this is much easier in Groovy. Take the following Java class for example:
What if we want to have getLine return something different? Well, first we have to make it protected:
Then, we have to subclass Book and provide the new implementation. The easiest way to do this in Java would be with an anonymous inner class:
This is much more concise in Groovy, thanks to closures, map coercion, and the fabulous “as” keyword:
Not only is it less code, but it also protects us from interface changes
to getLine!getLine can be changed to take a parameter or throw a new checked exception, and our test would remain unchanged, lessening the maintenance burden of our test case. Very nice.
So, although this is not ideal, it still has benefits over its Java counterpart.
maintenance burden of that test code. Since it makes tests easier to write and maintain, hopefully more people will do it! :)
One of the features of Groovy that immediately caught my attention was the ability to specify a new implementation for a method of a given class at runtime. I was first introduced to such power by Ruby, and have been wanting it elsewhere ever since. I immediately
thought that this would be great for testing our legacy code, where stubbing methods is difficult, yet often necessary to avoid major refactoring work. Changes involving major refactoring take much more time and increase the possibility of introducing more
bugs. This codebase is in the middle of being swapped out with a new one, so a major refactoring isn’t really worth the effort (it would however be worth the effort in the new codebase). Currently, to stub out private methods buried in our codebase, we’ve
been widening the accessibility of the method to protected, and subclassing the class to override the functionality. I’m sure this seems hacky, but it’s really less risky/evil then some of the alternatives. The code under test in this case is back end code
that is not available outside of our team. Obviously, widening the scope on a method of a released API or library would be a different scenario entirely. So, I was really excited to think that Groovy could possibly help us with this nastiness.
Sound too good to be true? Well, it is.
This great feature is only available for Groovy objects! Java
objects need not apply. You can add methods to Java classes,
but you can’t change them. Bummer.
While scouring the web to see if there was any other way to do this, I stumbled upon JMockit, a library that helps with unit testing in Java. JMockit lets you do exactly what
I want to do; provide a new implementation for any method on
a class! Private methods, static methods…no sweat. JMockit to the rescue!
Right? Wrong.
Exception in thread "main" mockit.RealMethodNotFoundForMockException: Corresponding real methods not found for the following mocks: groovy.lang.MetaClass getMetaClass(), Object invokeMethod(String, Object), Object getProperty(String), void setProperty(String, Object), void setMetaClass(groovy.lang.MetaClass)
With JMockit, the mock object containing the new implementation must not include any new methods that are not on the original class. The problem here is that all objects in Groovy contain several methods that are not on java.lang.Object. So, even though you
are not programmatically adding methods to the mock that aren’t on the original, since your object is automatically a GroovyObject, it has these new Groovy methods, whether you want them or not. And, specifically extending java.lang.Object doesn’t help either.
So, back to square one.
But, all hope is not lost. Sure, it sucks that we’re reduced back to widening the accessibility of a method so it can be stubbed out in a subclass, but at least doing this is much easier in Groovy. Take the following Java class for example:
1 | public class Book { |
2 | public String read() { |
3 | return getLine(); |
4 | } |
5 |
6 | private String getLine() { |
7 | return "Damn this is a long book!" ; |
8 | } |
9 | } |
1 | protected String getLine() { |
2 | return "Damn this is a long book!" ; |
3 | } |
1 | Book b = new Book() { |
2 | protected String getLine() { |
3 | return "Short book" ; |
4 | } |
5 | }; |
6 | assertEquals( "Short book" , b.read()); |
1 | def b = [ getLine: { "Short book" } ] as Book |
2 | assert "Short book" == b.read() |
to getLine!getLine can be changed to take a parameter or throw a new checked exception, and our test would remain unchanged, lessening the maintenance burden of our test case. Very nice.
So, although this is not ideal, it still has benefits over its Java counterpart.
相关文章推荐
- Difference Between Class.ForName() And ClassLoader.LoadClass() Methods In Java
- Methods for Using Java in ORACLE(转荐)
- 《Pragmatic unit testing:in java with Junit》阅读
- Unit Testing in Java: How Tests Drive the Code
- Pragmatic Unit Testing in Java with JUnit 书评
- 《Pragmatic Unit Testing In Java with JUnit》—单元测试之道读后感
- Pragmatic Unit Testing in Java with JUnit
- Java Methods for Financial Engineering: Applications in Finance and Investment
- Pragmatic Unit Testing in Java with JUnit 书评
- (转)Methods for Using Java in ORACLE
- Java Unit Testing with JUnit in NetBeans
- Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
- [Webpack 2] Use Karma for Unit Testing with Webpack
- Java for LeetCode 154 Find Minimum in Rotated Sorted Array II
- Java:Java中Overriding and Hiding Methods
- mybatis There is no getter for property named 'xx' in 'class java.lang.String
- Important Points for Inheritance in Java
- There is no getter for property named 'XXX' in 'class java.lang.String'
- Groovy Script in SoapUI REST Testing
- Effective Java 英文 第二版 读书笔记 Item 14:In public classes,use accessor methods,not public fields