본문 바로가기
  • think normal
새로워지기/서른의 생활코딩

스프링 부트에서 테스트 주도 개발 실습 - 1장. 다중통화를 지원하는 Money 객체

by 청춘만화 2019. 2. 15.

STS 4.0.1(스프링 부트)에서 

TDD(테스트 주도 개발 Test Driven Development) 실습하기


테스트 주도 개발 TDD 라는 책을 읽었었다.  

뭐 이것도 작년이지만.. 이제야 포스팅을 해본다. 일단 JAVA 부분만 포스팅을 해보고자 한다. 생각보다 짧아서 다른 포스팅과 동시에 작성해도 무리는 없어 보인다. 

책의 내용은 한 회사에서 보유한 프로그램 와이캐시 WhyCash에 대해 의뢰인으로부터 일부 기능(다중 통화 기능적용)에 대한 수정을 요청받는 가상의 사건이 있고 그 프로젝트를 수행하기 위해 TDD를 적용하여 개선해가는 과정을 다루고 있다.  

자, 이제 시작해보겠다. 아, 나의 TDD 테스트 환경은 아래와 같다. 

- OS : mac

- STS : 4.0.1

Build : gradle

frame-work : JUnit 




들어가는 글 >

 1. ~에서 저자는 다음과 같은 미묘한 농담을 던진다.

당신이 천재라면 당신에게 이런 법칙은 필요 없다. 또 당신이 멍청이라면 역시 이런 법칙이 도움이 되지 않을 것이다. 그 사이에 존재하는 대다수의 사람들은 다음 두 가지 단순한 법칙을 따름으로써 잠재력을 한껏 발휘할 수 있을 것이다.

- 어떤 코드건 작성하기 전에 실패하는 자동화된 테스트를 작성하라.

- 중복을 제거하라.


 2. 예제에 앞서 예제의 목적은 테스트 주도 개발의 리듬을 보도록 하는 데 있다고 한다.

1. 재빨리 테스트를 하나 추가한다.

2. 모든 체스트를 실행하고 새로 추가한 것이 실패하는 지 확인한다

3. 코드를 조금 바꾼다.

4. 모든 테스트를 실행하고 전부 성공하는지 확인한다.

5. 리팩토링[각주:1]을 통해 중복을 제거한다.  관련 참고 블로그 

 



1장. 다중통화를 지원하는 Money 객체 



1. 프로젝트를 생성한다.
1) Spring Stater Project 참고로 스프링 부트에서는 빈 프로젝트 하나를 아무렇게나? 생성해도 Junit를 바로 테스트 할 수 있는 환경이 기본으로 제공된다. TDD를 진행하는 만큼 가장 빠르게 테스트해 볼 수 있는 환경에서 시작해보겠다. 

2) 세팅 값 입력, 개인적으로는 그래들을 선택했다. 

3) 개인적으로 이 부분의 옵션은 아무것도 선택하지 않았다.  


2. 프로젝트가 생성되었으면

1) TEST 하위폴더에 있는 Test.class를 열어보자

2) 1차 요구사항은 다름과 같다.

       

       $5 + 10CHF = $10 (환율이 2:1일 경우)

       $5 x 2 = $10                              <-- 부터 해결해 보자,

amount를 private로 만들기 

Dollar 부작용(side effect)?

Money 반올림? 


 코드는 간단한 곱셈의 예이다.  <-- 아래 코드가 디버그와 리팩토링 시작의 첫 기준이 됩니다.

public class TddStudy2019ApplicationTests {
	@Test
	public void testMultiplication() {
		Dollar five = new Dollar(5);
		five.times(2);
		assertEquals(10,five.amount);
	}
}

(1) 첫 예제를 작성하여 Run As > JUnit 을 구동시키면 중간에 에러 팝업이 뜨는데 무시해도 좋다. Dollar 클래스 선언이 안되어 있어서 그렇다. 일단 테스트하고 에러를 확인하게되면 그 뒤에 해결할 예정이다. 일종의 TDD의 과정으로 봐도 무관하다.

(2) 빨간 줄이 나타난다. 구동 결과는 예상했던 바와 같이 에러이다. 우하단에 있는 에러 메시지를 확인해보자. 

- Doller class가 없음


2) 에러 메시지를 반영하여 코드를 개선해보자. 

(1) 일단 가볍게 Doller를 inner class로 선언해보았다.


public class TddStudy2019ApplicationTests { @Test public void testMultiplication() { Dollar five = new Dollar(5); five.times(2); assertEquals(10,five.amount); } } class Dollar{ }

(2) 다시 빨간 줄이 나타난다. 이번엔 에러 메시지가 바뀌었다. 그리고 본문 코드에 뭔가 빨간 줄들이 추가되었다. 하지만 걱정하지 말자. 그만큼 개선할 대상이 명확해졌음을 의미한다. 

- 생성자가 없음                       The constructor Dollar(int) is undefined

- times(int) 메서드가 없음       The method times(int) is undefined for the type Dollar

- amount 필드가 없음              amount cannot be resolved or is not a field


3) 변경된 에러 메시지를 반영하여 

(1) 다시 코드를 개선해보았다.

- 생성자를 만들고 Dollar(int amount)

- times(int) 메서드를 만들고 times(int multiplier),   <-- 책에서는 times() 스텁을 구현한다. 라고 표현했다. [각주:2] + 관련 내용 링크 

- amount 필드 int amount =10;를 추가했다.

 public class TddStudy2019ApplicationTests {
	@Test
	public void testMultiplication() {
		Dollar five = new Dollar(5);
		five.times(2);
		assertEquals(10,five.amount);
	}
}

class Dollar{
	Dollar(int amount){
	}
	
	void times(int multiplier) {	
	}
	
	int amount =10;
}
(2) 다시 실행하면 녹색줄이 나타난다. 성공. 


3. 첫 예제가 성공했다.

1) 하지만 개선해야 할 사항이 많다. 이는 마치 얼마 전에 포스팅 한 스프링 부트에서 토비 3.1 따라하기 : 1장 - 1.1 초난감 DAO 에서의  첫 예제와 동일한 맥락이다. 일단 돌아가게 하고 코드를 개선해가는 과정이 앞으로의 책 전반의 진행 방향이다. 

2) 테스트를 계속 진행하기 전에 일반화해야하는 주기. 잊지 말 것.

(1) 작은 테스트를 하나 추가한다        완료 

(2) 모든 테스트를 실행해서 테스트가 실패하는 것을 확인한다      완료 

(3) 조금 수정한다      완료 

(4) 모든 테스트를 실행해서 테스트가 성공하는 것을 확인한다      완료 

(5) 중복을 제거하기 위해 리팩토링을 한다.

의존성이 문제라면 중복 duplicarion은 문제의 징후이다.문제 자체는 남겨둔 채로 징후만 제거하면 다른 어딘가에서 최악의 형태로 문제가 드러나는 현실과 달리 프로그램에서는 중복만 제거하면 의존성도 제거된다. 다음 테스트로 진행하기 전에 중복을 제거함으로써 오직 한가지 one and only one의 코드 수정을 통해 다음 테스트도 통과되게 만들 가능성을 최대하기 위함이다.


4. 리팩토링. 이제 중복을 제거할 차례이다.

1) 그럼 뭘 조정할 수 있을까요? 사실상 첫 예제는 저자가 독자의 이해와 이야기 전개를 위한 상황극이다보니 코드가 조금 이상? 했습니다. 물론 그 이상한 점들을 개선하는 과정이 책 내용이니까 너무 시니컬하게 받아들이지는 마시고욥 >< 여하튼, 위의 예제는 사실 아래 내용과 동일합니다.

캡쳐사진에서는 amount가 이미 10 이니, 불필요해보이는(또는 헷갈리는) 매서드의 인자 값을 먼저 제거해봤습니다. 그러면 assertEquals(10, amount);와 같은 결과가 나온거죠. 이렇게 중복을 없애도 돌아가네요. 

다만, 상황극 상 5달러를 두번 곱해야하는 상황을 먼저 테스트 하고 그 테스트를 일단 돌아가게 하느라 예제와 같은 코드가 나온거죠. 어찌보면 캡쳐에 있는 코드가 더 맞는 코드 같지만 현실세계에서 이와같은 요구사항은 있을 수 없겠죠, 말그대로 그래서 저자는 예제와 같은 코드를 작성한 것이 아닌가 생각됩니다. 따라서 위에 기재한 '<-- 아래 코드가 디버그와 리팩토링 시작의 첫 기준이 됩니다. 에서 가르키는 코드를 기준으로 개선해나가면 될 것 같다. 


2) 위 내용을 전재로, 먼저 int amount = 10; 를 amount = 5 * 2; 로 조정할 수 있을 것 같습니다. 

class Dollar{
	int amount;
	
	Dollar(int amount){
	}
	
	void times(int multiplier) {
		amount = 5 * 2;
	}
}


3) 그리고 나머지도 아래와 같이 수정할 수 있을 것 같습니다. 결과는 '2), 3)' 모두 녹색바 입니다.

class Dollar{
	int amount;
	
	Dollar(int amount){
		this.amount = amount;
	}
	
	void times(int multiplier) {
		amount = amount * multiplier;
	}
}


4) 이렇게 해서 테스트 주기를 한 바퀴 돌릴 수 있었습니다.

(1) 작은 테스트를 하나 추가한다        완료 

(2) 모든 테스트를 실행해서 테스트가 실패하는 것을 확인한다      완료 

(3) 조금 수정한다      완료 

(4) 모든 테스트를 실행해서 테스트가 성공하는 것을 확인한다      완료 

(5) 중복을 제거하기 위해 리팩토링을 한다.     완료 

  1. ReFactoring 이란 코드의 외적 행위는 그대로 유지하면서 내부 구조를 변경하는 작업을 말한다. [본문으로]
  2. 스텁 구현(stub implementation)은 메서드의 서명부와 반환 값이 있는 경우 반환 명령만 적는 식으로하여, 이 메서드를 호출하는 (테스트)코드가 컴파일 될 수 있도록 껍데기만 만들어 주는 것을 말한다. 요컨데 stub 은 모의 구현을 의미이다. 즉, 인터페이스를 준수하고 테스트에 사용되는 간단한 가짜 구현을 말하는 것이다. [본문으로]

댓글