[TDD] TDD의 기초개념을 알아보자
- 5 mins주의
비 정기적으로 수정하는 문서
Layered Architecture
계층형 설계
- Web Layer
- DTO
- Service Layer : 비즈니스 로직을 구현하는 부분이 아님!, DB에서 데이터를 조회하고 도메인 객체에 메시지를 보내는 역할.
- Domain : 상태(Data)와 행위를 갖음
- Repository Layer : DB와 직접적인 연결관계를 맺는 부분, DB뿐만아니라 외부 API와의 연결도 해당 Layer에서 담당한다.
TDD
아직은 내게 어려운 친구…
Test Driven Development : 테스트 주도 개발
- 테스트는 빠른 시점에 피드백을 받을 수 있는 유용한 도구이다.
- 테스트를 버그를 찾기 위한 목적보다 새로운 라이브러리, 프레임워크를 빠르게 학습하기 위한 목적 으로 활용하자.
테스트하기 쉬운 코드는
- 새로운 기능이 추가되었을 때 확장하기 쉬운 구조. - 기능이 변경되었을 때 기능을 변경하기 쉬운 구조. - (100% 일치하는 것은 아니지만) 테스트하기 쉬운 코드의 경우 상당 부분 확장하기 좋고, 변경하기 좋은 구조를 가진다.
TDD CYCLE
메소드를 짤 때 항시 테스트 코드를 먼저 작성하고 로직을 짤 것
1. 실패하는 단위 테스트를 추가한다.
2. 테스트를 통과하도록 프로덕션 코드를 구현한다.
3. 리팩토링한다.
{ 위 과정을 반복한다. }
- production package와 test package는 동일해야한다.
접근제어자를 default로 하여 접근이 가능하게 한다.
Clean Code
오류처리
오류코드보다 예외를 사용할 것
오류코드를 사용하면 호출자가 복잡해짐. 또한 예외 상황을 잊어버리고 처리안 할 수 있음
- checkedException 말고 UnCheckedException을 발생시켜라.
null을 반환하지마라
- element가 없는 경우 빈 리스트를 반환할 수 있다.
Collections.emptyList();
- Optional 사용
Optional 변수의 이름은 maybe로 시작하는게 관례다.
Optional<User> maybeUser; // maybe[object name]
시스템
시스템 제작과 시스템 사용을 분리하라
- 소프트웨어 시스템은 (응용 프로그램 객체를 제작하고 의존성을 서로 ‘연결’하는) 시작 단계와 (시작 단계 이후에 이어지는) 실행 단계를 분리해야 한다.
- 사용과 제작을 분리하는 강력한 메커니즘 하나가 의존성 주입(Dependency Injection, DI)이다.
확장
소프트웨어 시스템은 물리적 시스템과 다르다. 관심사를 적절히 분리해서 관리한다면 소프트웨어 아키텍처는 점진적으로 발전할 수 있다.
관심사의 분리
AOP를 활용해 관심사를 분리한다면 시스템이 커지는 상황에서 확장에 유연하게 대처할 수 있다.
시스템 아키텍처 시운전
- 코드 수준에서 아키텍처 관심사를 분리할 수 있다면, 진정한 아키텍처 시운전이 가능해진다.
- 그때 그때 새로운 기술을 채택해 단순한 아키텍처를 복잡한 아키텍처로 키워갈 수도 있다.
- BDUF(Big Design Up Front)를 추구할 필요가 없다.
의사 결정을 최적화하라
- 관심사를 모듈로 분리한 POJO 시스템은 기민함을 제공한다.
- 이런 기민함 덕택에 최신의 정보에 기반을 두어 최선의 시점에 최적의 결정을 내리기가 쉬워진다.
ATDD
Acceptance Test Driven Development : 인수 테스트 주도 개발
- 단위테스트는 해당하는 메소드가 원하는 값을 출력하는지 테스트.
- ATDD는 브라우저에 입장에서 서버를 띄우고 디비에 접속해서 제대로 값이 넘어오는지 확인하는 테스트.
기존 TDD의 문제점
- TDD는 단위 테스트에 중점. TDD는 특정 부분에 대한 검증에 있어서는 뛰어난데 좀 더 큰 그림을 그리고, 전체 영역을 감당하는데는 한계.
- TDD를 통해 단위 테스트 커버리지도 높고, 품질 관리도 잘 되는 것처럼 보였는데 기능을 통합했을 때 전체적인 부분에서 삐끄덕 거리는 경우가 발생하는 경우가 생겼다.
- 경우에 따라서는 품질 지표는 좋은데 서비스를 오픈하기 힘든 상황까지 발생했다. 이 같은 이유는 TDD가 너무 국한된 부분에 대한 고려만 강조하다보니 전체적인 부분을 놓치는 경우가 많았다.
TDD의 단점을 보완해줄 ATDD
TDD의 단점을 보완하기 위해 인수 테스트(acceptance test)를 먼저 먼저 구현한 후 이후 단위 테스트를 통해 기능을 완성해 가는 과정으로 애플리케이션을 개발할 수 있다.
Acceptance Test 란?
- 시스템 내부 코드를 가능한 한 직접 호출하지 말고 시스템 전 구간을 테스트해야 한다. 즉, end-to-end 테스트
- 인수 테스트는 보통 기능 테스트, 고객 테스트, 시스템 테스트와 같은 용어로도 사용된다.
- 인수테스트
전체 시스템이 동작하는가?
- AcceptanceTest : Web(Controller) layer Test
@RunWith(SpringRunner.class)
@SpringBootTest
- 통합 테스트
변경할 수 없는 코드를 대상으로 코드가 동작하는가?
- MockTest : 보통 Service layer를 Test할 때 한다.
- 변경할 수 없는 바깥의 코드(JPA, API)를 어떻게 이용할지 검사하는 테스트
- DB, 외부 API에 의존해 진행하는 테스트는 통합 테스트
- 단위 테스트
객체가 제대로 동작하는가? 객체를 이용하기가 편리한가?
- UnitTest : Domain(Model) Test
TDD 프로세스 유지
각 기능을 인수 테스트로 시작하라.
- 프로덕션 코드를 만들기 전에 테스트를 작성하면 달성하고자 하는 바가 명확해 진다.
- 테스트로 시작하면 사용자 관점에서 시스템을 바라보게 되어 사용자가 필요로 하는 것을 이해하게 된다.
테스트를 가장 간단한 성공 케이스에서 시작하라
- 기능 구현을 시작할 때 실패 케이스에 집중하면 의욕을 저하시킬 수 있다.
- 일단 가장 간단한 성공 케이스부터 테스트를 시작한다.
입력에서 출력 순서로 개발하라
- 외부 이벤트를 받는 객체에서 중간 계층을 거쳐 중심 도메인 모델로 나아간 다음, 외부에서 확인 가능한 응답을 생성하는, 다른 경계에 위치한 객체에까지 이른다.
- 자바 웹 애플리케이션의 Layered Architecture 기준으로 살펴보면 다음과 같다.
Controller => Service => Repository 순으로 개발.
Domain(Model, Entity) 객체는 각 Layer에서 필요한 시점에 생성
[ATDD 기반으로 로그인 기능을 구현하는 과정]
1. 아이디와 비밀번호가 일치하는 경우에 대한 인수 테스트 구현 2. LoginController => UserService 구현 3. DB에 사용자 데이터 추가를 위한 User Entity, UserRepository 구현 4. 로그인 로직에 단위 테스트를 추가해 로직 완료 5. 아이디와 비밀번호가 일치하지 않는 경우에 대한 인수 테스트 구현 이후 과정 반복
Fixture
https://gist.github.com/bverbeken/5724148
테스트를 진행하다보면 각 Test Case 별로 비슷한 데이터를 계속해서 생성해야 하는 상황이 발생한다. 그래서 각 테스트용 데이터를 매번 만들어 사용한다. 이것을 Fixture(테스트용 데이터)라 한다.
Fixture를 만들기 쉽도록 할 때 Builder Pattern 을 적용하거나, 자주 사용하는 Fixture를 미리 만들어놓고 사용하기도 한다.
Validation
유효성 검사란. 사용자가 입력한 데이터가 유효한지 체크하는 것이다. 유효성 검사는 FE, BE 두 곳 모두에서 해야한다.
만약 불가피하게 한 곳에서만 유효성 검사를 해야한다면 BE는 꼭 해야한다. 왜냐하면 FE는 우회할 방법이 여럿 존재하기 때문에 서버에서 꼭 유효성을 검사해주어야 보안에 문제가 생기지 않는다.
FE와 BE 모두 유효성 검증을 해놨을 때 프론트에서 변경하고 백엔드에서 변경하지 않는다면 문제가 생길 수 있다. 유효성 또한 중복이 될 수 있다.
FE의 유효성 검증은 사용자 경험을 좋게해주는 효과가 있다.