테스트주도개발 02
Updated:
테스트 주도개발 2장
JUnit
- 에릭 감마와 켄트 백이 탄생시킨 JUnit은 Java 단위 테스트 프레임워크다. TDD의 근간.
* 기능
- 테스트 결과가 예상과 같은지를 판별해주는 단정문(assertions)
- 여러 테스트에서 공용으로 사용할 수 있는 테스트 픽스처(text fixture)
- 테스트 작업을 수행할 수 있게 해주는 테스트 러너 (test runner)
* 테스트 픽스처
- 반복적인 테스트를 수행하며 매번 동일한 결과를 얻게 돕는 ‘기반이 되는 상태나 환경’을 의미
- 일관된 테스트 실행환경 or 테스트 컨텍스트라고 부름
- 테스트 케이스에 사용할 객체 인스턴스 생성, DB 연동 참조 선언 등의 작업 or 작업의 결과로 만들어진 대상을 통칭한다.
- 1장에서 사용한 setUp, tearDown 메소드를 테스트 픽스처 메소드라고 한다.
* 테스트 케이스와 테스트 메소드
- 두 개의 용어는 혼용되어 사용된다. 정확히는 단위 테스트 케이스, 단위 테스트 메소드가 올바른 명칭.
- 테스트 케이스는 테스트 작업에 대한 시나리오적인 의미
- 테스트 메소드는 JUnit의 메소드를 지칭
JUnit3
규칙
- TestCase를 상속받는다. ex) AcountTest extends TestCase
- 테스트 메소드의 이름은 반드시 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가지 :
- 모든 단위 테스트 통과하는 시간을 짧게 유지하는것
- 시스템의 핵심을 끝에서 끝까지 간단하게 관통하는 테스트가 빨리 나오는 것
- TDD란 개발기간은 조금 늘어나지만 결함을 획기적으로 줄일 수 있는 기법