반사 또는 기타 수단을 통해 Java 최종 방법을 무시합니까?
-
09-09-2019 - |
문제
이 질문은 테스트 사례를 작성하려고 시도하는 동안 발생합니다. Foo는 소스 액세스 권한이없는 프레임 워크 라이브러리 내 클래스입니다.
public class Foo{
public final Object getX(){
...
}
}
내 응용 프로그램이 될 것입니다
public class Bar extends Foo{
public int process(){
Object value = getX();
...
}
}
다른 종속성으로 인해 FOO 객체를 만들 수 없으므로 단위 테스트 케이스는 비활성화 할 수 없습니다. 바르 테스트는 값이 null이므로 널 포인터를 던졌습니다.
public class BarTest extends TestCase{
public testProcess(){
Bar bar = new Bar();
int result = bar.process();
...
}
}
반사 API를 사용하여 getx ()를 비 결절로 설정할 수있는 방법이 있습니까? 아니면 어떻게 테스트해야합니까?
해결책
테스트에서 재정의 할 수있는 다른 방법을 만들 수 있습니다.
public class Bar extends Foo {
protected Object doGetX() {
return getX();
}
public int process(){
Object value = doGetX();
...
}
}
그런 다음 Bartest에서 Dogetx를 무시할 수 있습니다.
다른 팁
이것은 Google에서 "Final Method Java"의 주요 결과 중 하나였습니다. 나는 내 해결책을 떠날 것이라고 생각했다. 이 클래스는 예제 "베이글"클래스를 사용하여 간단한 솔루션을 보여주고 Javassist 라이브러리를 무료로 사용할 수 있습니다.
/**
* This class shows how you can override a final method of a super class using the Javassist's bytecode toolkit
* The library can be found here: http://jboss-javassist.github.io/javassist/
*
* The basic idea is that you get the super class and reset the modifiers so the modifiers of the method don't include final.
* Then you add in a new method to the sub class which overrides the now non final method of the super class.
*
* The only "catch" is you have to do the class manipulation before any calls to the class happen in your code. So put the
* manipulation as early in your code as you can otherwise you will get exceptions.
*/
package packagename;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Modifier;
/**
* A simple class to show how to use the library
*/
public class TestCt {
/**
* The starting point for the application
*/
public static void main(String[] args) {
// in order for us to override the final method we must manipulate the class using the Javassist library.
// we need to do this FIRST because once we initialize the class it will no longer be editable.
try
{
// get the super class
CtClass bagel = ClassPool.getDefault().get("packagename.TestCt$Bagel");
// get the method you want to override
CtMethod originalMethod = bagel.getDeclaredMethod("getDescription");
// set the modifier. This will remove the 'final' modifier from the method.
// If for whatever reason you needed more than one modifier just add them together
originalMethod.setModifiers(Modifier.PUBLIC);
// save the changes to the super class
bagel.toClass();
// get the subclass
CtClass bagelsolver = ClassPool.getDefault().get("packagename.TestCt$BagelWithOptions");
// create the method that will override the super class's method and include the options in the output
CtMethod overrideMethod = CtNewMethod.make("public String getDescription() { return super.getDescription() + \" with \" + getOptions(); }", bagelsolver);
// add the new method to the sub class
bagelsolver.addMethod(overrideMethod);
// save the changes to the sub class
bagelsolver.toClass();
}
catch (Exception e)
{
e.printStackTrace();
}
// now that we have edited the classes with the new methods, we can create an instance and see if it worked
// create a new instance of BagelWithOptions
BagelWithOptions myBagel = new BagelWithOptions();
// give it some options
myBagel.setOptions("cheese, bacon and eggs");
// print the description of the bagel to the console.
// This should now use our new code when calling getDescription() which will include the options in the output.
System.out.println("My bagel is: " + myBagel.getDescription());
// The output should be:
// **My bagel is: a plain bagel with cheese, bacon and eggs**
}
/**
* A plain bagel class which has a final method which we want to override
*/
public static class Bagel {
/**
* return a description for this bagel
*/
public final String getDescription() {
return "a plain bagel";
}
}
/**
* A sub class of bagel which adds some extra options for the bagel.
*/
public static class BagelWithOptions extends Bagel {
/**
* A string that will contain any extra options for the bagel
*/
String options;
/**
* Initiate the bagel with no extra options
*/
public BagelWithOptions() {
options = "nothing else";
}
/**
* Set the options for the bagel
* @param options - a string with the new options for this bagel
*/
public void setOptions(String options) {
this.options = options;
}
/**
* return the current options for this bagel
*/
public String getOptions() {
return options;
}
}
}
SEB는 정확하고, 귀하의 질문에 대한 답변을 얻기 위해, 기본 코드로 무언가를하지 않거나 (작동하지 않을 것이라고 확신합니다) 런타임시 클래스의 바이트 코드를 수정하고 클래스를 만들어냅니다. 런타임에서 메소드를 무시하면 메소드의 "최종"을 변경하는 방법을 볼 수 없습니다. 반성은 여기서 당신을 도울 것입니다.
다른 종속성으로 인해 단위 테스트 케이스가 FOO를 생성 할 수없는 경우, 이는 처음부터 장치 테스트를 제대로하지 않는다는 신호일 수 있습니다.
단위 테스트는 생산 코드가 실행되는 것과 동일한 상황에서 테스트하기위한 것이므로 테스트 내에서 동일한 생산 환경을 재현하는 것이 좋습니다. 그렇지 않으면 테스트가 완료되지 않습니다.
변수가 반환 된 경우 getX()
아니다 final
설명 된 기술을 사용할 수 있습니다 개인 방법을 테스트하는 가장 좋은 방법은 무엇입니까? 의 가치를 바꾸기 위해 private
변수 Reflection
.
public class Bar extends Foo{
public int process(){
Object value = getX();
return process2(value);
}
public int process2(Object value){
...
}
}
public class BarTest extends TestCase{
public testProcess(){
Bar bar = new Bar();
Mockobj mo = new Mockobj();
int result = bar.process2(mo);
...
}
}
내가 한 일은 결국 위였습니다. 약간 추악합니다 ... 제임스 솔루션은 이것보다 훨씬 낫습니다 ...