
워밍업 클럽 3기 BE 클린코드&테스트 - 3주차 발자국
회고
3주차 발자국을 4주차 작성 이후에 쓰게 됐습니다. 제가 놓치고 작성하지 못한터라 아쉽게만 여기고 있었는데 기회를 주셔서 감사합니다.
이번 3주차 수업은 실습에 높은 비중을 두고 진행됐습니다. 우빈님의 잘 정리된 프레젠테이션과 함께하는 이론 수업도 좋지만 실습은 실습만의 매력이 있습니다. 가령 이론 수업에서 배웠던 것들이 실제로 어떤 식으로 활용되는지 좀 더 구체적으로 알 수 있어서 좋았습니다. 이론으로 시작하는 추상적인 내용들이 실습을 통해 바로바로 구체적으로 변환되는 과정은 정말 유익하네요.
특히 테스트 코드에 대해선 정말 감동이었습니다. 예전에 테스트 코드를 작성을 시도해본 적이 있었습니다만, 적은 리퍼런스, 어려운 테스트 프레임워크 앞에서 좌절한 경험이 있었습니다. 우빈님의 강의를 들으며 다시 시도해보니, 이렇게 쉬웠던 일인가 싶을 정도로 테스트 코드에 자신감이 붙고 있습니다.
강의 내용 요약
레이어드 아키텍처와 테스트
통합 테스트
여러 모듈이 협력하는 기능을 통합적으로 검증하는 테스트
일반적으로 작은 범위의 단위 테스트만으로는 기능 전체의 신뢰성을 보장할 수 없다
보통 풍부한 단위 테스트 & 시나리오 단위 통합 테스트로 진행
Library vs Framework
라이브러리란?
내 코드가 주체가 된다
다른 기능이 필요할 때 외부에서 가져오는 코드를 라이브러리라고 한다
프레임워크
이미 프레임이 있다
이미 동작할 수 있는 환경이 구성되어 있고 내 코드가 환경에 추가된다
Spring의 특징
IoC (Inversion of Control)
스프링만의 개념은 아니다(다른 프레임워크들에서도 통용되고 있다)
DI (Dependency Injection)
AOP (Aspect Oriented Programming)
JPA (Java Persistence API)
ORM의 일종이다
Java 진영의 ORM 기술 표준
인터페이스
여러 구현체가 있고 대표적으로 Hibernate가 있다
반복적인 CRUD 쿼리를 생성 및 실행해주고 여러 부가 기능들을 제공한다
편리하지만 쿼리를 직접 작성하지 않기 때문에 SQL의 내부 원리를 이해하고 있어야 한다
Spring 진영에서는 JPA를 한번 더 추상화한 Spring Data JPA 제공
QueryDSL과 조합하여 많이 사용한다 (타입체크, 동적쿼리)
ORM
객체 지향 패러다임과 관계형 DB 패러다임의 불일치 해소
이전에는 개발자가 객체의 데이터를 각각 매핑하여 DB에 저장 및 조회
ORM을 사용함으로써 개발자는 단순 작업이 줄고 비즈니스 로직 작성에 집중하게 된다
Persistence Layer
Data Access 의 역할
비즈니스 가공 로직이 포함되어서는 안된다
Data에 대한 CRUD에만 집중한 레이어
Business Layer
비즈니스 로직을 구현하는 역할
Persistence Layer와의 상호 작용을 통해 비즈니스 로직 전개
트랜잭션을 보장해야 한다
Presentation Layer
외부 세계의 요청을 가장 먼저 받는 계층
파라미터에 대한 최소한의 검증을 수행한다
미션
Day 11
미션 설명
[Readable Code] 강의의 두 프로젝트(지뢰찾기, 스터디카페) 중 하나를 골라, 단위 테스트를 작성해 봅시다. 조건은 아래와 같습니다.
각 프로젝트 모두 강의 중에 작성한 tobe패키지 코드를 기준으로 함 (lesson 6-4가 가장 마지막 버전)
3개 이상의 서로 다른 클래스 & 총 7개 이상의 테스트 작성 (시간이 된다면 더 많이 작성해보면 좋겠죠?)
단, 같은 인터페이스를 구현하고 있는 구현체들은 1개 클래스로 간주한다.
무엇을 테스트하고자 했는지를 잘 나타낸 @DisplayName 작성하기
BDD(given/when/then) 스타일 따르기 (주석으로 표기)
나의 답
class StudyCafePassTypeTest {
@DisplayName("시간 단위 이용권은 라커 타입이 아니다.")
@Test
void HourlyPassTypeIsNotLockerType() {
// given
StudyCafePassType passType = StudyCafePassType.HOURLY;
// when
boolean isLockerType = passType.isLockerType();
boolean isNotLockerType = passType.isNotLockerType();
// then
assertThat(isLockerType).isFalse();
assertThat(isNotLockerType).isTrue();
}
@DisplayName("주 단위 이용권은 라커 타입이 아니다.")
@Test
void WeeklyPassTypeIsNotLockerType() {
// given
StudyCafePassType passType = StudyCafePassType.WEEKLY;
// when
boolean isLockerType = passType.isLockerType();
boolean isNotLockerType = passType.isNotLockerType();
// then
assertThat(isLockerType).isFalse();
assertThat(isNotLockerType).isTrue();
}
@DisplayName("1인 고정석은 라커 타입이다.")
@Test
void FixedPassTypeIsLockerType() {
// given
StudyCafePassType passType = StudyCafePassType.FIXED;
// when
boolean result1 = passType.isLockerType();
boolean result2 = passType.isNotLockerType();
// then
assertThat(result1).isTrue();
assertThat(result2).isFalse();
}
}
class StudyCafeSeatPassTest {
@DisplayName("인스턴스 생성 시 주입했던 passType, duration, price을 그대로 반환한다.")
@Test
void get() {
// given
StudyCafePassType passType1 = StudyCafePassType.HOURLY;
int duration1 = 2;
int price1 = 6500;
double discountRate1 = 0.0;
StudyCafeSeatPass studyCafeSeatPass = StudyCafeSeatPass.of(passType1, duration1, price1, discountRate1);
// when
StudyCafePassType passType2 = studyCafeSeatPass.getPassType();
int duration2 = studyCafeSeatPass.getDuration();
int price2 = studyCafeSeatPass.getPrice();
// then
assertThat(passType1).isEqualTo(passType2);
assertThat(duration1).isEqualTo(duration2);
assertThat(price1).isEqualTo(price2);
}
@DisplayName("이용권 타입과 이용 기간이 좌석 이용권과 같은 라커 이용권일 경우 true를 반환한다.")
@Test
void isSameDurationType() {
// given
StudyCafePassType passType = StudyCafePassType.FIXED;
int duration = 4;
int price = 250000;
double discountRate = 0.1;
StudyCafeSeatPass studyCafeSeatPass = StudyCafeSeatPass.of(passType, duration, price, discountRate);
StudyCafeLockerPass studyCafeLockerPass = StudyCafeLockerPass.of(passType, duration, price);
// when
boolean isSameDurationType = studyCafeSeatPass.isSameDurationType(studyCafeLockerPass);
// then
assertThat(isSameDurationType).isTrue();
}
@DisplayName("이용권의 할인율이 적용된 가격을 반환한다.")
@Test
void test() {
// given
StudyCafePassType passType = StudyCafePassType.FIXED;
int duration = 4;
int price = 250000;
double discountRate = 0.1;
int expectedDiscountPrice = (int) (price * discountRate);
StudyCafeSeatPass studyCafeSeatPass = StudyCafeSeatPass.of(passType, duration, price, discountRate);
// when
int discountPrice = studyCafeSeatPass.getDiscountPrice();
// then
assertThat(discountPrice).isEqualTo(expectedDiscountPrice);
}
}
class StudyCafeLockerPassTest {
@DisplayName("인스턴스 생성 시 주입했던 passType, duration, price을 그대로 반환한다.")
@Test
void get() {
// given
StudyCafePassType passType1 = StudyCafePassType.HOURLY;
int duration1 = 2;
int price1 = 6500;
StudyCafeLockerPass studyCafeLockerPass = StudyCafeLockerPass.of(passType1, duration1, price1);
// when
StudyCafePassType passType2 = studyCafeLockerPass.getPassType();
int duration2 = studyCafeLockerPass.getDuration();
int price2 = studyCafeLockerPass.getPrice();
// then
assertThat(passType1).isEqualTo(passType2);
assertThat(duration1).isEqualTo(duration2);
assertThat(price1).isEqualTo(price2);
}
}
댓글을 작성해보세요.