티스토리 뷰

실습하기 - 은행 계좌(Account) 클래스 만들기

  • 실패(질문) - 성공(응답)- 정제(리팩토링)의 단계를 밟아가며, 실습 진행
  • 현재 계좌 클래스에서 필요한 기능은 위와 같다. 여기서 예상 복리 추가는 제외하고 실습을 한다.
  • 기능 요구사항과 유의 사항은 아래와 같다.

1. 테스트 케이스 작성

  • case 1 : 구현 대상 클래스의 외형에 해당하는 메소드들을 먼저 만들고 테스트 케이스를 일괄적으로 만드는 방식
  • case 2 : 테스트 케이스를 하나씩 추가해나가면서 구현 클래스를 점진적으로 만드는 방식

case 1의 방식을 사용했을 경우에는, 모든 테스트가 정상 통과하는 올 그린(All Green) 상태에 이르기까지 긴 시간이 걸릴 수 있기 때문에 나는 case 2번 방식으로 진행 할 예정이다.

 

2. 프로젝트 생성, 테스트 클래스 생성

  • Java 프로젝트 Bank_TDD를 생성한다.
  • 테스트 클래스로 사용할 AccountTest 클래스를 생성하고 Package 명은 test로 한다.
    • [File] -> [New] -> [JUnit Test Case]를 사용해서 생성해도 되지만, TDD에 익숙해지면 사용하도록 하자.

테스트 클래스 AccountTest

3-1. 첫 번째 단계(실패/질문) : 계좌 생성 테스트

  • 계좌 생성에 대한 시나리오는 다음과 같다.

계좌 생성에 대한 시나리오

  • 잔고 조회까지 한꺼번에 시나리오로 넣고 싶을 수도 있지만, TDD에서는 하나의 테스트 케이스가 하나의 기능을 테스트하도록 만드는 것이 기본 원칙이니 하나씩 단계별로 진행하도록 한다.
  • '계좌를 생성한다' 는 부분을 코드로 기술

  • Account 클래스를 만든적이 없으니 에러가 발생하는 것이 당연하다. 당장 이 문제를 해결 할 수도 있지만 그냥 넘어가도록 한다. 이런 식으로 진행하는 이유는 테스트 케이스 작성 시 흐름을 잃지 않기 위해서이다.

  • 만일 생성된 계좌가 null 이라면 예외를 발생시키도록 만들었다. 

3-2. 첫 번째 단계(성공/응답) : 계좌 생성 메소드 구현

Account 클래스 생성


  • Account 클래스를 생성해서 Account의 빨간줄은 사라졌지만 계좌 생성 실패 시 예외를 발생시키도록 만든 부분에서 빨간줄이 생겼다.

  • Add throws declaration을 통해 예외를 호출하는 쪽으로 던져버린다.

  • 호출 한곳 에서 Surround with try/catch를 통해 예외 처리를 해준다.

  • 현재까지의 코드는 다음과 같다.

  • 가독성을 높이는 차원에서 예외가 발생하면 '실패', 예외가 발생하지 않으면 '성공' 메시지가 나오도록 변경하자.

  • 예외가 발생하지 않았기 때문에 성공이라는 메시지가 출력된다.

3-3. 첫 번째 단계(정제)

  • 현재 첫 번째 단계에서는 정제 할 부분이 없다고 판단되어 그냥 넘어가도록 한다.

4. JUnit의 사용

  • JUnit 무엇?
    • 단위 테스트 프레임워크 중 하나이다.
    • 문자 혹인 GUI 기반으로 실행된다.
    • xUnit이라 불리는 스타일을 따른다. <assertEquals(예상 값, 실제 값)>
    • 결과는 성공(녹색), 실패(붉은색) 중 하나로 표시

  • JUnit을 사용하기 위해 코드를 수정하고 가도록 한다.
  • 코드 내에서 main 메소드를 지운 다음, @Test라고 testAccount() 메소드 위에 적는다.
  • @Test는 해당 메소드를 테스트 메소드로 지정하기 위한 애노테이션(annotation)이며, 이런 식의 애노테이션 지정은 JUnit 4 버전에서 사용하는 방식이다.

  • 에러가 발생하는데, JUnit이라는 단위 테스트 라이브러리가 클래스 경로, 혹은 빌드 경로에 포함되어 있지 않아서 발생하는 에러로 컴파일러가 참조할 수 있도록 경로에 추가해주면 된다.

  • 나는 JUnit 4 버전을 사용할 것이기 때문에 4버전을 클릭해서 추가한다.
  • 아래와 같이 JUnit4 라이브러리가 추가 된다.


  • 이제는 main 메소드 대신 JUnit 단위 테스트 프레임워크를 이용해 테스트 메소드를 실행한다.
  • [Run] -> [Run As] -> [JUnit Test]를 선택하면 아래와 같은 창이 뜬다.


  • 위의 if 문에서 account를 null과 비교했을 때 일치되는 경우는 발생하지 않는다. 단지 시나리오 흐름상 '검증'이라는 부분을 표현하기 위해 사용했을 뿐이다. 만일 계좌를 생성하는 부분에서 문제가 생긴다면 throw new Exception()을 하지 않아도 자동으로 예외를 던질 것이기 때문에 if 문을 지우도록 한다.

  • 결과는 성공적으로 녹색이 뜬다.


5-1. 두 번째 단계(실패/질문) : 잔고 조회

  • 잔고조회에 대한 시나리오는 다음과 같다.

잔고조회에 대한 시나리오

  • 잔고 조회를 구현하기 위해 testGetBalance() 메소드를 만들어 준다.

  • 현재 생성자와, getBalance() 가 구현이 되어 있지 않기 때문에 생성자와 메소드를 생성만 해 두자.

  • 수정 후 실행결과는 다음과 같다.


  • 오류와 실패의 차이는 무엇일까?
    • '실패'는 AsserEquals 등의 테스트 조건식을 만족시키기 못했다는 것을 의미한다. 또, 그로 인해 내부적으로 fail()이 호출됐다는 의미기도 하다.
    • '오류'는 테스트 케이스 수행 중 예상치 못한 예외가 발생해서 테스트 수행을 멈췄다는 것을 뜻한다.
    • 일반적으로 '오류'는 작성자가 의도하지 않은 예상치 못한 실패를 뜻하며, 이 경우 테스트 케이스 자체에 문제가 있음을 말한다.
    • 따라서 본인이 작성한 테스트 케이스에 '오류'로 인한 '실패'가 발생하고 있다면, 빠른 시일 내에 '실패'로 카운트 될 수 있게 만들어야 한다.
  • fail() 무엇?
    • fail()은 JUnit에서 제공하는 메소드인데, fail 메소드가 호출되면 해당 테스트 케이스는 그 순간 무조건 실패한다.
    • fail() 대신에 throw new Exception(); 형태로 작성 할 수 있다.

5-2. 두 번째 단계(성공/응답) : 잔고 조회 기능 구현

  • 실패하는 질문에 대한 응답으로 녹색 막대가 나오도록 getBalance 메소드를 구현한다.
  • 먼저 return 10000; 을 통해 하드코딩으로 값을 넘겨서 테스트 해 보도록 하자.

  • 실행결과 녹색 막대나 보임을 알 수 있다.


  • testGetBalance() 에도 새로운 테스트 항목을 추가해보자.

  • 추가 한 후 실행 결과를 보면 빨간 막대 보인다.
  • getBalance()에서 10000으로 하드코딩하여 값을 넘겼기 때문에 fail()이 발생했다.


  • getBalance()를 수정해보자.

  • 수정했지만 빨간 막대나 나타난다.
  • balance의 값을 생성자에서 초기화를 해주지 않았기 때문이다.


  • 생성자에서 값을 초기화해주자.

  • 다음과 같이 녹색 막대가 보임을 알 수 있다.

5-3. 두 번째 단계(정제) : 잔고 조회 정제

  • public Account(int i) 생성자에서 변수명 i는 의미가 명확하지 않기 때문에 money로 변경 시켜 준다.

  • testGetBalance()에서 JUnit 테스트 프레임워크에 제공하는 assertEquals()라는 메소드를 이용해서 코드를 간결하게 해준다.

  • 위의 과정을 정제하더라도 똑같이 녹색 막대가 보인다.


  • assertEquals(예상값, 실제값)
  • assertEquals("설명", 예상값, 실제값)
    • 예상값과 실제값은 다양한 타입으로 오버라이드되어 있기 때문에 대부분의 타입에 대해 편리하게 사용 가능하다.
    • assertEquals(10000, accout.getBalance());
    • assertEquals("10000원으로 계좌 생성 후 잔고 조회", 10000, account.getBalance());
  • 만약 값이 일치 하지 않는다면 fail()을 던진다.

6-1. 세 번째 단계(실패/질문) : 입금과 출금 테스트

  • 입금하기(deposit)와 출금하기(withdraw) 테스트 케이스 작성

  • 테스트 케이스 작성한 후 실행을 하면 빨간막대가 보임을 알 수 있다.
  • 해당 메소드에 구현이 되어 있지 않기 때문에 빨간막대가 보이는 것은 당연하다.

6-2. 세 번째 단계(성공/응답) : 입금과 출금 기능 구현

  • deposit()과 withdraw() 메소드에 기능을 구현해 준다.

  • 구현을 하고 실행을 해 보면 녹색 막대가 보이는 것을 볼 수 있다.

6-3. 세 번째 단계(정제) : 입금과 출금 정제

  • deposit(int i)와 withdraw(int i)에서 i 값의 의미가 명확하지 않기 때문에 money로 변경해준다.


  • 이번에는 AccountTest 클래스의 소스 구조를 정제해보자.
  • Account account = new Account(10000);에서 account 변수에 커서를 대고 'ctrl + 1' 을 눌러 Convert Local Variable to field(지역변수 필드로) 를 선택해 준다.


  • 선택했다면 Account account 의 모든 Account를 제거 해주면 아래와 같은 코드가 된다.

  • 수정 후에도 여전히 녹색 막대가 출력된다.


  • 위에 수정한 소스 코드를 보면 매 테스트 메소드마다 account 변수를 초기화하는 부분이 존재한다. 이 부분을 setup() 이라는 메소드로 추출하여 정제해보자.

  • 중복되는 account = new Account(10000);을 블록 지정하고 오른쪽 마우스를 클릭하고 [Refactor] -> [Extract Method...] 을 클릭한다.

  • 메소드 이름을 setup으로 설정하고 OK를 클릭하면 중복되는 부분들이 모두 setup() 으로 변경됨을 알 수 있다.

  • 정제 후 다시 실행을 해 보면 여전히 녹색 막대가 보임을 알 수 있다.


  • setup() 메소드를 @Before 라고 지정하고 setup() 메소드를 제거한 후 실행 해 보자.
  • @Before라고 표시된 메소드는 @Test로 표시된 각 테스트 케이스가 실행되기 전에 먼저 실행된다.
  • 여기서 주의 할 점은 setup()의 접근 지정자를 private -> public으로 변경을 해 주어야 에러가 발생하지 않는다.

  • 정제 후 실행을 해 보면 여전히 녹색 막대가 보이는 것을 알 수 있다.


7. 최종 실습 코드

  • AccountTest.java
// AccountTest.java

package test;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;

public class AccountTest {
	
	private Account account;
	@Before
	public void setup() {
		account = new Account(10000);
	}
	@Test
	public void testAccount() throws Exception {
		account = new Account();
	}
	
	@Test
	public void testGetBalance() throws Exception {
		assertEquals(10000, account.getBalance());
		
		account = new Account(1000);
		assertEquals(1000, account.getBalance());
		
		account = new Account(0);
		assertEquals(0, account.getBalance());
	}

	@Test
	public void testDeposit() throws Exception {
		account.deposit(1000);
		assertEquals(11000, account.getBalance());
	}
	@Test
	public void testWithdraw() throws Exception {
		account.withdraw(1000);
		assertEquals(9000, account.getBalance());
	}
	
	public static void main(String[] args) {
		AccountTest test = new AccountTest();
		try {
			test.testAccount();
			test.testGetBalance();
			test.testDeposit();
			test.testWithdraw();
			
		} catch(Exception e) {
			System.out.println("실패");
			return;
		}
		System.out.println("성공");
	}
}
  • Account.java
package test;

public class Account {
	
	private int balance;

	public Account() {
	}
	
	public Account(int money) {
		this.balance = money;
	}

	public int getBalance() {
		return this.balance;
	}

	public void deposit(int i) {
		this.balance += i;
	}

	public void withdraw(int i) {
		this.balance -= i;
	}
}

8. JUnit의 단정문, 어노테이션

  • 대표적인 단정문
    • assertArrayEquals(a,b) : 배열 a와 b가 일치함을 확인
    • assertEquals(a,b) : 객체 a와 b의 값이 같은지 확인
    • assertSame(a,b) : 객체 a와 b가 같은 객체임을 확인
    • assertTrue(a) : a가 참인지 확인
    • assertNotNull(a) : a객체가 null이 아님을 확인
  • 어노테이션
    • @Test : 이 메소드는 테스트 대상 메소드임을 선언
    • @Before, @After : 해당 테스트 클래스 안에 메소드들이 테스트 되기 전과 후에 각각 실행되게 지정하는 어노테이션
    • @BeforeClass, @AfterClass : 해당 테스트 클래스에서 딱 한번씩만 수행되도록 지정하는 어노테이션

 

 

2019.09.03(화)

Java에서 TDD(Test-driven Development) 테스트 주도 개발방법론

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함