이어서...
오늘은 드디어 많은 것들을 진행했다.
TDD로 여러 기능들을 구현했는데, 양으로만 보면 전체 기능의 약 60%, 중요도로 보면 50% 정도의 기능들을 구현했다.
| ⬆️ ✅ 표시가 있는 기능들이 구현 완료한 기능들이다.
3주차 과제때는 기능들을 만들다가 컨트롤러를 만들어서 필요한 기능들을 추가적으로 구현했는데, 이번에는 기능들을 다 만들고 컨트롤러와 서비스에서 사용하게 해보려 한다.
( ⬆️ 과제 끝내고 와서 여기 다시 적는다. 이건 실패했다. 메인 로직에 TDD? 엄청 빡세다..)
아무래도 기능 자체로만 보면 2주차 과제의 연장선인것 같다. 구현 자체는 (아직까지는) 그렇게 어렵지 않고, 기능을 구현하기 위한 방법이 막히지 않고 떠오른다. 그래서 기능 구현을 위해 검색하는 시간이 대폭 줄었다.
TDD (feat. 단위테스트)
사실 아직까지도 TDD에 대한 오해가 하나 있었다. 2주차 때는 테스트 코드와 구현 코드를 test 패키지 아래에 다 작성하고 작성한 구현 코드를 다시 main 패키지 아래에 옮겼었는데, 이렇게 할 필요 없이 그냥 main 패키지 아래에 구현 코드를 적고 test 패키지 아래에 테스트 코드를 작성하면 되는 거였다....
어쩐지 인텔리제이의 Generate 기능을 사용해 테스트 코드를 만들면 test 패키지 아래 이동하더라.
| ⬆️ 이렇게 그냥 하면 된다...
그리고 1, 2주차 과제때는 구현 -> 테스트 -> 리팩토링의 과정보다는 한 클래스를 한번에 구현하고 한번에 테스트 했었다. 이번에는 정말로 정석대로 아주 작은 단위부타 하나씩 구현하고 테스트 했는데, 이렇게 하니까 내 코드에 더 확신이 생기는 것 같았다. '뭔가 놓친 부분은 없을까?' 하고 걱정하는 게 좀 더 줄은 것 같다.
검증 클래스
이번에도 역시 검증할 것들이 넘쳐났다.
정답 로또 검증 - 정규 표현식
위 정규식은 1부터 45까지의 6가지 숫자가 콤마를 사이에 두고 있을 때 통과하는 정규식이다. 즉, "1,4,10,25,40,45" 처럼 요구사항에 알맞는 문자열이면 통과하는 매우 엄격한 정규식이다.
사실 이 정규식 하나로 정답 로또의 입력은 다 검증할 수 있는데, 이렇게 하면 입력에서 어떤 점이 문제인지를 사용자에게 정확히 알려줄 수 없다는 단점이 있었다. 그래서 이 부분에서는 코드의 복잡도 보다는 사용자 편의성을 택했다.
이렇게 한 정규식에서 해결할 수 있는 검증을 여러 개의 검증으로 분리시켰다.
정답 로또 검증 - 중복된 숫자
중복된 숫자를 어디서 검증할지가 고민이었다. 중복된 숫자를 검증하기 위해서는 입력된 문자열을 콤마를 기준으로 분리해야 했기 때문이다. 분리된 문자를 검증하기 위한 객체를 따로 만들어야 하나 생각했는데, 그냥 정답 로또 검증 클래스에서 전부 진행하기로 했다. 다른 검증 메서드와 달리 문자열이 아닌 List를 매개변수로 받긴 하지만, 정답 로또 검증
이라는 클래스 객체의 역할에서 벗어나지 않는다고 봤기 때문이다.
구매한 로또 번호 오름차순 정렬
구매한 로또 번호를 오름차순으로 정렬해서 출력해야 한다는 요구사항이 있었다. 이 오름차순 정렬
이라는 책임을 어떤 객체에게 부여할까 고민했다. 처음에는 오름차순 정렬을 해주는 유틸리티 클래스를 만들까 고민했었는데, Lotto
클래스 내부에 만들기로 했다. 로또 번호에 대한 책임이 있는 Lotto
클래스가 스스로 관리하게 하는게 맞다고 생각했기 때문이다.
이렇게 오름차순으로 정렬한 로또 번호를 상태로 가지게 했다.
검증 기능 구현 1차 완료
검증 기능을 구현하고, 리팩토링도 하며 발매된 로또 번호 검증
이외에는 모든 검증 기능 구현과 예외사항 테스트를 완료했다. 발매된 로또 번호 검증
기능을 아직 하지 않은 이유는 아직 Lotto
클래스의 작성을 완료하지 않았기 때문이다. 다른 기능부터 구현한 후 Lotto
클래스의 작성이 완료되면 이후 구현할 예정이다.
로또 구매 기능
로또를 구매하는 LottoPurchaseService
클래스를 작성했다.
이번에는 자료구조를 LinkedHashSet
으로 하지 않았는데, 정렬이 불필요하다고 생각했기 때문이다. 출력 요구사항을 생각했을 때, 순서와 상관없이 로또 객체를 꺼내오기만 하면 될 것 같았다.
단위 테스트가 맞는가?
LottoPurchaseService
기능을 테스트 하던 중, 의문이 생겼다. 내가 지금 하고 있는게 단위 테스트가 맞는가? 라는 의문이였다.
해당 테스트 코드에서는 테스트를 위해 Lotto
클래스를 import 한다. LottoPurchaseService
의 반환 타입이HashSet<Lotto>
였기 때문이다. *이러면 다른 테스트 하려는 코드 이외에 다른 클래스의 영향을 받는게 아닌가? *라는 생각이 들었다.
내가 내린 결론은 단위 테스트가 맞다
이다. 왜냐면 이 테스트 코드는 LottoPurchaseService
클래스의 purchase()
메서드의 기능에 대해서만 테스트 하고 있기 때문이다. Lotto
객체를 테스트하거나 하지 않는다.
다만 관련해서 학습을 해보니, Lotto
객체를 Mock
객체로 대체하면 더 정밀한 단위 테스트가 된다고 한다. 아직 Mock
을 다루는건 배우지 못했기에, 일단은 패스다. 우선순위에서 밀리는 일이다.
LottoMessage
로또 게임 애플리케이션에서 게임 안내, 결과 출력의 메시지는 LottoMessage
enum 클래스에 작성을 했다.
이 LottoMessage
클래스를 테스트 하던 도중, 에러가 발생했다.
아마 WINNING_STATISTICS
에서 %s%%
부분과 관련이 있는 문제인 것 같았다. 여기서 %s%%
뒤의 '입'을 잘못된 문자열 포매팅으로 인식한 것 같았다.
그런데 분명히 문자열 포맷에서 %를 표현하기 위해서는 %%
처럼 %를 두 번 써줘야 하는게 맞았다. 이 문제를 해결하기 위해 1시간 정도를 해멨는데, 이 코드에는 문제가 없으니 다른 코드에서 분명히 문제가 있는게 분명하다는 생각으로 하나씩 찬찬히 되짚어봤다.
원인은 format()
문제는 이곳이었다.
OutputView
에서 해당 결과를 출력하게 하는데, 그냥 문자열을 가져오는게 아닌 포매팅 후 가져오게 코드를 작성했었다.
이렇게 LottoMessage
클래스 안에 getFromatMessage()
메서드로 가변 매개변수를 받아 문자를 이미 포매팅 해서 가져오고 있었는데, OutputView
에서 printf
로 한번 더 포맷팅을 해서 생긴 문제였다. 이 부분을 print
로 고쳐주니 에러가 해결되었다.
+ 수익률 변수의 타입
위의 사진들을 보면 수익률을 String
타입으로 받는다. 왜 소수점 값을 가지는 float
, double
을 사용하지 않았는가 하면...float
나 double
타입은 소수점을 다룰 때 정확한 값이 아닌 근사치로 값을 정한다는걸 자바를 공부하다 배웠었기 때문이다. 수익률은 민감한 금융 계산 문제니, 해결할 방법이 필요했다. 알아본 방법으로는 BigDecimal
을 사용하면 된다고 하니, 이후 수익률 계산 기능을 구현할 때 BigDecimal
을 사용해 문자열로 수익률을 반환해야겠다고 결정했다.
이것 때문에 미리 문자열로 수익률 값을 매개변수로 받아 출력하게 만들었다. 이것도 설계라면 설계일 것이다!
마무리
오늘은 짧은 시간 안에 많은 걸 구현했다.
4시간 만에 구현할 기능 목록 13개 중 8개를 완료 했으니, 약 60% 정도의 기능을 구현한 것이다.
내일은 모든 기능 구현 완료와 테스트 통과 까지를 목표로 삼는다. 내일은 정말로 아무런 일정이 없으니 과제에 모든 시간을 쏟을 수 있다.
1, 2주차에 비해 어쩔 수 없이 적은 시간을 과제에 사용했지만, 재미는 여전하다. 오히려 머리가 너무 복잡해지지 않아서 쾌적한 상태로 과제를 하니 즐거움이 더 커진 것 같다.
1주차 때는 코드 여기저기를 왔다갔다 하며 리팩토링을 위한 리팩토링을 위한 리팩토링을 하다 보니 머리가 좀 복잡했었는데, 지금은 상쾌한 프로그래밍을 한 것 같은 기분이다. (아직 어려운 로직을 작성 안해서 그런 걸수도 있다..)
어려운 문제를 해결하는 기쁨과 적당히 어려운 문제를 해결하는 기쁨이 다르달까?
(시기에 따라 상대적이지만) 1주차의 어려웠던 과제를 할 때는 학습에 대한 지적 충만감과 성취감이, 적당히 어려운 이번 3주차의 과제를 할 때는 머릿속에 있는 걸 써먹을 것의 기대와 성장한 나 자신에 대한 자랑스러움이 주된 즐거움과 기쁨이었던 것 같다.
새롭게 알게된 점
1. printf()
자체에 포매팅 기능이 있다.
위의 에러에서 문제가 생겼던 근본적인 원인은 printf()
자체도 %d, %s 등의 형식 지정자에 값을 대입할 수 있기 때문이다. 즉, String.format()
을 통해 "총 수익률은 %s%%입니다."
에서 %s가 포매팅 되어 사라졌는데, %%입
을 printf
로 다시 포매팅 하려니까 생긴 문제였다.
2. TDD의 이해도 상승
TDD 과정은 단위 테스트만으로 이루어지는 것도 아니고 통합 테스트만으로 이루어지는 것도 아니다. 단위 테스트가 벽돌 하나하나의 품질을 체크하는 것이라면, TDD는 벽돌의 품질을 미리 체크하고 그것들을 쌓아 올리면서 전체적인 건물의 품질도 함께 테스트 하는 것이라고 생각한다. 결국 TDD는 테스트 주도 '개발' 이라는 것!
좋았던 점
1. 이번 과제가 쉽게 느껴지는 점
1주차에 비해 훨씬 성장한 나를 볼 수 있었다. 고마워요 우테코!
2. 문제 해결을 위한 아이디어, 방법이 머릿속에서 계속 떠오르는 점
'이 기능을 어떻게 구현해야 할까?' 하고 오랫동안 고민할 필요가 없었다. 그저 머릿속에 떠오른 구현 방법을 그대로 코드로 옮기고, 나중에 리팩토링 하면 된다. 적어도 아직까진 그렇다.
기만은 아니다...
그 만큼 객체지향적, 프로그래밍적 사고를 가지게 된게 아닐까? <- (과제를 마무리하고 나서 여기 적는다. 딱 오늘의 구현까지만 쉬웠다 ㅋ.ㅋ)
아쉬웠던 점
1. 시간의 부족
사실 그리 아쉽진 않지만(어쩔 수 없었기 때문에) 굳이 아쉬운 점을 꼽아보라면, 이번 3주차 과제에 들일 수 있었던 시간이 부족했던 것이었다. 좀 더 좋은 프로그램, 세세한 기능, 아름다운 객체지향을 달성하는 즐거움이 있는데, 시간의 부족으로 이 즐거움을 최대한 만끽하지는 못할 것 같아서 아쉽다.
도움이 된 자료들
| [JUnit5] 기본 사용법 정리
| [JUnit5] @ParameterizedTest로 한 번에 테스트하자 <- 이건 정말 꿀기능이었다. 강추!
| 로또 최대 몇장,구매 가능한가? 10만원 이상 가능? <- 최대 로또 구매 가능 금액을 정하는 기준을 잡는데 도움이 되었다. 1인당 최대 로또 구매 금액은 법적으로 10만원임을 명심하시길..
| 실수 소수점 다루기 ( Math & String.format)
| 정확한 답이 필요하다면 float와 double은 피하라
| [Java] String.format() | 문자열 형식 지정하기 | %%%ds가 뭘까? | %%
출처: https://splendidlolli.tistory.com/328 [자꾸 생각나는 체리쥬빌레:티스토리]
'우아한테크코스' 카테고리의 다른 글
[우테코 6기 프리코스] 3주차 전체 회고 (0) | 2023.12.20 |
---|---|
[우테코 6기 프리코스] 3주차 - 6일차 회고 (0) | 2023.12.20 |
[우테코 6기 프리코스] 3주차 - 3, 4일차 회고 (0) | 2023.12.20 |
[우테코 6기 프리코스] 3주차 - 2일차 회고 (0) | 2023.12.20 |
[우테코 6기 프리코스] 3주차 - 1일차 회고 (0) | 2023.12.20 |