모범 사례 : setUp () 또는 선언시 JUnit 클래스 필드를 초기화 하시겠습니까?


120

이렇게 선언 할 때 클래스 필드를 초기화해야합니까?

public class SomeTest extends TestCase
{
    private final List list = new ArrayList();

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

아니면 이와 같은 setUp ()에서?

public class SomeTest extends TestCase
{
    private List list;

    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        this.list = new ArrayList();
    }

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

더 간결하고 최종 필드를 사용할 수 있기 때문에 첫 번째 형식을 사용하는 경향이 있습니다. 설정 을 위해 setUp () 메서드를 사용할 필요 가없는 경우 에도 여전히 사용해야하며 그 이유는 무엇입니까?

설명 : JUnit은 테스트 메서드 당 한 번씩 테스트 클래스를 인스턴스화합니다. 즉 list, 내가 선언 한 위치에 관계없이 테스트 당 한 번 생성됩니다. 또한 테스트간에 시간적 종속성이 없음을 의미합니다. 따라서 setUp ()을 사용하는 데 이점이없는 것 같습니다. 그러나 JUnit FAQ에는 setUp ()에서 빈 컬렉션을 초기화하는 많은 예제가 있으므로 이유가 있다고 생각합니다.


2
대답은 JUnit 4 (선언에서 초기화)와 JUnit 3 (setUp 사용)에서 다릅니다. 이것이 혼란의 근원입니다.
Nils von Barth 2015

답변:


99

기본 테스트 템플릿 과 같은 JUnit FAQ의 예제에 대해 구체적으로 궁금하다면 테스트 중인 클래스가 setUp 메서드 (또는 테스트 메서드)에서 인스턴스화되어야한다는 것이 모범 사례라고 생각합니다. .

JUnit 예제가 setUp 메서드에서 ArrayList를 만들면 모두 testIndexOutOfBoundException, testEmptyCollection 등과 같은 경우를 사용하여 해당 ArrayList의 동작을 테스트합니다. 누군가가 수업을 작성하고 제대로 작동하는지 확인하는 관점입니다.

자신의 클래스를 테스트 할 때도 동일한 작업을 수행해야합니다. setUp 또는 테스트 메서드에서 개체를 만들어 나중에 중단 할 경우 적절한 출력을 얻을 수 있습니다.

반면에 테스트 코드에서 Java 컬렉션 클래스 (또는 다른 라이브러리 클래스)를 사용하는 경우 테스트를 원하기 때문이 아니라 테스트 픽스처의 일부일뿐입니다. 이 경우 의도 한대로 작동한다고 안전하게 가정 할 수 있으므로 선언에서 초기화해도 문제가되지 않습니다.

그만한 가치를 위해, 저는 상당히 크고 몇 년 된 TDD 개발 코드베이스를 작업합니다. 우리는 습관적으로 테스트 코드의 선언에있는 것들을 초기화하는데, 제가이 프로젝트에 참여한 지 1 년 반 동안 문제가 발생하지 않았습니다. 그래서 이것이 합리적이라는 증거가 적어도 몇 가지 있습니다.


45

나는 스스로 파헤 치기 시작했고 setUp(). 실행 중에 예외가 발생 setUp()하면 JUnit은 매우 유용한 스택 추적을 인쇄합니다. 반면에 객체 생성 중에 예외가 발생하면 오류 메시지는 JUnit이 테스트 케이스를 인스턴스화 할 수없고 실패가 발생한 행 번호를 볼 수 없다는 메시지를 표시합니다. 아마도 JUnit이 리플렉션을 사용하여 테스트를 인스턴스화하기 때문일 것입니다. 클래스.

이것은 결코 던지지 않을 것이기 때문에 빈 컬렉션을 만드는 예제에는 적용되지 않지만 setUp()메서드 의 장점입니다 .


18

Alex B의 답변 외에도.

특정 상태에서 리소스를 인스턴스화하려면 setUp 메서드를 사용해야합니다. 생성자에서이 작업을 수행하는 것은 타이밍 문제 일뿐만 아니라 JUnit이 테스트를 실행하는 방식으로 인해 각 테스트 상태는 하나를 실행 한 후 지워집니다.

JUnit은 먼저 각 테스트 메서드에 대한 testClass의 인스턴스를 만들고 각 인스턴스가 생성 된 후 테스트 실행을 시작합니다. 테스트 방법을 실행하기 전에 설정 방법을 실행하여 일부 상태를 준비 할 수 있습니다.

데이터베이스 상태가 생성자에서 생성되는 경우 모든 인스턴스는 각 테스트를 실행하기 전에 서로 바로 db 상태를 인스턴스화합니다. 두 번째 테스트에서 테스트는 더티 상태로 실행됩니다.

JUnits 라이프 사이클 :

  1. 각 테스트 메서드에 대해 다른 테스트 클래스 인스턴스를 만듭니다.
  2. 각 testclass 인스턴스에 대해 반복합니다. 설정 호출 + testmethod 호출

두 가지 테스트 방법으로 테스트에서 일부 로깅을 사용하면 다음과 같은 결과를 얻을 수 있습니다. (숫자는 해시 코드입니다.)

  • 새 인스턴스 생성 : 5718203
  • 새 인스턴스 생성 : 5947506
  • 설정 : 5718203
  • TestOne : 5718203
  • 설정 : 5947506
  • TestTwo : 5947506

3
맞지만 주제에서 벗어났습니다. 데이터베이스는 기본적으로 전역 상태입니다. 이것은 내가 직면 한 문제가 아닙니다. 나는 적절하게 독립적 인 테스트의 실행 속도에만 관심이 있습니다.
Craig P. Motlin

이 초기화 순서는 중요한주의 사항 인 JUnit 3에서만 적용됩니다. JUnit 4에서는 테스트 인스턴스가 느리게 생성되므로 선언 또는 설정 메서드에서 초기화가 모두 테스트 시간에 발생합니다. 또한 일회성 설정의 경우 @BeforeClassJUnit 4에서 사용할 수 있습니다 .
Nils von Barth 2015

11

JUnit 4 :

  • 를 들어 클래스에서 테스트 , A의 초기화 @Before캐치 실패에, 방법.
  • 들어 다른 클래스 , 선언에 초기화 ...
    • ... 간결성 final을 위해 질문에 명시된대로 필드를 표시하기 위해
    • ... 실패 할 수있는 복잡한 초기화 가 아니라면 ,이 경우를 사용 @Before하여 실패를 포착합니다.
  • 들어 글로벌 상태 (특히. 느린 초기화 데이터베이스 같은)를 사용 @BeforeClass하지만, 조심 시험 사이의 종속성.
  • 단일 테스트에 사용되는 객체의 초기화는 물론 테스트 메서드 자체에서 수행되어야합니다.

@Before메서드 또는 테스트 메서드 에서 초기화 하면 실패에 대한 더 나은 오류보고를 얻을 수 있습니다. 이것은 테스트 대상 클래스 (깨질 수 있음)를 인스턴스화하는 데 특히 유용하지만 파일 시스템 액세스 ( "파일을 찾을 수 없음") 또는 데이터베이스에 연결 ( "연결 거부")과 같은 외부 시스템을 호출하는 데에도 유용합니다.

이다 허용 간단한 표준 항상 사용 가질 @Before선언에 항상 초기화 (삭제 오류하지만 자세한 정보) 또는 (간결을하지만 혼란 오류를 제공) 복잡한 코딩 규칙을 따라야하기 어려운 있기 때문에, 이것은 큰 문제가되지 않습니다.

초기화 setUp는 JUnit 3의 유물로, 모든 테스트 인스턴스가 열심히 초기화되어 값 비싼 초기화를 수행하면 문제 (속도, 메모리, 리소스 고갈)가 발생합니다. 따라서 모범 사례는 setUp테스트가 실행될 때만 실행되는 에서 값 비싼 초기화를 수행하는 것입니다. 이것은 더 이상 적용되지 않으므로을 사용할 필요가 훨씬 적습니다 setUp.

여기에는 특히 Craig P. Motlin (질문 자체 및 자체 답변), Moss Collum (테스트중인 클래스) 및 dsaff가 주도한 몇 가지 다른 답변이 요약되어 있습니다.


7

JUnit 3에서 필드 이니셜 라이저는 테스트가 실행되기 전에 테스트 메소드 당 한 번 실행 됩니다. 필드 값이 메모리에서 작고 설정 시간이 거의 걸리지 않고 전역 상태에 영향을주지 않는 한 필드 이니셜 라이저를 사용하는 것은 기술적으로 괜찮습니다. 그러나 이것이 유지되지 않으면 첫 번째 테스트가 실행되기 전에 많은 메모리 또는 필드 설정 시간을 소비하고 메모리가 부족할 수도 있습니다. 이러한 이유로 많은 개발자는 항상 setUp () 메서드에서 필드 값을 설정합니다.이 메서드는 엄격하게 필요하지 않은 경우에도 항상 안전합니다.

JUnit 4에서 테스트 객체 초기화는 테스트 실행 직전에 발생하므로 필드 이니셜 라이저를 사용하는 것이 더 안전하고 권장되는 스타일입니다.


흥미 롭군. 처음에 설명한 동작은 JUnit 3에만 적용됩니까?
Craig P. Motlin 2011 년

6

귀하의 경우 (목록 작성) 실제로 차이가 없습니다. 하지만 일반적으로 setUp ()을 사용하는 것이 좋습니다. Junit이 예외를 올바르게보고하는 데 도움이되기 때문입니다. 테스트의 생성자 / 이니셜 라이저에서 예외가 발생하면 테스트 실패 입니다. 그러나 설정 중에 예외가 발생하면 테스트 설정시 일부 문제로 생각하는 것이 당연하며 junit은이를 적절하게보고합니다.


1
잘했다. 항상 setUp ()에서 인스턴스화하는 데 익숙해지면 걱정할 문제가 한 가지 더 적습니다. 예를 들어, 내 컬렉션에서 내 fooBar를 어디서 인스턴스화해야합니까? 그것은 당신이 지켜야 할 일종의 코딩 표준입니다. 목록이 아니라 다른 인스턴스화를 통해 이점을 얻을 수 있습니다.
Olaf Kock

@Olaf 코딩 표준에 대한 정보를 제공해 주셔서 감사합니다. 저는 그것에 대해 생각하지 않았습니다. 나는 코딩 표준에 대한 Moss Collum의 아이디어에 더 동의하는 경향이 있습니다.
Craig P. Motlin

5

나는 가장 자주 설정 방법을 사용하지 않는 가독성을 선호합니다. 기본 설정 작업에 시간이 오래 걸리고 각 테스트 내에서 반복되는 경우 예외를 만듭니다.
그 시점에서 @BeforeClass주석을 사용하여 해당 기능을 설정 방법으로 이동합니다 (나중에 최적화).

@BeforeClass설정 방법을 사용한 최적화의 예 : 일부 데이터베이스 기능 테스트에 dbunit을 사용합니다. 설정 방법은 데이터베이스를 알려진 상태 (매우 느리게 ... 데이터 양에 따라 30 초-2 분)로 설정합니다. 주석이 달린 설정 방법으로이 데이터를로드 @BeforeClass한 다음 각 테스트 내에서 데이터베이스를 다시로드 / 초기화하는 것과는 반대로 동일한 데이터 집합에 대해 10-20 개의 테스트를 실행합니다.

Junit 3.8 (예제에 표시된대로 TestCase 확장)을 사용하려면 주석을 추가하는 것보다 약간 더 많은 코드를 작성해야하지만 "클래스 설정 전에 한 번 실행"은 여전히 ​​가능합니다.


1
나는 또한 가독성을 선호하기 때문에 +1. 그러나 두 번째 방법이 최적화라고 확신하지 못합니다.
Craig P. Motlin

@Motlin 설정으로 최적화 할 수있는 방법을 명확히하기 위해 dbunit 예제를 추가했습니다.
Alex B

데이터베이스는 기본적으로 전역 상태입니다. 따라서 db 설정을 setUp ()으로 이동하는 것은 최적화가 아니므로 테스트를 제대로 완료하는 데 필요합니다.
Craig P. Motlin

@Alex B : Motlin이 말했듯이 이것은 최적화가 아닙니다. 코드에서 초기화가 수행되는 위치를 변경하는 것이지만 몇 번이나 얼마나 빨리 수행되지는 않습니다.
Eddie

나는 "@BeforeClass"주석의 사용을 암시하려고했다. 명확하게하기 위해 예제를 편집합니다.
Alex B

2

각 테스트는 개체의 새로운 인스턴스를 사용하여 독립적으로 실행되기 때문에 setUp()개별 테스트 및 tearDown(). 이것이 setUp()방법 을 사용하는 것이 좋은 이유 중 하나입니다 (다른 사람들이 제시 한 이유 외에도) .

참고 : JUnit 테스트 객체가 정적 상태를 유지하는 것은 좋지 않습니다! 추적 또는 진단 목적 이외의 목적으로 테스트에서 정적 변수를 사용하는 경우 JUnit의 목적 중 일부를 무효화하는 것입니다. 즉, 테스트가 임의의 순서로 실행될 수 있으며 각 테스트는 신선하고 깨끗한 상태.

사용의 장점 setUp()은 모든 테스트 메서드에서 초기화 코드를 잘라내어 붙여 넣을 필요가없고 생성자에 테스트 설정 코드가 없다는 것입니다. 귀하의 경우에는 약간의 차이가 있습니다. 빈 목록을 만드는 것만으로도 안전하게 표시하거나 생성자에서 간단한 초기화를 수행 할 수 있습니다. 그러나 당신과 다른 사람들이 지적했듯이, 실패 할 경우 진단 스택 덤프를 얻을 수 있도록 던질 수있는 모든 Exception작업을 수행해야 setUp()합니다.

귀하의 경우 빈 목록을 생성하는 경우 제안하는 것과 동일한 방식으로 수행합니다. 선언 지점에 새 목록을 할당합니다. 특히 이런 식으로 final테스트 클래스에 적합한 경우 표시 할 수있는 옵션이 있기 때문 입니다.


1
최종 표시를 위해 개체 생성 중에 목록 초기화를 실제로 지원하는 첫 번째 사람이기 때문입니다. 정적 변수에 대한 내용은 질문과 관련이 없습니다.
Craig P. Motlin

@Motlin : 사실, 정적 변수에 대한 내용은 주제에서 약간 벗어납니다. 내가 왜 그것을 추가했는지는 잘 모르겠지만, 그 당시에는 내가 첫 단락에서 말한 내용의 확장 인 적절 해 보였습니다.
Eddie

의 장점은 final질문에 언급되어 있습니다.
Nils von Barth 2015

0
  • 상수 값 (픽스처 또는 어설 션에서 사용)은 선언에서 초기화되어야하며 final변경되지 않아야합니다.

  • 테스트 대상 개체는 설정 방법으로 초기화해야합니다. 물론 지금은 설정하지 않아도 나중에 설정할 수 있습니다. init 메소드에서 인스턴스화하면 변경이 쉬워집니다.

  • 모의 된 경우 테스트 대상 개체의 종속성은 직접 인스턴스화해서는 안됩니다. 오늘날 모의 프레임 워크는 리플렉션을 통해 인스턴스화 할 수 있습니다.

mock에 의존하지 않는 테스트는 다음과 같습니다.

public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Before
    public void beforeEach() {
       some = new Some(new Foo(), new Bar());
    } 

    @Test
    public void populateList()
         ...
    }
}

분리 할 종속성이있는 테스트는 다음과 같습니다.

@RunWith(org.mockito.runners.MockitoJUnitRunner.class)
public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Mock
    Foo fooMock;

    @Mock
    Bar barMock;

    @Before
    public void beforeEach() {
       some = new Some(fooMock, barMock);
    }

    @Test
    public void populateList()
         ...
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.