인프런 워밍업 스터디 클럽 2기 백엔드(클린코드&테스트코드) 발자국 - 3주차
인프런 워밍업 클럽 2기, 백엔드(클린코드&테스트코드) 과정에 참여하고 있습니다.
이번 3주차에는 테스트에 대한 이론적인 내용과 TDD, 실용적인 테스트 방법등을 학습했습니다.
[학습 요약]
테스트의 필요성
왜 작성해야 할까?
사람이 수동으로 직접 테스트하는 데에는 한계가 있다.
테스트 코드 작성을 통해 얻는 이점
빠른 피드백
자동화
안정감
테스트 코드를 작성하지 않는다면..
변화가 생기는 매순간마다 발생할 수 있는 모든 Case를 고려해야 한다.
변화가 생기는 매순간마다 모든 팀원이 동일한 고민을 해야한다.
빠르게 변화하는 소프트웨어의 안정성을 보장할 수 없다.
테스트 코드가 병목이 된다면..
프로덕션 코드의 안정성을 제공하기 힘들어진다.
테스트 코드 자체가 유지보수하기 어려운, 새로운 짐이 된다.
잘못된 검증이 이루어질 가능성이 생긴다.
올바른 테스트 코드는,
자동화 테스트로 비교적 빠른 시간 안에 버그를 발견할 수 있고, 수동 테스트에 드는 비용을 크게 절약할 수 있다.
소프트웨어의 빠른 변화를 지원한다.
팀원들의 집단 지성을 팀 차원의 이익으로 승격시킨다.
가까이 보면 느리지만, 멀리 보면 가장 빠르다.
테스트는 귀찮다. 귀찮지만, 해야한다!
단위 테스트
작은 코드 단위를 독립적으로 검증하는 테스트
작은 코드: 클래스 or 메서드
검증 속도가 빠르고, 안정적이다.
외부에 의존을 하지 않기 때문에
JUnit5
단위 테스트를 위한 테스트 프레임워크
XUnit - Kent Beck
SUnit(SmallTalk), JUnit(Java), NUnit(.NET), DUnit(Delphi)
AssertJ
테스트 코드 작성을 원할하게 돕는 테스트 라이브러리
풍부한 API, 메서드 체이닝 지원
테스트 케이스 세분화하기
항상 질문하기: 암묵적이거나, 아직 드러나지 않은 요구사항이 있는가?
해피 케이스와 예외 테스트를 도출해낼 수 있어야 한다.
경계값 테스트
경계값?: 범위(이상, 이하, 초과, 미만), 구간, 날짜 등
테스트하기 어려운 영역을 구분하고 분리하기
테스트하기 어려운 영역을 갖고 있는 코드가 추가된다면, <u>전체 테스트가 망가질 가능성</u>이 생긴다.
예시)
LocalDateTime.now()
를 사용하는 기능을 테스트할 때, 테스트를 실행하는 시간에 따라 성공할수도, 실패할수도 있게 됨.이 때,
LocalDateTime.now()
를 사용하는 코드가 곧 테스트하기 어려운 영역이 된다.
이 때, 테스트가 어려운 영역을 외부에서 주입받도록 변경하면 테스트 하기 수월해진다.
외부로 분리할수록, 테스트 가능한 코드는 많아진다.
테스트하기 어려운 영역 코드 예시
관측할 때마다 다른 값에 의존하는 코드
현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등
외부 세계에 영향을 주는 코드
표준 출력, 메시지 발송, 데이터베이스에 기록하기 등
테스트하기 쉬운 영역 코드 예시
순수 함수
같은 입력에는 항상 같은 결과 반환
외부 세상과 단절된 형태 (전역 변수를 변경하지 않는)
TDD
프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론
[Red - Green - Refactor] 3단계 구조로 순환하며 진행
Red: 실패하는 테스트 작성
의도적으로 실패하게 테스트를 작성하라는게 아니다.
도메인적 지식을 기반으로, 테스트가 통과해야만 하는 조건으로 테스트 코드를 작성하라는 의미.
ex) 테스트에서는
10
이라는 값이 나올것을 기대하지만, 구현이 미완성이라서0
이 리턴되는 경우에 기대값을 변경하지 않고 그대로 두는 것을 말한다.
Green: 테스트 통과, 최소한의 코딩
최대한 빠르게 테스트 통과만을 위한 코딩을 수행.
TDD 방법론에서는 하드코딩도 허용.
Refactor: 구현 코드 개선, 테스트 통과 유지
구현부 코드를 변경한 다음에도 테스트가 통과 된다 -> 구현이 올바르게 되었다 라고 판단할 근거가 된다.
피드백
TDD가 제공하는 핵심 가치
테스트 코드를 통해 내가 구현한 프로덕션 코드에 대해서 자주, 빠르게 피드백을 받을 수 있다.
기능 구현 후 테스트 작성 vs. 테스트 작성 후 기능 구현
선 기능 구현, 후 테스트 작성 케이스
테스트 자체의 누락 가능성
특정 테스트 케이스(해피 케이스)만을 검증할 가능성
잘못된 구현을 다소 늦게 발견할 가능성
선 테스트 작성, 후 기능 구현
복잡도가 낮은, 테스트 가능한 코드로 구현할 수 있게 한다.
복잡도가 낮다?: 유연하고, 유지보수하기 쉽다
쉽게 발견하기 어려운 엣지(Edge) 케이스를 놓치지 않게 해준다.
구현에 대한 빠른 피드백을 받을 수 있다.
과감한 리팩토링이 가능해진다.
TDD: 관점의 변화
TDD 이전의 관점: 테스트는 구현부 검증을 위한 보조 수단
TDD 이후의 관점: 테스트와 상호 작용하며 발전하는 구현부
TDD는 클라이언트 관점에서 피드백을 주는 Test Driven 도구이다.
테스트는 "문서"다
테스트는 "문서"다. 왜?
프로덕션 기능을 설명하는 테스트 코드 문서
다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완
어느 한 사람이 과거에 경험했던 고민의 결과물을 팀 차원으로 승격시켜서, 모두의 자산으로 공유할 수 있다.
우리는 항상 팀으로 일한다
DisplayName을 섬세하게
명사의 나열보다 문장으로 작성하자
테스트 행위에 대한 결과까지 기술하기
before: 음료 1개 추가 테스트
after: 음료를 1개 추가할 수 있다.
도메인 용어를 한층 추상화된 내용을 담기
메서드 자체의 관점보다, 도메인 정책관점으로 생각하자.
테스트의 현상을 중점으로 기술하지 말 것
before: 특정 시간 이전에 주문을 생성하면 실패한다.
after: 영업 시작 시간 이전에는 주문을 생성할 수 없다.
BDD, Behavior Driven Development
TDD에서 파생된 개발 방법
함수 단위의 테스트에 집중하기 보다, 시나리오에 기반한 테스트케이스(TC) 자체에 집중하여 테스트한다.
개발자가 아닌 사람이 봐도 이해할 수 있을 정도의 추상화 수준(레벨)을 권장
Given / When / Then
Given: 시나리오 진행에 필요한 모든 준비 과정 (객체, 값, 상태 등)
When: 시나리오 행동 진행
Then: 시나리오 진행에 대한 결과 명시, 검증
Spring & JPA 기반 테스트
레이어드 아키텍처와 테스트
Layered Architecture 는 관심사의 분리를 위해 수행한다!
책임을 나누고, 유지보수하기 용이하게 만들자
Spring & JPA 기반 애플리케이션에서 일반적으로 사용하는 레이어 구조
User <-> [Presentation Layer] <-> [Business Layer] <-> [Persistence Layer] <-> DB
통합 테스트
여러 모듈이 협력하는 기능을 통합적으로 검증하는 테스트
일반적으로 작은 범위의 단위 테스트만으로는 기능 전체의 신뢰성을 보장할 수 없다.
풍부한 단위 테스트 & 큰 기능 단위를 검증하는 테스트
Spring / JPA 훑어보기
스프링은 Library? Framework?
라이브러리는 내 코드가 주체가 된다.
프레임워크는 말 그대로 이미 프레임이 짜여있고, 그 프레임에 짜맞추는 방식으로 코드를 작성하게 됨.
Spring
IoC(Inversion of Control)
DI(Dependency Injection)
AOP(Aspect Oriented Programming)
ORM (Object-Relational Mapping)
객체 지향 패러다임과 관계형 DB 패러다임의 불일치
이전에는 개발자가 객체의 데이터를 한땀한땀 매핑하여 DB에 저장 및 조회 (CRUD)
ORM을 사용함으로써 개발자는 단순 작업을 줄이고, 비즈니스 로직에 집중할 수 있다.
JPA (Java Persistence API)
Java 진영의 ORM 기술 표준
인터페이스이고, 여러 구현체가 있지만 보통 Hibernate를 주로 사용한다.
반복적인 CRUD SQL을 생성 및 실행해주고, 여러 부가 기능들을 제공한다.
편리하지만 쿼리를 직접 작성하지 않기 때문에, 어떤 식으로 쿼리가 만들어지고 실행되는지 명확하게 이해하고 있어야 한다.
Spring 진영에서는 JPA를 한번 더 추상화한 Spring Data JPA 제공
QueryDSL과 조합하여 많이 사용한다.
타입 체크, 동적 쿼리
JPA 에서 주로 사용되는 어노테이션들
@Entity, @Id, @Column
@ManyToOne, @OneToMany, @OneToOne, @ManyToMany
@ManyToMany: 일대다 - 다대일 관계로 풀어서 사용한다.
[후기]
본격적으로 테스트 코드에 대한 학습을 시작한 주간이었다. 개발자로 일을 한지 꽤 오래되었지만 테스트를 작성해본 적이 많지 않아서 테스트를 작성하는것에 언제나 애를 먹었었는데, 기초적이고 이론적인 내용부터 자세히 공부하고 예제로 따라해보니 지금까지 잘 모르고 지금까지 완벽하게 이해하지 못하고 넘어갔었던 내용들을 어느정도 해소할 수 있었던 것 같아 좋았다.
댓글을 작성해보세요.