인프런 워밍업 스터디 클럽 2기 백엔드(클린코드&테스트코드) 발자국 - 3주차

인프런 워밍업 스터디 클럽 2기 백엔드(클린코드&테스트코드) 발자국 - 3주차

인프런 워밍업 클럽 2기, 백엔드(클린코드&테스트코드) 과정에 참여하고 있습니다.

이번 3주차에는 테스트에 대한 이론적인 내용과 TDD, 실용적인 테스트 방법등을 학습했습니다.

강의 링크: Readable Code: 읽기 좋은 코드를 작성하는 사고법

 


[학습 요약]

 

테스트의 필요성

왜 작성해야 할까?

  • 사람이 수동으로 직접 테스트하는 데에는 한계가 있다.

  • 테스트 코드 작성을 통해 얻는 이점

    • 빠른 피드백

    • 자동화

    • 안정감

테스트 코드를 작성하지 않는다면..

  • 변화가 생기는 매순간마다 발생할 수 있는 모든 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: 일대다 - 다대일 관계로 풀어서 사용한다.

 


[후기]

본격적으로 테스트 코드에 대한 학습을 시작한 주간이었다. 개발자로 일을 한지 꽤 오래되었지만 테스트를 작성해본 적이 많지 않아서 테스트를 작성하는것에 언제나 애를 먹었었는데, 기초적이고 이론적인 내용부터 자세히 공부하고 예제로 따라해보니 지금까지 잘 모르고 지금까지 완벽하게 이해하지 못하고 넘어갔었던 내용들을 어느정도 해소할 수 있었던 것 같아 좋았다.

댓글을 작성해보세요.

채널톡 아이콘