본문 바로가기

JAVA

[Java] Unit Test - 1.테스트 개요

728x90
반응형

1. JUnit의 핵심

테스트 클래스가 되는 조건은 두가지이다.

  - public 클래스여야 한다.

  - 파라미터를 받지 않는 생성자를 제공해야 한다.

 

 

테스트 메소드가 되기 위한 조건은 4가지 이다.

  - @Test 어노테이션이 부여되어야 한다.

  - public 메서드여야 한다.

  - 파라미터를 받지 않아야 한다.

  - 반환형은 void 여야 한다.

import static org.junit.Assert.assertEquals;

import org.junit.Test;

 

public class CalculatorTest {

    @Test

    public oid testAdd() {

        Calculator calculator = new Calculator();

        double result = calculator.add(1, 1);

 

        assertEquals(2, result, 0);

    }

}

  • JUnit은 각 @Test메서드를 호출할 때마다 테스트 클래스의 인스턴스를 새로 생성한다. 
  • 테스트 메서드들을 독립된 메모리 공간에서 실행시킴으로써, 혹시 모를 의도치 않은 부작용을 방지하기 위함이다. 
  • 테스트 검증에는 JUnit의 Assert 클래스에 정의된 assert 메서드를 사용한다.

 

 

  • assert 메서드

 

assert 메서드설명

assertArrayEquals("message", A, B) 배열 A와 B가 일치함을 확인한다.
assertEquals("message", A, B) 객체 A와 B가 일치함을 확인한다. B를 파라미터로 A의 equals() 메서드를 호출한다.(A.equals(B))
assertSame("message", A, B) 객체 A와 B가 같은 객체임을 확인한다. assertEquals 메서드는 두 객체의 값이 같은가를 검사하는데 반해(equals 메서드 사용), assertSame 메서드는 두 객체가 동일한, 즉 하나의 객체인가를 검사한다.
assertTrue("message", A) 조건 A가 참(true)임을 확인한다.
assertNotNull("message", A) 객체 A가 null이 아님을 확인한다.
  • 여러개의 테스트 클래스를 동시에 실행해야 할 때는, 테스트 스위트라 불리는 또 다른 객체를 생성한다. 
  • 테스트 스위트는 특수한 형태의 테스트 러너로, 테스트 클래스와 똑같은 방식으로 실행할 수 있다. 
  • 테스트 클래스와 스위트, 러너의 동작 방식을 이해한다면 어떠한 클래스도 거뜬히 작성해내는 경지에 오를 것이다. 
  • 이 세 객체는 JUnit 프레임워크를 지탱하는 척추에 비유될 수 있다.

 

  • JUnit 핵심 객체

JUnit 개념역할

Assert 테스트하려는 조건을 명시한다. assert 메서드는 조건이 만족되면 아무 일도 없었다는 듯이 조용히 지나가며, 만족하지 못하면 얘외를 던진다.
Test @Test 어노테이션이 부여된 메서드로, 하나의 테스트를 뜻한다. Junit은 먼저 메서드를 포함하는 클래스의 인스턴스를 만들고, 어너테이션된 메서드를 찾아 호출한다.
Test 클래스 @Test 메서드를 포함한 클래스이다.
Suite 스위트는 여러 테스트 클래스를 하나로 묶는 수단을 제공한다.
Runner 러너는 테스트를 실행시킨다.

파라메터화(parameterized) 테스트

// 1.

@RunWith(value=Parameterized.class)

public class ParameterizedTest {

 

    // 2.

    private double expected;

    private double valueOne;

    private double valueTwo;

 

    // 3.

    @Parameters

    public static Collection<Integer[]> getTestParameters() {

        return Arrays.asList(new Integer[][] {

                {2, 1, 1},  // 예상 값, 값 1, 값 2

                {3, 2, 1},  // 예상 값, 값 1, 값 2

                {4, 3, 1}   // 예상 값, 값 1, 값 2

            }

        );

    }

 

    // 4.

    public ParameterizedTest(double expected, double valueOne, double valueTwo) {

        this.expected = expected;

        this.valueOne = valueOne;

        this.valueTwo = valueTwo;

    }

 

    // 5.

    @Test

    public void sum() {

        // 6.

        Calculator cal = new Calculator();

 

        // 7.

        assertEquals(expected, cal.add(valueOne, valueTwo), 0);

    }

}

  • 파라메터화 테스트 러너를 사용하려면 다음 조건을 만족시켜야 한다.
    1. 테스트 클래스에는 반드시 @RunWith 어노테이션을 부여해야 하며, 그 파라미터로는 Parameterized 클래스를 사용한다.
    2. 테스트에 사용될 값을 인스턴스 변수로 선언하고
    3. @Parameters라 표시된 메서드가 하나 필요하다.
      • @Paramters public static java.util.Collection 이어야 한다.
      • 어떠한 파라미터도 입력 받아서는 안된다.
      • Collection의 원소는 배열(array)이고, 길이는 모두 같아야 한다.
    4. 그 길이는 유일한 public 생성자가 받는 파라미터의 수와도 일치해야 한다.
    5. 테스트 메서드 sum()을 구현했다.
    6. sum 메서드는 Calculator 프로그램의 인스턴스를 생성하고,
    7. 앞서 제공된 파라미터들을 사용해 결과를 단언한다.
  • JUnit 런타임 작동 방식
    1. 정적 메서드인 getTestParameters를 호출해 컬렉션 객체를 얻는다.
      • 컬렉션에 저장된 배열의 수만큼 순환한다.
    2. JUnit은 유일한 public 생성자를 찾는다.
      • 이때, public 생성자가 두 개 이상이면 AssertionError를 던진다.
      • 이제 찾은 생성자에 배열의 원소를 파라미터로 넣어 호출한다.
      • 첫번째 배열 {2, 1, 1}을 파라미터로 입력 파라미터 세 개짜리 생성자를 호출할 것이다.
    3. @Test 메서드를 호출한다.

 

"테스트는 두려움을 지루함으로 변화시켜주는 프로그래머의 돌이다." 켄트 백 : 테스트 주도 개발

부트스트랩 단계

import static org.junit.Assert.*;

import org.junit.Test;

 

// 1.

public class TestDefaultController {

    private DefaultController controller;

 

    // 2.

    @Before

    public void instantiate() throws Exception {

        controller = new DefaultController();

    }

 

    // 3.

    @Test

    public void testMethod() {

        // 4.

        throw new RuntimeException("implement me");

    }

}

  1. 테스트 클래스의 이름에는 Test라는 접두어를 붙인다.
  2. DefaultController 인스턴스를 만들기 위해 @Before 어노테이션 메서드를 이용한다.
    • @Before 메서드는 각 테스트 메서드 사이에서 호출되는 JUnit의 기본 확장 포인트이다.
  3. 실행할 테스트 케이스가 하나도 없으면 안되므로 더미 테스트 메서드를 추가하였다.
    • 이후에 테스트를 수행할 기반이 다 갖춰졌다는 확신이 서면, 그 때 진짜 테스트 메서드를 추가하면 된다.
    • 이 테스트는 실행은 되지만, 실패하기 때문에 다음 단계는 테스트가 성공하도록 수정할 것이다.
  4. 구현이 덜 끝난 테스트 코드는 예외를 던져야 한다는 모범 사례를 따르도록 하자.
    • 이를 통해 테스트가 통과하는 것을 막고, 구현할 게 아직 남아있음을 잊지 않을 수 있다.

@Before와 @After

- @Before와 @After 어노테이션이 부여된 메서드들은 매 @Test 메서드가 호출되기 바로 직전과 직후에 실행된다.

- @Test 메서드의 성공 실패 여부는 상관없다.

- 때문에 필요한 도메인 객체를 미리 생성해두거나, 특정 상태로 미리 셋팅하기 위한 공통 코드를 뽑아두기에 최적의 장소라 할 수 있다.

- 원한다면 여러 개의 @Before와 @After 메서드를 정의할 수 있지만, 이들 사이의 실행 순서를 정하는 방법은 없으니 주의하기 바란다.

 

- @BeforeClass와 @AfterClass라는 어노테이션도 제공한다.

- 클래스 안에 정의된 모든 @Test 메서드들을 수행하기 전과 수행한 후에 오직 한 번씩만 호출된다.

- @BeforeClass와 @AfterClass 메서드도 원한다면 여러 개 정의할 수 있지만, 실행 순서는 정의되어 있지 않다.

 

- @Before/@After, @BeforeClass/@AfterClass 메서드 모두는 반드시 public이어야 하며,

- @BeforeClass와 @AfterClass 메서드는 public이면서 동시에 static이여야 한다.

 

@Test

- 테스트 메서드에 의미 있는 이름을 부여하라.

- assert 호출 시에는 실패 원인을 기술하라.

- 하나의 @Test 메서드에서는 하나의 테스트만 수행하라.

- 실패할 가능성이 있는 모든 것을 테스트하라.

- 테스트를 통해 코드를 개선하라.

- 예외 테스트도 읽기 쉽게 만들어라.

- 테스트를 건너뛸 때는 반드시 그 이유를 명시하라.

 

- @Test(expected=SomeExcception.class) : 테스트 목적이 예외 상황 테스트이고, 발생되길 기대하는 예외가 있으니, 그 예외가 무엇인지 명시할 필요가 있다.

- @Test(timeout=130) : 주어진 시간 내에 완료되지 못한 테스트는 실패라고 판단하며, 밀리초 단위로 제한 시간을 설정할 수 있다.

- @Ignore(value="테스트 제외 이유 설명") : 때로는 일부 테스트들은 건너뛰는 것이 좋을 때도 있다.

 

햄크레스트 (Hamcrest)

  • 수 많은 유용한 매처(matcher) 객체(제약이나 술어라 불린다)를 제공하며, 다양한 언어로 포팅되어 있다.

  • 테스트 프레임워크는 아니고, 그 보다는 간단한 매칭 규칙을 선언적으로 정의할 때 큰 도움이 된다.

public class HamcrestTest {

    private List<String> values;

 

    @Before

    public void setUpList() {

        values = new ArrayList<String>();

        values.add("x");

        values.add("y");

        values.add("z");

    }

 

    @Test

    public void testWithoutHamcrest() {

        assertTrue(values.contains("one") || values.contains("two") || values.contains("three"));

    }

}

import static org.junit.Assert.assertThat;

import static org.hamcrest.CoreMatchers.anyOf;

import static org.hamcrest.Corematchers.equalTo;

import static org.junit.JUnitMatchers.hasItem;

 

public class HamcrestTest {

    private List<String> values;

 

    @Before

    public void setUpList() {

        values = new ArrayList<String>();

        values.add("x");

        values.add("y");

        values.add("z");

    }

 

    @Test

    public void testWithHamcrest() {

        assertTrue(values, hasItem(anyOf(equalTo("one"), equalTo("two"), equalTo("three"))));

    }

}

  • 매처의 강력한 기능 중 하나가 서로 다른 매처를 조합해 사용하는 능력이다.

  • assert 코드에 Hamcrest 매처를 사용할지 말지는 순전히 개인 취향이다.

  • 표준 assert 매커니즘 대비 Hamcrest가 제공하는 독특한 장점은, assert가 실패했을 때 사람이 읽을 수 있는 형태의 설명을 제공한다는 것이다.

 

  • 가장 널리 쓰이는 Hamcrest 매처

코어(core)의미

anything 무엇이든 상관없이 모든 것을 가리킴. assert 문의 가독성을 높이고 싶을 때 유용하다.
is 문장 가독성 향상 목적으로만 사용된다.
allOf 포함한 모든 매처가 매칭되는지 검사한다(&& 연산자와 동일).
anyOf 포함한 매처 중 어느 하나라도 매치되는 것이 있는지 검사한다(
not 포함한 매처들의 의미를 부정한다(! 연산자와 동일)
instanceOf, isCompatibleType 객체들이 호환 가능한 타입인지 확인한다.
sameInstance 객체 신원을 확인한다.
notNullValue, nullValue 값이 null인지(혹은 null 아닌지) 검사한다.
hasProperty 자바빈이 특정 속성을 갖는지 검사한다.
hasEntry, hasKey, hasValue 주어진 Map이 명시된 entry, key, value를 포함하는지 검사한다.
hasItem, hasItems 주어진 컬렉션이 명시한 아이템, 혹은 아이템들을 포함하는지 검사한다.
closeTo, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEquals 주어진 숫자가 또 다른 숫자에 근접한지, 더 큰지, 더 크거나 같은지, 더 작은지, 더 작거나 같은지 검사한다.
equalToIgnoringCase 주어진 문자열이 다른 문자열과 일치하는지 검사한다(대소문자 무시).
equalToIgnoringWhiteSpace 주어진 문자열이 다른 문자열과 일치하는지 검사한다(공백문자 무시).
containsString, endsWith, startWith 주어진 문자열이 다른 문자열을 포함하는지, 그 문자열로 시작하거나 끝나는지 검사한다.

 

단위 테스트가 필요한 이유

  1. 기능 테스트보다 훨씬 높은 테스트 커버리지 달성이 가능하다.

  2. 팀 생산성을 향상시킨다.

  3. 회귀 테스트(regression test)를 수행하고 디버깅의 필요성을 줄여준다.

  4. 리팩터링과 코드 수정 시 올바로 하고 있다는 확신을 준다.

  5. 구현 품질을 향상시킨다.

  6. 기대하는 행위를 문서화한다.

  7. 코드 커버리지 등 각종 측정을 가능하게 한다.

 

테스트의 종류

  1. 통합 테스트(integration test)

  2. 기능 테스트(functional test)

  3. 스트레스 테스트(stress test)와 부하 테스트(load test)

  4. 인수 테스트(acceptance test)

 

단위 테스트의 세 가지 맛

  1. 논리 단위 테스트 : 한 메서드에 집중한 테스트, 목 객체(Mock Object)나 스텁(Stub)을 이용해 테스트 메서드의 경계를 제어할 수 있다.

  2. 통합 단위 테스트 : 실제 운영 환경(혹은 그 일부)에서 컴포넌트 간 연동에 치중한 테스트, 예를 들어 데이터베이스를 사용하는 코드라면 데이터 베이스를 효과적으로 호출하는가를 테스트할 수 있다.

  3. 기능 단위 테스트 : 자극 반응(Stimulus Response)을 확인하기 위해 통합 단위 테스트의 경계를 확장한 테스트. 예를 들어 인증된 클라이언트만 접근 가능한 보안 웹 페이지를 가진 웹 애플리케이션을 가정해보자. 만약 로그인을 하지 않은 채 보안 페이지에 접근하면, 클라이언트를 로그인 페이지로 돌려보내야 한다. 이 상황을 검사하려면, 기능 단위 테스트(Functional Unit Test)는 이 페이지에 접속하려는 HTTP 요청을 보내고, 응답으로 재전송 코드(HTTP 상태 코드 302)가 오는지 확인하는 방법이 있다.

 

테스트 주도 개발 (Test-driven development, TDD)

  • 자동화 테스트가 실패했을 때와 코드 중복을 제거하려 할 때에만 새로운 코드를 작성하도록 권하는 프로그래밍 실천법이다.

  • TDD의 목표는 '작동하는 깨끗한 코드(Clean code that works)'이다.

  1. 개발 주기 조정하기

    • 전통적인 개발 주기는 코딩, 테스트, (반복), 커밋 순

    • TDD 실천자들은 테스트, 코드, (반복), 커밋 순

    • 테스트가 설계를 이끌고 메서드의 첫 대상이 된다.

    • 코드를 설계 > 동작 방식을 문서화 > 단위 테스트를 수행한다.

    • 테스트 > 코드, 리팩터링, (반복), 커밋

    • 지속적으로 회귀 테스트를 수행하라.

  2. TDD 실천으로 가는 두 단계

    1. 새 코드를 작성하기 앞서 실패하는 자동화 테스트를 작성하라.

    2. 중복을 제거하라.

 

2. Unit Test 원칙

Unit Test 에서의 F.I.R.S.T 원칙

  - 일반적으로 단위테스트 코드를 작성할 때 5가지 원칙을 강조한다.

 

  F - Fast : 테스트 코드를 실행하는 일은 오래 걸리면 안된다.

  I - Independent/Isolated : 독립적으로 실행이 되어야 한다. 또는 고립시킨다.

  R - Repeatable : 반복 가능해야 한다.

  S - Self Validating : 스스로 검증 가능해야 한다.

  T - Timely : 바로 사용 가능해야 한다.

 

3. 무엇을 테스트 할 것인가?

Right-BICEP

  - [Right]-BICEP : 결과가 올바른가?

  - Right-[B]ICEP : 경계 조건(boundary conditions)은 맞는가?

    - CORRRECT 기억법

      - [C]onformance : 값이 기대한 양식을 준수하고 있는가?

      - [O]rdering : 값의 집합이 적절하게 정렬되거나 정렬되지 않았나?

      - [R]Range : 이성적인 최솟값과 최댓값 안에 있는가?

      - [R]eference : 코드 자체에서 통제할 수 없는 어떤 외부 참조를 포함하고 있는가?

      - [E]xistence : 값이 존재하는가? (널이 아니거나(non-null), 0이 아니거나(nonzero), 집합이 존재하는가 등)?

      - [C]ardinality : 정확히 충분한 값들이 있는가?

      - [T]ime : 모든 것이 순서대로 일어나는가? 정확한 시간에? 정시에?

  - Right-B[I]CEP : 역 관계(inverse relationship)를 검사할 수 있는가?

  - Right-BI[C]EP : 다른 수단을 활용하여 교차 검사(cross-check)할 수 있는가?

  - Right-BIC[E]P : 오류 조건(error conditions)을 강제로 일어나게 할 수 있는가?

  - Right-BICE[P] : 성능 조건(performance characteristics)은 기준에 부합하는가?

728x90
반응형

'JAVA' 카테고리의 다른 글

[Java] Unit Test - 3.테스트 작성  (0) 2020.02.06
[Java] Unit Test - 2.테스트 개요  (0) 2020.02.05
[JAVA] @Scheduled Cron 표현식  (0) 2019.08.09
[Java] 날짜 시간 계산 하기  (0) 2019.08.06
[Java] String, StringBuffer, StringBuilder  (0) 2019.07.17