테스트 가능성을 설계 할 때 정적 유틸리티 클래스를 처리하는 방법


62

우리는 시스템을 테스트 할 수 있고 대부분 TDD를 사용하여 개발하도록 시스템을 설계하려고합니다. 현재 우리는 다음과 같은 문제를 해결하려고 노력하고 있습니다.

여러 곳에서 ImageIO 및 URLEncoder (표준 Java API)와 같은 정적 헬퍼 메소드와 대부분 정적 메소드 (Apache Commons 라이브러리와 같은)로 구성된 다양한 라이브러리를 사용해야합니다. 그러나 이러한 정적 도우미 클래스를 사용하는 메서드를 테스트하는 것은 매우 어렵습니다.

이 문제를 해결하기위한 몇 가지 아이디어가 있습니다.

  1. 정적 클래스 (예 : PowerMock)를 조롱 할 수있는 모의 프레임 워크를 사용하십시오. 이것은 가장 간단한 해결책 일지 모르지만 어떻게 든 포기하는 느낌입니다.
  2. 모든 정적 유틸리티 주위에 인스턴스화 가능한 랩퍼 클래스를 작성하여이를 사용하는 클래스에 삽입하십시오. 이것은 비교적 깨끗한 솔루션처럼 들리지만 결국 래퍼 클래스를 많이 만들지 않을까 걱정됩니다.
  3. 이 정적 헬퍼 클래스에 대한 모든 호출을 대체 할 수있는 함수로 추출하고 실제로 테스트하려는 클래스의 서브 클래스를 테스트하십시오.

그러나 나는 이것이 TDD를 할 때 많은 사람들이 직면 해야하는 문제 일 것이라고 생각합니다. 따라서이 문제에 대한 해결책이 이미 있어야합니다.

이러한 정적 도우미를 사용하는 클래스를 테스트 가능하게 유지하는 가장 좋은 전략은 무엇입니까?


"신뢰할 수있는 공식 출처"의 의미를 잘 모르겠지만 @berecursive가 그의 답변에 쓴 내용에 동의합니다. PowerMock은 이유가 있기 때문에 래퍼 클래스를 직접 작성하지 않으려는 경우 "포기"처럼 느껴지지 않아야합니다. 최종 및 정적 방법은 단위 테스트 (및 TDD)와 관련하여 고통입니다. 몸소? 나는 당신이 설명한 방법 2를 사용합니다.
Deco

"신뢰할 수있는 공식 출처"는 질문에 대한 현상금을 시작할 때 선택할 수있는 옵션 중 하나 일뿐입니다. 내가 실제로 의미하는 것은 : TDD 전문가가 쓴 기사에 대한 경험이나 언급. 또는 같은 문제에 직면 한 사람에 의한 어떤 종류의 경험 ...

답변:


34

(여기에 "공식적인"출처는 없습니다. 잘 테스트하는 방법에 대한 사양이없는 것 같습니다. 제 의견은 도움이 될 것입니다.)

이러한 정적 메소드가 진정한 종속성을 나타내는 경우 랩퍼를 작성하십시오. 그래서 다음과 같은 것들을 위해 :

  • ImageIO
  • HTTP 클라이언트 (또는 기타 네트워크 관련)
  • 파일 시스템
  • 현재 시간 얻기 (종종 주입이 도움이되는 가장 좋아하는 예)

... 인터페이스를 만드는 것이 합리적입니다.

그러나 Apache Commons의 많은 메소드는 조롱하거나 위조 해서는 안됩니다 . 예를 들어, 문자열 목록 사이에 쉼표를 추가하여 문자열 목록을 결합하는 방법을 사용하십시오. 없다 아무 소용 이 조롱의은 - 단지 정적 호출이 정상적인 작업을 할 수 있습니다. 정상적인 행동을 원하지 않거나 대체 할 필요가 없습니다. 외부 리소스 나 다루기 어려운 것을 다루지 않고 단지 데이터 일뿐입니다. 결과는 예측 가능하며 결과가 제공하는 것 이외의 다른 것이 되기를 원하지 않을 것입니다.

필자는 HTTP와 같은 논리적 종속성의 큰 혼란에 대한 진입 점이 아니라 예측 가능한 "순수한"결과 (base64 또는 URL 인코딩과 같은)를 사용하여 실제로 편리한 방법 인 모든 정적 호출을 제거했다고 생각합니다. 진정한 의존성으로 옳은 일을하는 데 실용적입니다.


20

이것은 분명히 의견이 많은 질문 / 답변이지만 가치가 있다고 생각하면 2 센트를 넣을 것이라고 생각했습니다. TDD 스타일 방법 2의 관점에서 분명히 편지에 따르는 접근법입니다. 방법 2에 대한 논쟁은 해당 클래스 중 하나의 구현 (예 : ImageIO동등한 라이브러리) 을 대체하려는 경우 해당 코드를 사용하는 클래스에 대한 자신감을 유지하면서 수행 할 수 있다는 것입니다.

그러나 앞에서 언급했듯이 많은 정적 메서드를 사용 하면 많은 래퍼 코드를 작성하게됩니다. 이것은 장기적으로 나쁜 것이 아닙니다. 유지 보수성 측면에서 이에 대한 주장이 분명히 있습니다. 개인적으로 나는이 접근법을 선호합니다.

그러나 PowerMock은 이유가 있습니다. 정적 메소드가 관련된 테스트는 매우 고통스럽기 때문에 PowerMock의 시작이라는 것은 매우 잘 알려진 문제입니다. PowerMock을 사용하는 것보다 모든 도우미 클래스를 포장하는 데 얼마나 많은 작업이 필요한지 옵션을 평가해야한다고 생각합니다. PowerMock을 사용하는 것이 '포기하는 것'이라고 생각하지 않습니다. 클래스를 줄 바꿈하면 큰 프로젝트에서 더 많은 유연성을 얻을 수 있다고 생각합니다. 공개 계약 (인터페이스)이 많을수록 의도와 구현 사이를 더 명확하게 구분할 수 있습니다.


1
확실하지 않은 추가 문제 : 래퍼를 구현할 때 래핑 된 클래스의 모든 메소드 또는 현재 필요한 메소드를 모두 구현 하시겠습니까?

3
민첩한 아이디어를 따르는 경우 가장 간단한 작업을 수행하고 필요하지 않은 작업은 피해야합니다. 따라서 실제로 필요한 방법 만 노출해야합니다.
Assaf Stone

@AssafStone 동의 함

PowerMock에주의하십시오. 메소드를 조롱하기 위해 해야하는 모든 클래스 조작에는 많은 오버 헤드가 있습니다. 광범위하게 사용하면 테스트 속도가 훨씬 느려집니다.
bcarlso

DI / IoC 라이브러리를 채택하여 테스트 / 마이그레이션을 결합하는 경우 래퍼 작성을 많이해야합니까?

4

이 문제를 해결하고이 문제를 겪고있는 모든 사람을위한 참고 자료로, 문제를 해결하기로 결정한 방법을 설명하겠습니다.

기본적으로 # 2 (정적 유틸리티의 래퍼 클래스)로 설명 된 경로를 따릅니다. 그러나 원하는 출력을 생성하기 위해 필요한 데이터를 유틸리티에 제공하기에는 너무 복잡한 경우에만 사용합니다 (예 : 메소드를 조롱해야하는 경우).

StringEscapeUtils, 필요한 문자열을 쉽게 제공 할 수 있기 때문에 Apache Commons와 같은 간단한 유틸리티를 위해 래퍼를 작성할 필요가 없으며 정적 메소드에 모의 객체를 사용하지 않습니다 (작성해야 할 경우) 랩퍼 클래스와 랩퍼 인스턴스를 조롱).



1

나는 주요 보험 회사에서 일하고 있으며 소스 코드는 최대 400MB의 순수 자바 파일로갑니다. 우리는 TDD에 대해 생각하지 않고 전체 응용 프로그램을 개발해 왔습니다. 올해 1 월부터 각 개별 구성 요소에 대한 junit 테스트를 시작했습니다.

우리 부서의 가장 좋은 솔루션은 시스템에 의존 할 수있는 (C로 작성된) 일부 JNI 메소드에서 Mock 객체를 사용하는 것이므로 모든 OS에서 매번 결과를 정확하게 예측할 수는 없었습니다. 우리는 지원하는 모든 OS에 대해 응용 프로그램의 각 개별 모듈을 테스트하기 위해 특수하게 조롱 된 클래스와 JNI 메소드의 특정 구현을 사용하는 것 외에 다른 옵션이 없었습니다.

그러나 정말 빠르며 지금까지 꽤 잘 작동했습니다. 나는 그것을 추천합니다-http: //www.easymock.org/


1

환경 (웹 서비스 엔드 포인트, DB에 액세스하는 dao 계층, http 요청 매개 변수를 처리하는 컨트롤러)으로 인해 테스트하기 어려운 오브젝트가 있거나 오브젝트를 개별적으로 테스트하려는 경우 오브젝트가 서로 상호 작용하여 목표를 달성합니다. 당신은 그 객체를 조롱합니다.

정적 메서드를 조롱해야 할 필요성은 나쁜 냄새이며, 응용 프로그램을 더 객체 지향적으로 디자인해야하며 단위 테스트 유틸리티 정적 메서드는 프로젝트에 많은 가치를 부여하지 않으며 래퍼 클래스는 상황에 따라 좋은 접근 방법이지만 시도해보십시오. 정적 메소드를 사용하는 객체를 테스트합니다.


1

때로는 옵션 4를 사용합니다

  1. 전략 패턴을 사용하십시오. 플러그 가능 인터페이스의 인스턴스에 구현을 위임하는 정적 메소드를 사용하여 유틸리티 클래스를 작성하십시오. 구체적인 구현을 구현하는 정적 이니셜 라이저를 코딩하십시오. 테스트를 위해 모의 구현을 연결하십시오.

이 같은.

public class DateUtil {
    public interface ITimestampGenerator {
        long getUtcNow();
    }

    class ConcreteTimestampGenerator implements ITimestampGenerator {
        public long getUtcNow() { return System.currentTimeMillis(); }
    }

    private static ITimestampGenerator timestampGenerator;

    static {
        timestampGenerator = new ConcreteTimeStampGenerator;
    }

    public static DateTime utcNow() {
        return new DateTime(timestampGenerator.getUtcNow(), DateTimeZone.UTC);
    }

    public static void setTimestampGenerator(ITimestampGenerator t) {...}

    // plus other util routines, which may or may not use the timestamp generator 
}

이 접근법에 대해 내가 좋아하는 것은 유틸리티 메소드를 정적으로 유지한다는 것입니다. 코드 전체에서 클래스를 사용하려고 할 때 나에게 딱 맞는 느낌입니다.

Math.sum(17, 29, 42);
// vs
new Math().sum(17, 29, 42);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.