2장은 문자열 계산기 구현을 통한 테스트와 리팩토링 입니다.
사실 저는 부끄럽게도 단한번도 테스트 코드를 작성해본적이 없는데요.
프로그래밍을 하면서 테스트는 했겠지만 테스트 코드를 이용해서가 아닌 IDE의 디버깅 툴과, 결과값 출력을 확인하는 방식으로 진행해 왔는데 최근 들어서는 테스트 코드를 작성하면서 개발하는게 실수로 인해 서비스에 문제가 발생하거나, 다른 예외 상황에 대해 생각해 볼 수 있게끔 해주는거 같아서 필요성을 느끼고는 있었으나 실제로 업무에 적용하지는 못하고 있었는데 이 책을 공부하면서 테스트 코드 작성에 익숙해지고 실무에서도 자유롭게 사용할 수 있게끔 되었으면 좋겠습니다.
junit 기본 사용법
시작하기전에 junit4에 대한 사용법을 익혀봅니다, 책에 있는 링크는 Youtube가 변경되어서 그런가 정상적으로 영상이 안나와서 여기에서 보면 정상적으로 영상을 확인 가능하고요.
영상에는 책에서 간단하게 소개하는 junit4에 대해서 다루고 있습니다.
책에서 간단하게 소개되는 junit annotation은
- @Test - 단위 테스트를 정의함
- @Before - 각 단위테스트 실행 전 실행되는 함수를 정의함 (@Test가 붙은 각각의 함수가 실행되기전 실행됨)
- @After - 각 단위테스트 실행 후 실행되는 함수를 정의함 (@Test가 붙은 각각의 함수가 실행되고 나서 실행됨)
이정도 이고, 함수는
- assertEquals() - 값이 같은지 확인
- assertTrue() - 값이 true인지 확인
- assertFalse() - 값이 false인지 확인
- assertNull() - 값이 null인지 확인
이정도 인데 이거 외에도 다른 annotation과 함수들이 있으므로 다음번에 따로 포스팅 하기로 하겠습니다.
문자열 계산기 요구사항
문자열 계산기를 구현하기 앞서 무선 요구사항을 먼저 확인해보겠습니다.
- 쉼표 또는 콜론을 구분자로 가지는 문자열을 전달하는 경우 구분자로 분리한 각 숫자의 합을 반환
- 1번의 쉼표 또는 콜론외에 커스텀 구분자를 지정할수 있고, ‘//‘와 ‘\n’사이의 문자를 커스텀 구분자로 사용
- 숫자 값이 음수일때 RuntimeException 처리 해야함
구현전 요구사항을 더 작은 단위로 나눠 테스트할 경우의 수를 생각해보는게 좋다고 하는데요
어떤 테스트케이스를 생각해보는게 좋을까 생각해보다가 아래와 같은 테스트 케이스를 생각해보습니다.
(더 작은단위로 쪼개라는게 더 디테일하게 테스트 케이스를 생각해보자 라고 받아들였습니다.)
- 쉼표 또는 콜론 외에 다른 구분자가 들어올 때 (커스텀구분자로 정의 하지 않은 상태에서)
- 커스텀 구분자로 정의 했으나 각숫자를 다른 구분자로 구분했을 때
- 숫자와 구분자 외에 다른 문자가 포함되어있을 때 (숫자 앞,뒤에 공백이 들어가는 경우도 포함)
위와 같은 테스트 케이스를 생각했고, 요구사항에 맞춰 개발하고 리팩토링 하면서 테스트 케이스를 하나씩 추가해보도록 하겠습니다.
그리고 코드를 작성하고, 리팩토링을 할때 아래의 기준에 맞춰서 개발하는게 일정수준으로 깔끔한 코드를 구현할 수 있다고 하고,
완벽하게 기준에 맞춰서 구현할 수 는 없지만 최대한 노력하면서 구현하는게 좋다고 합니다.
- 메소드가 한 가지 책임만 가지도록 구현한다.
- 들여쓰기 깊이를 1단계로 유지한다. (중첩문 사용을 지양하자 라는 뜻)
- else를 사용하지 말아라.
위에 나와있는 요구사항과 생각해본 테스트 케이스를 토대로 단위테스트 코드를 작성해 보았습니다.
(책에 나와있는 테스트 코드외에 몇개를 더 추가했는데, 혹시라도 더 추가하면 좋을만한게 있으면 코멘트 달아주세요!)
1 | import org.junit.Before; |
맨 아래 paramWithSpace는 문자열에 공백이 있을 경우 trim()을 이용해 공백 제거 후 처리를 해도 되지만
NumberFormatException 이 발생하는 것으로 개발을 했고,
테스트 할때 아래 코드처럼 input값을 2개 종류로 확인하고 싶었으나 가장 위에서 실행된 케이스만 실행되고 종료되었습니다.
1 | // 입력된 문자열에 숫자 앞뒤 공백이 있을때 |
그래서 따로 분리를 해놨는데 한개의 테스트 케이스 함수내에서 여러개의 조건을 실행하는 방법에 대해 확인을 해봐야 할거 같습니다.
그럼 이번에는 요구사항에 맞는 동작을 수행하고, 모든 테스트 케이스를 통과하는 코드를 작성해보겠습니다.
아래 코드는 위 작성된 테스트 케이스를 모두 pass하는 코드이나 리팩토링을 하기 전 상태입니다.
책에서는 리팩토링과 테스트 케이스 작성이 동시에 진행이 되었는데 요구사항을 더 작은 단위로 나눠서 테스트할 경우의 수를 생각하다보니 테스트 케이스를 먼저 작성하고 실행 코드를 개발하게 되었습니다.
(테스트코드를 이용한 개발 경험이 부족해 원래 이렇게 진행되는게 맞는건진 잘 모르겠습니다)
아래는 구현된 add 함수 입니다.
1 | import java.util.regex.Matcher; |
부분적으로 주석을 간단하게 적어 두었는데요.
주석들을 기준으로 add함수 1개에서 여러개의 함수로 나뉘어지면서 리팩토링 할 예정입니다.
그리고 책에서도 심하다 싶을정도로 리팩토링을 한 이유는 리팩토링을 하는 연습을 하기 위함이라고 합니다.
리팩토링 진행하면서 생각해본 내용을 적어보자면
- StringUtils의 사용
아래 코드는 문자열 text가 주어지면 null 이거나 “” 일때 0을 리턴하게 되어있는데요.
if문에서 조건을 commons-lang라이브러리의 StringUtils클래스에 있는 isEmpty 함수를 이용해서 처리해도 될거 같다는 생각을 했습니다.
다만 라이브러리를 따로 추가해줘야해서 여기서는 사용하지는 않았습니다.
1 | // 빈 문자열 확인 |
- Exception 처리
음수를 식별하는 함수는 아래와 같이 구현했습니다.
1 | private int checkNegative(String numberText) { |
구분자로 나눈 문자열 배열에서 한개씩 param값으로 넘겨서 int 형변환을 시켜주고 음수 판단을 처리하는 부분인데요.
음수일때 RuntimeException을 발생하게 구현했습니다. 여기까지는 별로 고민할 부분이 없었는데요
테스트 케이스 코드중에 문자열에 공백이 포함되어 있는 값이 들어올때 NumberFormatException 이 발생하는걸로 테스트 케이스를 작성했는데 위에 Integer.parseInt() 호출시에 numberText가 ‘ 1’, ‘2 ‘, ‘ 3 ‘이런식으로 앞뒤 어느 한군데라도 공백을 포함하고 있으면 Exception이 발생을 합니다.
음수 처리하는것 처럼 따로 Exception 발생 코드를 추가하지 않아도 말이죠.
위 내용의 경우엔 요구사항에 없는 내용이라 어떻게 구현하던 상관은 없을거 같습니다만 그래도 try-catch를 통해서 예외처리를 하거나, 아예 param값으로 받은 numberText를 trim함수를 이용해 공백제거를 하는 식으로 구현을 해도 되었을거 같다는 생각을 했습니다. 공부용으로는 Exception을 발생하고 프로그램이 죽어도 괜찮겠지만, 실제 서비스에서는 Exception 발생 가능성이 있는 부분엔 예외처리를 통해서 프로그램이 끝까지 실행되게끔 처리하는게 좋다고 생각을 합니다.(생각은 이렇게 하지만 업무에서 예외처리를 자주 빼먹는게 함정 입니다만)
테스트 코드를 작성하고, 동작코드를 리팩토링 하면서 나온 코드는 아래와 같습니다.
1 | import java.util.regex.Matcher; |
챕터 2에 있는 내용을 제 스타일대로 조금 변형해서 공부하면서 기록 해보았습니다.
혹시라도 추가적으로 리팩토링을 했으면 좋겠다, 혹은 이런것도 생각해보면 좋겠다 하는게 있으면 언제든 댓글 달아주세요
저는 피드백에 항상 목이 말라있습니다.
위에서 사용한 코드는 여기에 있습니다.
추가학습자료