애플리케이션 서버에서 실행되지 않을 때 단위 테스트는 데이터 소스를 어떻게 설정해야 합니까?

StackOverflow https://stackoverflow.com/questions/831760

문제

도움을 주셔서 감사합니다.많은 분들이 내 전체 접근 방식이 잘못되었거나 하위 수준 코드가 컨테이너에서 실행 중인지 여부를 알 필요가 없어야 함을 나타내는 답변을 게시했습니다(예상했어야 함).나는 동의하는 편이다.그러나 저는 복잡한 레거시 애플리케이션을 다루고 있으며 현재 문제에 대해 대대적인 리팩토링을 수행할 수 있는 옵션이 없습니다.

한 걸음 물러서서 원래 질문의 동기가 된 질문을 해보겠습니다.

JBoss에서 실행되는 레거시 애플리케이션이 있고 하위 수준 코드를 일부 수정했습니다.수정을 위해 단위 테스트를 만들었습니다.테스트를 실행하려면 데이터베이스에 연결해야 합니다.

레거시 코드는 다음과 같은 방식으로 데이터 소스를 가져옵니다.

(jndiName은 정의된 문자열입니다.)

Context ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup(jndiName);

내 문제는 단위 테스트에서 이 코드를 실행할 때 컨텍스트에 정의된 데이터 소스가 없다는 것입니다.이에 대한 나의 해결책은 내가 애플리케이션 서버에서 실행 중인지 확인하고, 그렇지 않은 경우 테스트 DataSource를 생성하여 반환하는 것이었습니다.앱 서버에서 실행 중인 경우 위의 코드를 사용합니다.

그래서 나의 진짜 질문은 다음과 같습니다이를 수행하는 올바른 방법은 무엇입니까?테스트 중인 코드가 실행 중인 위치를 인식할 필요가 없도록 단위 테스트에서 적절한 데이터 소스를 반환하도록 컨텍스트를 설정할 수 있는 승인된 방법이 있습니까?


맥락:내 원래 질문:

JBoss에서 실행 중인지 여부를 알아야 하는 일부 Java 코드가 있습니다.코드가 컨테이너에서 실행 중인지 여부를 알려주는 정식 방법이 있나요?

나의 첫 번째 접근 방식은 실험을 통해 개발되었으며 초기 컨텍스트를 얻고 특정 값을 찾을 수 있는지 테스트하는 것으로 구성됩니다.

private boolean isRunningUnderJBoss(Context ctx) {
        boolean runningUnderJBoss = false;
        try {
            // The following invokes a naming exception when not running under
            // JBoss.
            ctx.getNameInNamespace();

            // The URL packages must contain the string "jboss".
            String urlPackages = (String) ctx.lookup("java.naming.factory.url.pkgs");
            if ((urlPackages != null) && (urlPackages.toUpperCase().contains("JBOSS"))) {
                runningUnderJBoss = true;
            }
        } catch (Exception e) {
            // If we get there, we are not under JBoss
            runningUnderJBoss = false;
        }
        return runningUnderJBoss;
    }

Context ctx = new InitialContext();
if (isRunningUnderJboss(ctx)
{
.........

이제 이것이 작동하는 것처럼 보이지만 해킹처럼 느껴집니다.이를 수행하는 "올바른" 방법은 무엇입니까?이상적으로는 JBoss뿐만 아니라 다양한 애플리케이션 서버와 작동하는 방식을 원합니다.

도움이 되었습니까?

해결책

전체 접근 방식은 나에게 잘못된 느낌이 듭니다. 앱이 어떤 컨테이너를 실행 중인지 알아야한다면 뭔가 잘못하고 있습니다.

스프링을 사용할 때 나는 Tomcat에서 Weblogic으로, 아무것도 바꾸지 않고 뒤로 이동할 수 있습니다. 적절한 구성으로 Jboss와 동일한 트릭을 수행 할 수 있다고 확신합니다. 그것이 내가 촬영 한 목표입니다.

다른 팁

전체 개념은 앞쪽으로 돌아 왔습니다. 하위 레벨 코드는 이러한 종류의 테스트를 수행하지 않아야합니다. 다른 구현이 필요한 경우 관련 시점에서 전달하십시오.

의존성 주입 (스프링, 구성 파일 또는 프로그램 인수 등)과 공장 패턴의 일부 조합이 일반적으로 가장 효과적입니다.

예를 들어 귀나 전쟁이 개발, 테스트 또는 제작 환경에 들어가는 경우 구성 파일을 설정하는 개미 스크립트에 인수를 전달합니다.

아마도 이와 같은 것 (못생긴하지만 작동 할 수 있습니다)

 private void isRunningOn( String thatServerName ) { 

     String uniqueClassName = getSpecialClassNameFor( thatServerName );
     try { 
         Class.forName( uniqueClassName );
     } catch ( ClassNotFoudException cnfe ) { 
         return false;
     }
     return true;
 } 

그만큼 getSpecialClassNamefor 메소드는 각 응용 프로그램 서버에 대해 고유 한 클래스를 반환합니다 (더 많은 앱 서버가 추가 될 때 새 클래스 이름을 반환 할 수 있음)

그런 다음 다음과 같이 사용합니다.

  if( isRunningOn("JBoss")) {
         createJBossStrategy....etcetc
  }
Context ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup(jndiName);

누가 InitialContext를 구성합니까?해당 구성은 테스트하려는 코드 외부에 있어야 합니다. 그렇지 않으면 컨텍스트를 모의할 수 없습니다.

레거시 애플리케이션에서 작업 중이라고 하셨으므로 먼저 클래스에 컨텍스트나 데이터 소스를 쉽게 종속성 주입할 수 있도록 코드를 리팩터링하세요.그러면 해당 클래스에 대한 테스트를 더 쉽게 작성할 수 있습니다.

클래스를 구성하는 코드를 리팩터링할 때까지 아래 코드와 같이 두 개의 생성자를 사용하여 레거시 코드를 전환할 수 있습니다.이렇게 하면 Foo를 더 쉽게 테스트할 수 있고 Foo를 사용하는 코드를 변경하지 않고 유지할 수 있습니다.그런 다음 코드를 천천히 리팩터링하여 이전 생성자를 완전히 제거하고 모든 종속성을 주입할 수 있습니다.

public class Foo {
  private final DataSource dataSource;
  public Foo() { // production code calls this - no changes needed to callers
    Context ctx = new InitialContext();
    this.dataSource = (DataSource) ctx.lookup(jndiName);
  }
  public Foo(DataSource dataSource) { // test code calls this
    this.dataSource = dataSource;
  }
  // methods that use dataSource
}

하지만 리팩토링을 시작하기 전에 이를 뒷받침할 몇 가지 통합 테스트가 필요합니다.그렇지 않으면 DataSource 조회를 생성자로 이동하는 등의 간단한 리팩토링으로도 문제가 발생하는지 여부를 알 수 없습니다.그런 다음 코드가 더 좋아지고 테스트 가능해지면 단위 ​​테스트를 작성할 수 있습니다.(정의에 따라 테스트가 파일 시스템, 네트워크 또는 데이터베이스에 닿는 경우 단위 테스트가 아니라 통합 테스트입니다.)

단위 테스트의 장점은 초당 수백 또는 수천 개의 빠르게 실행되며 한 번에 하나의 동작만 테스트하는 데 매우 중점을 둔다는 것입니다.이렇게 하면 자주 실행할 수 있으므로(한 줄을 변경한 후 모든 단위 테스트 실행을 주저하면 너무 느리게 실행됩니다) 빠른 피드백을 얻을 수 있습니다.그리고 매우 집중적이기 때문에 실패한 테스트의 이름만 봐도 프로덕션 코드의 정확히 어디에 버그가 있는지 알 수 있습니다.

통합 테스트의 이점은 모든 부품이 올바르게 연결되었는지 확인한다는 것입니다.그것도 중요하지만 데이터베이스를 건드리는 등의 작업으로 인해 속도가 매우 느려지기 때문에 자주 실행할 수는 없습니다.그러나 지속적 통합 서버에서는 적어도 하루에 한 번 실행해야 합니다.

이 문제를 해결하는 몇 가지 방법이 있습니다. 하나는 단위 테스트 중일 때 컨텍스트 개체를 클래스에 전달하는 것입니다. 메소드 서명을 변경할 수없는 경우, 비교 컨텍스트의 생성을 보호 된 방법으로 리팩터하고 메소드를 재정의하여 조롱 된 컨텍스트 객체를 반환하는 서브 클래스를 테스트하십시오. 그것은 적어도 수업을 시험하에 놓을 수 있으므로 거기에서 더 나은 대안을 리팩터링 할 수 있습니다.

다음 옵션은 데이터베이스 연결을 컨테이너에 있는지 여부를 알 수있는 공장을 만들고 각 경우에 적절한 작업을 수행하는 것입니다.

생각해야 할 한 가지는 - 일단 컨테이너 에서이 데이터베이스 연결을 받으면 어떻게해야합니까? 더 쉽지만 전체 데이터 액세스 계층을 전달 해야하는 경우 단위 테스트는 아닙니다.

단위 테스트에서 레거시 코드 이동 방향에 대한 추가 도움을 얻으려면 Michael Feather 's를 살펴 보는 것이 좋습니다. 레거시 코드로 효과적으로 작동합니다.

이 작업을 수행하는 깨끗한 방법은 라이프 사이클 리스너를 구성하는 것입니다. web.xml. 원하는 경우 글로벌 플래그를 설정할 수 있습니다. 예를 들어 a를 정의 할 수 있습니다 ServletContextListener 당신의 web.xml 그리고 contextInitialized 방법, 컨테이너 내부에서 실행되는 글로벌 플래그를 설정하십시오. 글로벌 플래그가 설정되지 않으면 컨테이너 내부에서 실행되지 않습니다.

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