문제

Java에 대한 나의 좌우명은 "Java가 정적 블록을 가지고 있기 때문에 사용해야한다는 의미는 아닙니다." 농담을 제외하고, Java에는 악몽 테스트를하는 많은 트릭이 있습니다.내가 가장 싫어하는 두 가지는 익명 클래스와 정적 블록입니다.우리는 정적 블록을 사용하는 많은 레거시 코드를 가지고 있으며 이는 단위 테스트 작성을 추진할 때 짜증나는 점 중 하나입니다.우리의 목표는 최소한의 코드 변경으로 이 정적 초기화에 의존하는 클래스에 대한 단위 테스트를 작성할 수 있는 것입니다.

지금까지 동료들에게 제안한 것은 정적 블록의 본문을 개인용 정적 메서드로 이동하고 이를 호출하는 것입니다. staticInit.그러면 이 메서드는 정적 블록 내에서 호출될 수 있습니다.단위 테스트를 위해 이 클래스에 의존하는 다른 클래스를 쉽게 모의할 수 있습니다. staticInit JMockit을 사용하면 아무것도 하지 않습니다.예를 들어 보겠습니다.

public class ClassWithStaticInit {
  static {
    System.out.println("static initializer.");
  }
}

로 변경됩니다

public class ClassWithStaticInit {
  static {
    staticInit();
  }

  private static void staticInit() {
    System.out.println("static initialized.");
  }
}

JUnit에서 다음을 수행할 수 있습니다.

public class DependentClassTest {
  public static class MockClassWithStaticInit {
    public static void staticInit() {
    }
  }

  @BeforeClass
  public static void setUpBeforeClass() {
    Mockit.redefineMethods(ClassWithStaticInit.class, MockClassWithStaticInit.class);
  }
}

그러나 이 솔루션에는 자체적인 문제도 있습니다.넌 달릴 수 없어 DependentClassTest 그리고 ClassWithStaticInitTest 실제로 정적 블록을 실행하기를 원하기 때문에 동일한 JVM에서 ClassWithStaticInitTest.

이 작업을 수행하는 방법은 무엇입니까?아니면 JMockit 기반이 아닌 더 나은 솔루션이 더 깨끗하게 작동할 것이라고 생각하시나요?

도움이 되었습니까?

해결책

이 문제가 발생하면 일반적으로 정적 메서드를 보호하여 수동으로 호출할 수 있다는 점을 제외하고는 설명하신 것과 동일한 작업을 수행합니다.게다가 메서드가 문제 없이 여러 번 호출될 수 있는지 확인합니다(그렇지 않으면 테스트가 진행되는 한 정적 초기화 프로그램보다 나을 것이 없습니다).

이는 합리적으로 잘 작동하며 정적 초기화 메서드가 내가 기대하는/원하는 작업을 수행하는지 실제로 테스트할 수 있습니다.때로는 정적 초기화 코드를 갖는 것이 가장 쉽고 이를 대체하기 위해 지나치게 복잡한 시스템을 구축하는 것은 가치가 없습니다.

이 메커니즘을 사용할 때 보호된 메서드가 다른 개발자가 사용하지 않기를 바라는 마음으로 테스트 목적으로만 노출된다는 점을 문서화합니다.물론 이것은 실행 가능한 솔루션이 아닐 수도 있습니다. 예를 들어 클래스의 인터페이스가 외부적으로 표시되는 경우(다른 팀을 위한 일종의 하위 구성 요소 또는 공개 프레임워크로).하지만 이는 문제에 대한 간단한 해결책이며 설정하는 데 타사 라이브러리가 필요하지 않습니다(제가 좋아하는 것입니다).

다른 팁

파워모크 EasyMock과 Mockito를 확장하는 또 다른 모의 프레임워크입니다.PowerMock을 사용하면 쉽게 할 수 있습니다 원치 않는 행동 제거 클래스(예: 정적 초기화 프로그램)에서.귀하의 예에서는 JUnit 테스트 케이스에 다음 주석을 추가하기만 하면 됩니다.

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("some.package.ClassWithStaticInit")

PowerMock은 Java 에이전트를 사용하지 않으므로 JVM 시작 매개변수를 수정할 필요가 없습니다.jar 파일과 위의 주석을 추가하기만 하면 됩니다.

이것은 좀 더 "고급" JMockit에 들어갈 것입니다.JMockit에서 정적 초기화 블록을 재정의할 수 있다는 것이 밝혀졌습니다. public void $clinit() 방법.따라서 이렇게 변경하는 대신

public class ClassWithStaticInit {
  static {
    staticInit();
  }

  private static void staticInit() {
    System.out.println("static initialized.");
  }
}

우리는 떠나는 게 낫겠다 ClassWithStaticInit 있는 그대로 두고 다음을 수행합니다. MockClassWithStaticInit:

public static class MockClassWithStaticInit {
  public void $clinit() {
  }
}

이를 통해 실제로 기존 클래스를 변경하지 않을 수 있습니다.

때때로 내 코드가 의존하는 클래스에서 정적 초기화 프로그램을 발견합니다.코드를 리팩토링할 수 없으면 다음을 사용합니다. 파워모크'에스 @SuppressStaticInitializationFor 정적 초기화 프로그램을 억제하는 주석:

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("com.example.ClassWithStaticInit")
public class ClassWithStaticInitTest {

    ClassWithStaticInit tested;

    @Before
    public void setUp() {
        tested = new ClassWithStaticInit();
    }

    @Test
    public void testSuppressStaticInitializer() {
        asserNotNull(tested);
    }

    // more tests...
}

자세히 알아보기 원치 않는 행동 억제.

부인 성명:PowerMock은 내 동료 두 명이 개발한 오픈 소스 프로젝트입니다.

당신이 증상을 치료하고 있는 것처럼 들리네요:정적 초기화에 의존하는 잘못된 디자인.어쩌면 일부 리팩토링이 실제 솔루션일 수도 있습니다.이미 약간의 리팩토링을 수행한 것 같습니다. staticInit() 하지만 해당 함수는 정적 초기화 프로그램이 아닌 생성자에서 호출해야 할 수도 있습니다.정적 초기화 기간을 없앨 수 있다면 더 나을 것입니다.오직 귀하만이 이 결정을 내릴 수 있습니다(코드베이스를 볼 수 없습니다) 그러나 일부 리팩토링은 확실히 도움이 될 것입니다.

조롱의 경우 EasyMock을 사용하지만 동일한 문제가 발생했습니다.레거시 코드의 정적 초기화 프로그램으로 인한 부작용으로 인해 테스트가 어려워집니다.우리의 대답은 정적 초기화 프로그램을 리팩터링하는 것이었습니다.

Groovy에서 테스트 코드를 작성하고 메타프로그래밍을 사용하여 정적 메서드를 쉽게 모의할 수 있습니다.

Math.metaClass.'static'.max = { int a, int b -> 
    a + b
}

Math.max 1, 2

Groovy를 사용할 수 없다면 코드를 리팩토링해야 합니다(초기화 프로그램과 같은 것을 삽입해야 할 수도 있음).

친절한 감사

나는 당신이 정말로 정적 초기화 프로그램 대신 어떤 종류의 팩토리를 원한다고 가정합니다.

싱글톤과 추상 팩토리를 혼합하면 오늘날과 동일한 기능과 우수한 테스트 가능성을 얻을 수 있지만 그렇게 하면 상용구 코드가 상당히 많이 추가되므로 리팩터링을 시도하는 것이 더 나을 수 있습니다. 정적 문제를 완전히 없애거나 최소한 덜 복잡한 솔루션으로 해결할 수 있는 경우.

하지만 코드를 보지 않고는 가능한지 말하기가 어렵습니다.

저는 Mock 프레임워크에 대해 잘 알지 못하므로 제가 틀렸다면 정정해 주세요. 하지만 언급한 상황을 처리하기 위해 두 개의 다른 Mock 객체를 가질 수는 없나요?와 같은

public static class MockClassWithEmptyStaticInit {
  public static void staticInit() {
  }
}

그리고

public static class MockClassWithStaticInit {
  public static void staticInit() {
    System.out.println("static initialized.");
  }
}

그런 다음 다양한 테스트 사례에서 사용할 수 있습니다.

@BeforeClass
public static void setUpBeforeClass() {
  Mockit.redefineMethods(ClassWithStaticInit.class, 
                         MockClassWithEmptyStaticInit.class);
}

그리고

@BeforeClass
public static void setUpBeforeClass() {
  Mockit.redefineMethods(ClassWithStaticInit.class, 
                         MockClassWithStaticInit.class);
}

각기.

실제로 대답은 아니지만 궁금합니다. 호출을 "반전"할 수 있는 방법이 있습니까? Mockit.redefineMethods?
그러한 명시적인 메서드가 존재하지 않으면 다음과 같은 방식으로 다시 실행하면 안 되나요?

Mockit.redefineMethods(ClassWithStaticInit.class, ClassWithStaticInit.class);

그러한 메소드가 존재하는 경우 클래스에서 실행할 수 있습니다. @AfterClass 방법 및 테스트 ClassWithStaticInitTest 동일한 JVM에서 아무것도 변경되지 않은 것처럼 "원래" 정적 초기화 블록을 사용합니다.

하지만 이것은 단지 직감이므로 뭔가 빠졌을 수도 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top