일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- try-with-reources
- java
- Checked Exception
- Unchecked Exception
- 바이트코드
- Final
- 보안 그룹
- 이펙티브 자바
- 예외
- JVM
- Spring
- AWS
- https
- 자바스터디
- 피리티어
- spring-security
- 파라미터 그룹
- 이펙티브자바
- bytecode
- Effective Java
- RDS
- ec2
- exception
- error
- Annotation
- 자바
- 생각정리
- springboot
- Today
- Total
개발 일지
이번 글에서는 테스트에 대한 인프런 강의를 바탕으로 테스트 코드란 무엇이고 테스트 코드가 왜 중요한지, 더 좋은 테스트 코드를 작성하기 위한 방법은 뭔지 등을 정리해 보겠습니다.
Practical Testing: 실용적인 테스트 가이드 | 박우빈 - 인프런
박우빈 | 이 강의를 통해 실무에서 개발하는 방식 그대로, 깔끔하고 명료한 테스트 코드를 작성할 수 있게 됩니다. 테스트 코드가 왜 필요한지, 좋은 테스트 코드란 무엇인지 궁금하신 모든 분을
www.inflearn.com
테스트 코드 강의를 듣게 된 배경
- 친구와 프런트엔드-백엔드 역할을 나눠 간단한 토이 프로젝트를 진행하면서 ec2에 배포를 먼저 해두고 개발을 했었습니다. ( 전에는 먼저 개발 후 나중에 배포를 했었습니다)
- CI/CD를 구현했음에도 불구하고, 한번 코드를 수정하고 재배포하는 데 시간이 걸려 재 배포 전 자체적으로 확실하게 검증할 필요를 느꼈습니다.
- 검증 목적으로 선택한 것은 이미 어느 정도 작성할 수 있고 빠른 피드백을 받을 수 있는 "테스트 코드"
- 하지만 그동안 작성했던 테스트 코드는 따로 공부한 것이 아닌 그때그때 필요한 부분을 찾아가며 작성한 것이어서,
- 테스트 코드에 대한 개념이 제대로 잡혀있지 않았고, 특히 Mock에 대한 이해가 부족하다는 것을 깨달았습니다.
- 이번 기회에 테스트 코드에 대해 기초부터 확실히 공부하고자 인프런에서 강의를 듣게 되었습니다.
테스트에 대한 글뿐만 아니라 라이브 코딩을 진행하면서 개발에 도움이 될만한 이야기들도 함께 해주시기 때문에 강의가 알차고 좋았습니다. 무엇보다 테스트코드가 왜 필요한지 명확히 알려주시기 때문에 기술을 배우는 목적을 알고 시작할 수 있어서 좋았습니다.
테스트 코드란
테스트 코드는 소프트웨어 개발에서 작성한 프로그램이 의도한 대로 동작하는지 검증하기 위해 작성되는 코드입니다. 이는 특정 기능이나 모듈이 기대하는 대로 작동하는지 확인하는 데 사용됩니다. 테스트 코드는 개발자가 작성한 프로덕션 코드와 별도로 작성되며, 다양한 시나리오를 테스트하여 코드의 품질과 신뢰성을 높이는 역할을 합니다.
테스트 코드가 중요한 이유
그렇다면 이러한 테스트 코드가 과연 "왜" 필요할까요? 우리는 왜 테스트 코드에 대하여 공부하고 테스트 코드를 사용해야 할까요. 먼저 테스트 코드를 작성하지 않으면 수동으로 테스트를 진행해야 하는데 수동으로 테스트를 진행하면 다음과 같은 어려움이 있습니다.
- 개발자는 사람이고, 사람은 항상 실수를 유발할 수 있는 가능성을 가지고 있습니다.
- 하나하나 수동으로 테스트를 하다 보면 소프트웨어가 커지는 속도를 따라갈 수가 없게 되고 결국 테스트로 커버할 수 없는 영역이 발생하게 됩니다.
- 피드백이 늦어지게 되고 그러면 개발 사이클이 느리게 돌아가게 됩니다.
- 유지보수가 어려워지고 결과적으로 소프트웨어의 신뢰성이 떨어지게 되며 신뢰성이 없는 소프트웨어는 사용자가 사용할 수 없습니다.
테스트 코드를 활용하면 이런 어려움들을 해결할 수 있습니다.
우리가 테스트 코드로 얻고자 하는 것
빠른 피드백:
- 테스트 코드는 개발자에게 코드 변경 사항이 의도한 대로 작동하는지 즉시 피드백을 제공합니다. 이를 통해 버그를 빠르게 발견하고 수정할 수 있습니다.
자동화:
- 테스트 코드를 통해 반복적인 검증 작업을 자동화할 수 있습니다. 이는 수작업 검증에 비해 시간과 노력을 절약하고, 인간의 실수를 줄입니다.
안정성:
- 테스트 코드는 소프트웨어의 안정성을 높이는 데 기여합니다. 다양한 테스트(단위 테스트, 통합 테스트, 시스템 테스트 등)를 통해 코드의 다양한 측면을 검증함으로써, 예상치 못한 버그를 줄이고, 제품의 신뢰성을 높일 수 있습니다.
이러한 이유로 테스트를 작성하는 것은 귀찮지만 꼭 필요합니다.
"가까이 보면 느리지만 멀리 보면 가장 빠릅니다."
내가 지금 시간을 30분, 1시간 더 투자해서 테스트를 작성하는 게 나중에 미래의 수많은 시간을 아낄 수 있을 것입니다.
좋은 테스트 코드를 작성하는 방법
테스트란 문서다
- 프로적션 기능을 설명하는 테스트 코드 문서
- 다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완
- 어느 한 사람이 과거에 경험했던 고민의 결과물을 팀 차원으로 승격 시켜서 문서와 가능
"우리는 항상 팀으로 일한다"
그렇다면 어떻게 더 좋은 테스트 코드, 더 좋은 문서를 남길 수 있을까요.
1. 테스트케이스 세분화 하기
해피케이스와 예외케이스에 대한 경곗값 테스트는 필수입니다.
- 경곗값: 범위(이상, 이하, 초과, 미만), 구간, 날짜 등
- 해피케이스: 요구사항을 만족시키는 경우
- 예외케이스: 범위를 벗어나는 조건에 대해 올바르게 에러를 처리하거나, 예외를 발생시키는지 확인하는 테스
요구사항: 음료는 1잔 이상 주문할 수 있다.
- 해피케이스: 요구사항을 만족시키는 테스트 작성 -> 음료를 1~ 2잔 주문하는 테스트 작성
- 예외 케이스: 0잔은 주문할 수 없음 -> 0잔 주문 할 때 예외가 잘 발생하는지에 대한 테스트 작성
또한 테스트 케이스를 세분화하는 과정에서, 우리는 항상 "암묵적이거나 아직 드러나지 않은 요구 사항이 있는가"에 대한 질문을 던지고 고민해야 합니다. 위 요구사항에의 암묵적인 요구사항은 "주문은 가게 운영시간에만 할 수 있다"일 것입니다.
암묵적인 요구사항: 가게 운영 시간(10:00~22:00) 외에는 주문을 생성할 수 없다
- 해피케이스: 요구사항을 만족시키는 테스트 작성
- 예외 케이스:
이 요구사항에 대한 테스트를 작성하기 위해서는 생각할 점이 하나 있습니다. 아래는 주문객체를 생성하는 코드입니다.
public Order createOrder() {
LocalDateTime currentDateTime = LocalDateTime.now();
LocalTime currentTime = currentDateTime.toLocalTime();
if(currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) {
throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요");
}
return new Order(currentDateTime, beverages);
}
- 위 코드는 메서드 내부에서
LocalDateTime.now()
를 사용하여 현재 시간을 가져와서 Order 객체를 생성합니다. - 현재 시간을 가져오기 때문에 요구사항인 "가게 운영 시간(10:00~22:00) 외에는 주문을 생성할 수 없다"는 조건을 테스트하려면 실제 영업시간에만 테스트를 진행해야 하는 제약이 발생합니다.
- 테스트의 제약을 없애려면 어떻게 해야 할까요?
2. 테스트하기 어려운 영역을 구분하고 분리하기
위와 같은 문제점을 해결하기 위해서는 다음과 같이 테스트를 진행하기 어려운 영역들을 분리해야 합니다.
테스트를 작성할 때는 완벽하게 제어할 수 있는 것이 중요합니다. 제어할 수 없는 것은 상위레이어로 분리하는 것
제어할 수 없는 값들은
관측할 때마다 다른 값에 의존하는 코드
- 현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등
외부 세계에 영향을 주는 코드
- 표준 출력, 메시지 발송, 데이터베이스에 기록하기 등
현재 시간은 관측할 때마다 변하는 값이므로 제어할 수 없는 부분입니다. 따라서 상위 레이어에서 시간을 받아오도록 리팩토링해 보겠습니다.
public Order createOrder(LocalDateTime currentDateTime) {
LocalTime currentTime = currentDateTime.toLocalTime();
if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) {
throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요.");
}
return new Order(currentDateTime, beverages);
}
- 이렇게 currentDateTime을 외부에서 받아오도록 리펙토링을 하면 테스트 환경에서는 예측 가능한 고정된 값을 사용하여 테스트를 수행할 수 있고, 이는 테스트의 신뢰성과 일관성을 높입니다.
// 해피케이스
@Test
void createOrderWithCurrentDateTime() {
CafeKiosk cafeKiosk = new CafeKiosk();
Americano americano = new Americano();
cafeKiosk.add(americano);
LocalDateTime currentDateTime = LocalDateTime.of(2024, 3, 24, 10, 0);
Order order = cafeKiosk.createOrder(currentDateTime);
assertThat(order.getBeverageList()).hasSize(1);
assertThat(order.getBeverageList().get(0).getName()).isEqualTo("아메리카노");
}
// 예외케이스
@Test
void createOrderOutsideOpenTime() {
CafeKiosk cafeKiosk = new CafeKiosk();
Americano americano = new Americano();
cafeKiosk.add(americano);
assertThatThrownBy(() -> cafeKiosk.createOrder(LocalDateTime.of(2024, 3, 24, 9, 59)))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("주문 시간이 아닙니다. 관리자에게 문의하세요.");
}
- 고정적인 값으로 해피케이스와 예외케이스에 대한 테스트 코드를 작성할 수 있습니다.
3. DisplayName을 섬세하게 ( @DisplayName: Juite5부터 추가된 어노테이션)
ex) 음료를 1개 추가 테스트 -> 음료를 1개 추가할 수 있다. -> 음료를 1개 추가하면 주문목록에 담긴다.
- 명사의 나열보다 문장으로 작성하기
- ~테스트 지양하기
- 테스트 행위에 대한 결과까지 기술하기
ex) 특정 시간 이전에 주문을 생성하면 실패한다 -> 영업 시작 시간 이전에는 주문을 생성할 수 없다.
- 도메인 용어를 사용하여 한층 추상화된 내용을 담기
- 테스트의 현상을 중점으로 기술하지 말 것( ~실패한다, ~성공한다)
정리
테스트 코드란
- 소프트웨어 개발에서 작성한 프로그램이 의도한 대로 동작하는지 검증하기 위해 작성되는 코드
테스트 코드를 작성해야 하는 이유 (테스트 코드의 장점)
- 빠른 피드백
- 자동화
- 안정성
- 문서화
좋은 테스트 코드를 작성하기 위해서는
- 테스트 케이스를 세분화시키기
- 항상 "암묵적이거나 아직 드러나지 않은 요구 사항이 있는가"에 대한 질문을 던지고 고민하기
- 완벽하게 제어하기 위해 테스트하기 어려운 부분을 분리하기
- DisplayName을 섬세하게 작성하기
여기까지 강의 들으면서 좋았던 내용들을 전반적으로 정리를 해보았습니다.
여기 정리한 부분 말고도 좋았던 내용이나 정리해 두고 싶은 내용들은 이후에 따로 정리해 보려고 합니다
더 공부해 볼 것들
- 핵사고날
- @Transaction