테스트주도개발 02

Updated:

테스트 주도개발 2장

JUnit

  • 에릭 감마와 켄트 백이 탄생시킨 JUnit은 Java 단위 테스트 프레임워크다. TDD의 근간.
* 기능
  1. 테스트 결과가 예상과 같은지를 판별해주는 단정문(assertions)
  2. 여러 테스트에서 공용으로 사용할 수 있는 테스트 픽스처(text fixture)
  3. 테스트 작업을 수행할 수 있게 해주는 테스트 러너 (test runner)
* 테스트 픽스처
  • 반복적인 테스트를 수행하며 매번 동일한 결과를 얻게 돕는 ‘기반이 되는 상태나 환경’을 의미
  • 일관된 테스트 실행환경 or 테스트 컨텍스트라고 부름
  • 테스트 케이스에 사용할 객체 인스턴스 생성, DB 연동 참조 선언 등의 작업 or 작업의 결과로 만들어진 대상을 통칭한다.
  • 1장에서 사용한 setUp, tearDown 메소드를 테스트 픽스처 메소드라고 한다.
* 테스트 케이스와 테스트 메소드
  • 두 개의 용어는 혼용되어 사용된다. 정확히는 단위 테스트 케이스, 단위 테스트 메소드가 올바른 명칭.
  • 테스트 케이스는 테스트 작업에 대한 시나리오적인 의미
  • 테스트 메소드는 JUnit의 메소드를 지칭

JUnit3

규칙

  1. TestCase를 상속받는다. ex) AcountTest extends TestCase
  2. 테스트 메소드의 이름은 반드시 test로 시작해야 한다. ex) testGetAccount()

구성요소 1: 테스트 픽스처 메소드

  • setUp(), tearDown() : 각 테스트 메소드 실행전, 후로 실행

구성요소 2: 단정문

* assertEquals([message], expected, actual)
  • JUnit에서 다음과 같이 정의
static public void assertEquals(String message, Object expected, Object actual) {
    if (expected == null && actual == null)
    return;
    if (expected != null && expected.equals(actual))
    return;
    failNotEquals(message, expected, actual);
}

//assertEquals(0.3333, 1/3d, 0.00001); // 이 경우 double형의 오차 값을 보정
* ssertSame, assertNotSame([message], expected, actual)
  • 두 객체가 정말 동일한 객체인지 주소값으로 비교하는 단정문

  • 캐시, 싱글톤으로 만들어진 객체 비교

    • 싱글톤이란 : 특정 클래스의 인스턴스가 오직 하나만 생성될 수 있게 만들어주는 디자인 패턴. 이때 static으로 지정된 getInstance() 같은 메소드를 통해서만 객체에 접근이 가능하다. 일반적으로 객체 생성과 삭제에 비용이 많이 드는 객체를 싱글톤으로 만들어 놓아 효율을 높인다.
* assertTrue, assertFalse([message], expected)
  • 예상 값의 참/ 거짓을 판별
* assertNull, assertNotNull([message], expected)
  • 대상 값의 null 여부를 판단
* fail([message])
  • 이 메소드가 호출되면 테스트 케이스는 즉시 실패.
  • 테스트 케이스를 아직 작성하지 못했을 경우 사용하면 좋음

구성요소 3: 테스트 러너

  • junit.swingui.TestRunner.run(테스트클래스.class);
  • junit.textui.TestRunner.run(테스트클래스.class);
  • junit.awtui.TestRunner.run(테스트클래스.class);
  • JUnit 프레임워크는 독립적인 소프트웨어.

  • 때문에 명령행 프롬프트에서 실행하거나 shell scripit에서 실행 가능

  • 이를 위해 세가지 방식을 제공. Swing UI, 텍스트, Java AWT UI
  • 예제
import junit.framework.TestCase;

public class DisplayTest extends TestCase {
    public void testGetString() {
    	assertEquals("Happy", Display.getString());
    }
}
public static void main(String [] args){
    junit.swingui.TestRunner.run(DisplayTest.class);
    junit.textui.TestRunner.run(DisplayTest.class);
    junit.awtui.TestRunner.run(DisplayTest.class);
}
  • CMD에서 수행할 경우 다음과 같이 실행
    • java - CP junit.jar;. junit.textui.TestRunner DisplayTest

구성요소 4: 테스트 스위트

  • 여러 개의 테스트 케이스를 한번에 수행할 때 사용한다. (요즘 잘 사용 안함)
  • 테스트 스위트는 테스트 케이스와 다른 테스트 스위트를 포함시킬 수 있다.
  • 메소드는 반드시 public static Test suite()여야 한다.
  • 테스트 추가는 suite.addTestSuite(테스트 클래스.class) 형식을 갖는다.
class DisplaySuiteTest {
    public static void main(String [] args){
        junit.swingui.TestRunner.run(DisplaySuiteTest.class);
    }

	public static Test suite() {
        TestSuite suite = new TestSuite();
        suite.addTestSuite(DisplayTest.class); // 테스트 케이스 추가
        suite.addTestSuite(DisplayReceiverTest.class);
        suite.addTest(AnotherSuiteTest.suite()); // 다른 테스트 스위트를 포함할 경우
        return suite;
     }
}

* JUnit 테스트 클래스의 생성자

  • JUnit 프레임워크는 Java의 리플렉션을 사용해서 테스트 메소드를 실행할 때마다 테스트 클래스를 강제로 인스턴스화 한다.
    • 리플렉션 : Java에서는 리플렉션이라는 기능을 통해 인스턴스화된 객체 로부터 원래 클래스의 구조를 파악해내어 동적으로 조작하는 것이 가능하다. 마치 기계를 분해해서 마음대로 재구성하는 것처럼 말이다. 리플렉션을 이용하면 private 메소드나 필드까지도 마음대로 조작할 수 있다. 리플렉션을 사용하면, 이런 식으로 Java의 일반적인 규칙들을 무시할 수도 있기 때문에, 보통 한정적으로만 사용할 것을 권장한다. 그리고 시스템 비 용이 매우 많이 드는 기능이다. » 이건 뭔소리인지 모르겠다 일단 넘어가자;
  • 이유는 좋은 테스트 케이스는 다른 테스트 케이스 수행이나 수행 결과에 영향을 미치지 말아야 하기 때문

JUnit4

추가 된 텍스트 픽스처 메소드

예외 테스트

  • 예외 테스트를 지정했을 때, 해당 예외가 발생하지 않으면 테스트는 실패로 간주
@Test (expected=NumberFormatException.class)
public void testException(){
	String value = "a108";
	System.out.println(Integer.parseInt(value));
} 

테스트 시간 제한

  • 해당 시간 내에 메소드가 수행완료되지 않으면 실패한 케이스로 간주
@Test(timeout=5000)
public void testPingback() throws Exception {
	ciServer.sendNotimail();
	assertEquals(NOTIFICATED, ciServer.getState());
}

배열 지원

  • 값이 같더라도 순서가 다르면 실패
@Test
public void testArrayAssertEquals() throws Exception {
    String [] names = {"Tom", "JIMMY", "JOHIN"};
    String [] anotherNames = {"Tom", "JOHIN", "JIMMY"};
    assertArrayEquals(names, anotherNames);
}

RunWith

  • JUnit 테스트 클래스 내의 각 테스트 메소드 실행을 담당하고 있는 클래스를 테스트 러너라고 한다.
  • 테스트 러너는 테스트 클래스 구조에 맞게 테스트 메소드를 실행하고 결과를 표시
  • 눈에 보이지 않고 내부적으로 BlockJUnit4ClassRunner이라는 테스트 러너 클래스가 실행
  • 이클립스가 그 결과를 해석해서 우리가 볼 수 있게 화면으로 제공
  • @RunWith는 BlockJUnit4ClassRunner 대신에 @RunWith로 지정된 클래스를 이용해 클래스 내의 테스트 메소드들을 수행하도록 지정해주는 어노테이션. JUnit의 확장지점.
  • 즉, 자신만의 테스트 러너를 만들어 확장, 사용할 수 있도록 도와주는 것을 말함.
  • 스프링의 경우 SpringJUnit4ClassRunner가 이를 확장 기능을 이용한 대표적 사례.
  • @RunWith(SpringJUnit4ClassRunner.class) 지정하면 스프링에 만들어져 있는 @Repeat, @IfProfileValue을 사용 할 수 있음.
  • JUnit 내부에도 기본 러너를 확장한 대표 클래스의 경우 SuiteClasses

@SuiteClasses

  • Suite 클래스는 JUnit3의 static Test Suite() 메소드와 동일한 기능
  • 즉, @SuiteClasses를 이용하여 여러 개의 테스트 클래스를 일괄적으로 수행
//JUnit4
@RunWith(Suite.class)
@SuiteClasses(ATest.class, BTest.class, CTest.class)
public class ABCSuite {
}
//JUnit3
public class ABCSuite extends TestCase {
	public static Test suite() {
        TestSuite suite = new TestSuite();
        suite.addTestSuite(ATest.class);
        suite.addTestSuite(BTest.class);
        suite.addTestSuite(CTest.class);
        return suite;
	}
}

룰(Rule)

  • 하나의 테스트 클래스 내에서 각 테스트 메소드의 동작 방식을 재정의하거나 추가하기 위해 사용
규칙 설명
TemporaryFolder(임시폴더) 테스트 메소드 내에서만 사용 가능한 임시폴다,파일을 만들어줌
ExternalResource(외부자원) 외부 자원을 명시적으로 초기화
ErrorCollector(에러수집기) 테스트 실패에도 테스트를 중단하지 않고 진행
Verifier(검증자) 테스트 케이스와는 별개의 조건을 만들어서 확인
TestWatchman(테스트 감시자) 테스트 실행 중간에 사용자가 끼어들 수 있게 도움
TestName(테스트 이름) 테스트 메소드 이름을 알려줌
Timeout(타임아웃) 일괄적인 타임아웃 설정
ExpectedException(예상된 예외) 테스트 케이스 내에서 예외와 예외 메시지를 직접 확인할 때 사용

이론(Theory)

  • 테스트 데이터와 상관없이 작성 대상 메소드를 항상 유지해야 하는 논리적인 규칙을 표현할 때 사용

  • 외국 개발자도 어려워하고 실험적이라니.. 일단 패스

비교표현의 확장 : Hamcrest(햄크레스트)

  • Hamcrest는 Mock 저자들이 참여해 만들고 있는 Matcher 라이브러리
  • 테스트 표현식을 좀 더 문맥적으로 자연스럽고 우아하게 만들어줌
assertEquals( "YoungJim", customer.getName() );
assertThat( customer.getName(), is("YoungJim") );
  • equals보다 that이 조금 더 자연어(인간이 읽는 언어)로 가깝다.

* assertThat()

assertThat(테스트대상, Matcher구문);

assertThat(“메시지”, 테스트대상, Matcher구문);

적용 전 assertEquals(100, account.getBalance( ));
적용 후 assertThat(account.getBalance( ), is(equalTo(10000)));
적용 전 assertNotNull(resource.newConnection( ));
적용 후 assertThat(resource.newConnection( ), is(notNullValue( ));
적용 전 assertTrue(account.getBalance( ) > 0);
적용 후 assertThat(account.getBalance( ), isGreaterThan(0));
적용 전 assertTrue(user.getLoginName().indexOf (“Guest”) > -1);
적용 후 assertThat(user.getLoginName(), containsString(“Guest”));
* 코어(Core) : 오브젝트나 값들에 대한 기본적인 Matcher
메소드 설명 클래스명
anyting 어떤 오브젝트가 사용되는 일치한다고 판별 IsAnything
describedAs 테스트 실패 시에 보여줄 추가 메시지를 만들어줌 DescribedAs
equalTo 두 오브젝트가 동일한지 판별 IsEqual
is 내부적으로 equalTo와 동일. 가독성 증진용. Is
assertThat(entity, equalTo(expectedEntity));
assertThat(entity, is(equalTo(expectedEntity)));
assertThat( entity, is(expectedEntity) );
// 위에 세 문장은 의미가 동일

오브젝트(Object) : 오브젝트와 클래스를 비교하는 Matcher

메소드 설명 클래스명
hasToString toString 메소드의 값과 일치 여부를 판별한다. assertThat(account, hasToString(“Account”)); HasToString
instanceOf typeCompatibleWith 동일 인스턴스인지 타입 비교. 동일하거나 상위 클래스, 인터페이스인지 판별 IsInstanceOf IsCompatibleType
notNullValue nullValue Null인지, 아닌지를 판별 IsNull
sameInstance Object가 완전히 동일한지 비교. equals 비교가 아닌 ==(주소 비교)로 비교하는 것과 동일 IsSame

논리(Logical)

메소드 설명 클래스명
allOf 비교하는 두 오브젝트가 각각 여러 개의 다른 오브젝트를 포함하고 있을 경우 (예를 들어 collection) 서로 동일한지 판별 AllOf
anyOf allOf와 동일하나 하나라도 일치하면 true AnyOf
not 서로 다를 경우 IsNot

빈즈(Beans) : 자바 빈과 비교

메소드 설명 클래스명
hasProperty Java 빈즈 프로퍼티 테스트 HasProperty

컬렉션(Collection) : 배열과 컬렉션

메소드 설명 클래스명
arrary 두 배열의 요소 일치하는지 판별 IsArray
hasEntry, hasKey, hasValue Map 요소 포함 여부 IsMapContaining
hasItem, hasItems 특정 요소 포함 여부 IsCollectionContaining
hasItemInArray 배열 내에 대상 여부 IsArrayContaining

숫자(Number) : 수비교

메소드 설명 클래스명
closeTo 부동소수점 값 근사값 일치 여부 IsCloseTo
greaterThan, greaterThanOrEqualTo 값 비교. >, >= OrderingComparison
lessThan, lessThanEqualTo 값 비교. <, <= OrderingComparison

텍스트(Text) : 문자열 비교

메소드 설명 클래스명
containsString 문자열 포함 StringContains
startsWith 문자열 시작 StringStartsWith
endsWith 문자열 종료 StringEndsWith
equalToIgnoringCase 대소문자 구분 않고 비교 IsEqualIgnoringCase
equalToIgnoringWhiteSpace 문자열 공백 구분 않고 비교 IsEqualIgnoringWhiteSpace

Matchers

  • 사용자 편의를 위해 만들어짐
  • 위임 같은 느낌으로 Matchers 클래스 import해서 사용하면 위에 메소드들을 편하게 사용.
  • 아래는 정의 되어 있는 코드 예시
package org.hamcrest;

public class Matchers {
    public static <T> org.hamcrest.Matcher<T> allOf() {
    	return org.hamcrest.core.AllOf.<T>allOf(first, second, third,fourth);
    }
    public static <T> org.hamcrest.core.AnyOf<T> anyOf() {
   	 	return org.hamcrest.core.AnyOf.<T>anyOf(matchers);
    }
    public static <T> org.hamcrest.Matcher<T> is(org.hamcrest.Matcher<T> matcher) {
    	return org.hamcrest.core.Is.<T>is(matcher);
    }


** 인터뷰 내용에서 개인적 좋은 내용 **

  • TDD는 개발자의 체질적인 변화를 요구

  • TDD의 중요 혈자리 2가지 :

  1. 모든 단위 테스트 통과하는 시간을 짧게 유지하는것
  2. 시스템의 핵심을 끝에서 끝까지 간단하게 관통하는 테스트가 빨리 나오는 것
  • TDD란 개발기간은 조금 늘어나지만 결함을 획기적으로 줄일 수 있는 기법