블로그

ykm8864

[인프런 워밍업 클럽 2기] Layered Architecture에서의 Testing 정리

Layered Architecture의 레이어별 특징 및 테스팅 방법 정리예시: 음식 배달 서비스각 레이어를 음식 배달 서비스의 역할로 설명한다. 음식 배달 서비스를 통해 사용자가 음식을 주문하고, 음식점에서 음식을 준비하고, 배달원이 고객에게 음식을 전달하는 과정을 생각해 본다. Persistence Layer특징비지니스 가공 로직이 포함되어서는 안 된다.이 레이어는 데이터베이스와 상호작용하여 음식점의 메뉴, 고객 정보, 주문 기록 등을 저장하고 관리한다. 마치 식당의 재료 창고처럼, 데이터의 상태를 정확히 관리하고 필요한 정보를 보관한다. 창고에서는 식당의 주방장이 재료를 요청하면, 정확한 재료를 꺼내주는 역할을 한다. 데이터의 가공은 하지 않고, 필요한 데이터만 정확하게 주고받는 것이 이 레이어의 역할이다.테스트 방법- 단위 테스트: 재료 창고에서 특정 재료를 꺼내거나, 새로운 재료를 추가할 때 그 과정이 제대로 이루어지는지 검증한다. Mocking을 통해 창고에 직접 접근하지 않고, 데이터가 올바르게 반환되는지 확인한다.- 통합 테스트: 실제 창고에서 재료를 꺼내고 추가하는 테스트를 진행한다. 예를 들어, '김치 찌개 재료'를 창고에 추가하고, 해당 재료를 제대로 꺼낼 수 있는지를 확인한다. Business Layer특징트랜잭션을 보장해야 한다.이 레이어는 주방에서 음식의 조리 과정을 담당한다. 사용자 주문에 따라 어떤 메뉴를 어떻게 조리할지 결정하고, 레시피에 따라 재료를 가공하거나 조리 과정을 관리한다. 주방장은 여러 재료를 받아서 맛있는 요리를 만들어내는 역할을 한다. 마치 식당 주방장이 음식의 맛과 품질을 관리하듯, 비즈니스 로직을 통해 데이터와 요구사항을 처리한다.테스트 방법- 단위 테스트: 주방장이 다양한 재료로 요리를 만들 때 레시피에 따라 올바르게 조리하는지 검증한다. Mocking을 통해 창고에서 직접 재료를 받지 않고, 주방에서의 조리 과정 자체에 집중한다.- 통합 테스트: 주방과 창고가 함께 작동하며 올바른 음식을 내놓는지를 확인한다. 예를 들어, '된장찌개' 주문이 들어왔을 때 창고에서 된장과 야채를 받아, 조리 과정을 거쳐 최종 요리가 완성되는지를 검증한다. Presentation Layer특징MockMvc를 통해 내가 테스트하고자하는 부분에 집중한다.상위레이어가 하위 레이어를 알고 있는 것은 당연하지만, 하위레이어가 상위레이어를 알고 있는 것은 의존성이 강한 구조이다.이 레이어는 완성된 음식을 고객에게 전달하는 역할을 한다. 주로 배달원이 주문한 음식을 정확하게 고객에게 전달하며, 사용자가 원하는 방식대로 음식이 전달되는지 책임진다. 고객의 요구사항을 주방에 전달하고, 그 결과를 받아 다시 고객에게 전달하는 역할을 한다. 배달원이 고객에게 주문한 음식을 가져다주는 것처럼, 이 레이어는 사용자와 가장 가까운 곳에서 상호작용을 수행한다.테스트 방법- 단위 테스트: 배달원이 주문한 음식과 고객의 요청을 제대로 처리하는지 검증한다. Mocking을 통해 주방이나 창고에 접근하지 않고, 배달원과 사용자의 상호작용에 집중한다.- 통합 테스트: 배달원, 주방, 창고가 함께 작동하면서 사용자의 주문을 제대로 처리하는지 검증한다. 예를 들어, 고객이 '비빔밥'을 주문했을 때, 주방과 창고의 도움을 받아 배달원이 정확하게 비빔밥을 전달하는지를 확인한다. 흐름도사용자 <-> Presentation Layer (배달원 역할) <-> Business Layer (주방 역할) <-> Persistence Layer (재료 창고 역할)- 사용자(고객)는 Presentation Layer를 통해 주문을 요청한다.- Presentation Layer는 Business Layer에 주문 내용을 전달한다.- Business Layer는 Persistence Layer에서 필요한 데이터를 가져와 주문에 따라 데이터를 가공한다.- 최종적으로 Presentation Layer는 사용자가 원하는 형식으로 결과를 전달한다. - 각 레이어는 역할을 명확히 나누어, 서로에게 필요한 정보를 정확하게 전달하는 데 집중한다.

웹 개발워밍업클럼2기몰입하는개발자테스트코드TDD

ykm8864

[인프런 워밍업 클럽 백엔드 스터디 2기] 3주차 발자국

워밍업 클럽 백엔드 스터디 2기 3주차 발자국 입니다.본격적인 TDD 가이드에 대하여 배우기 시작했습니다.테스트는 왜 필요할까?테스트 = 귀찮다?프로그램이 커지면 수동테스트의 정확도가 어떨까?사람이 할거야?변화가 생기는 매순간마다 모든 팀원이 동일한 고민을 해야한다면 소프트웨어의 신뢰도가 낮아질 가능성이 높다. 올바른 테스트 코드자동화 테스트로 비교적 빠른 시간 안에 버그를 발견할 수 있고, 수동 테스트에 드는 비용을 크게 절약할 수 있다.소프트웨어의 빠른 변화를 지원한다.팀원들의 집단 지성을 팀 차원의 이익으로 승격시킨다.가까이 보면 느리지만, 멀리 보면 가장 빠르다.단위테스트란작은 코드(클래그 or 메서드) 단위를 독립적으로 검증하는 테스트단위테스트는 검증속도가 빠르고 안정적이다.우리는 JUnit5 와 AssertJ 를 사용하여 테스트 코드를 작성해보았다. 테스트 케이스 세분화하기질문하기 : 암묵적이거나 아지 드러나지 않은 요구사항이 있는가? 해피케이스와 예외케이스에 대하여 테스트 케이스를 세분화 해야한다.-> 경계값 테스트라는 개념을 배웠다.어떠한 정수가 있는데 정수가 3이상일 때, A라는 조건이 성립한다면?⇒ 테스트를 4나 5로 테스트 하는게 야니고 3에 대하여 테스트를 작성하는게 맞다.⇒ 예외 케이스는 2나 1이나 -1과 같은 경계값보다 아래의 경우의 수로 예외케이스를 짜는게 맞다. 테스트하기 어려운 영역 분리하기외부세계에 관련된 도메인은 테스트하기 어려우니 분리하여 가능한 것에 집중한다.input 과 output이 외부세계에 영향을 주거나, 외부세계에 영향을 받는 경우에는 테스트하기 어렵다.ex) 표준출력, 메시지발송, 데이터베이스에 기록하기 등.. 강의에서는 "현재시간" 은 테스트를 돌리는 시간이 바뀜에 따라 매번 결과값이 달라질 수 있으므로 정확한 테스트가 어려워 프로덕트 코드에는 현재시간을 매개변수로 넣고, 테스트할 때는 테스트 하고자 하는 시간을 매개변수로 세팅하여 분리한다. 즉, 테스트 어려운 조건이 생기면 테스트 어려운 영역을 외부로 분리하여 조치한다.TDD프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론RED - GREEN - REFACTOR위 과정으로 TDD가 이루어진다. 실패케이스를 먼저 짜고, 통과를 위한 통과 케이스를 짜고, 리팩토링하고 이를 반복한다.선기능 구현, 후 테스트의 문제점테스트 자체의 누락 가능성(아 테스트 짤 시가니 없네)특정 테스트 케이스만 검증할 가능성(해피케이스만 생각)잘못된 구현을 다소 늦게 발견할 가능성 테스트는 [문서]다.언어가 사고를 제한한다.프로덕션 기능을 설명하는 테스트 코드 문서다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보안해피케이스와 예외케이스를 생각하여 더 깊게 이해할 수 있다.어느 한 사람이 과거에 경험했던 고민의 결과물을 팀차원으로 승격시켜서, 모두의 자산으로 공유할 수 있다.테스트케이스를 통해 고민의 결과를 공유하여 소스코드를 관리할 수 있다.  DisplayName을 섬세하게테스트 하고자 하는 내용을 명확하게 기술하자문장형태로 작성하자!테스트 행위에 대한 결과까지 기술한다.도메인 용어를 사용하여 한층 추상화된 내용을 담기테스트의 현상 중점 기술: "특정 시간 이전에 주문을 생성하면 실패한다."여기서는 "실패한다"는 구체적인 현상을 강조한다. 테스트가 어떤 상황에서 실패하는지를 중심으로 표현한 것이다. 하지만 이 표현은 테스트가 도메인의 규칙이나 의도를 명확하게 설명해주지 못할 수 있다. 실제 비즈니스 로직의 핵심 규칙보다는 실패 여부에 집중하게 되는 문제가 있게된다.도메인 관점에서의 기술: "영업 시작 시간 이전에는 주문을 생성할 수 없다."이 표현은 현상보다는 도메인의 규칙을 명확하게 기술. "영업 시작 시간 이전에는 주문을 생성할 수 없다"는 표현은, 해당 비즈니스 로직이 적용되는 이유나 규칙을 이해하는 데 도움이 되며, 비즈니스 도메인에서의 규칙을 중심으로 설명하는 방식이다. 레이어드 아키텍쳐와 테스트Presentation Layer - Business Layer - Persistence Layer관심사의 분리를 위해 레이어를 분리한다.기본적인 JPA 엔티티를 설계하고 테스트하고자하는 레이어를 분리하였다.Persistence Layer 테스트기본적인 CRUD를 구성한 후 데이터베이스와의 연동테스트를 하였다.EnableJpaAuditing을 통하여 테이블의 상태를 트랙킹하고 H2 테이버베이스 콘솔과 비지니스 구현 로직이 정상 작동하는지 확인하였다.Business Layer 테스트비지니스 로직을 구현하는 역할 Persistence Layer와의 상호작용(data를 읽고 쓰는 행위)를 통해 비지니스 로직을 전개시킨다. 트랜잭션을 보장해야한다.도메인 로직에 대한 테스트 코드를 작성하였다. 이때 트랜잭션에 대한 관리가 중요했다.상품을 주문할 때, 제고차감에 대한 로직에서 트랜잭션 관리의 중요성을 느꼈는데 기본적인 테스트코드에서의 @Transactional 개념은 이러하다.트랜잭션 관리: @Transactional을 사용하면, 해당 테스트 메서드가 실행될 때 자동으로 트랜잭션이 시작된다. 그리고 테스트가 완료되면, 해당 트랜잭션은 롤백처리.데이터 정리: 트랜잭션이 롤백되므로, 테스트 중 데이터베이스에 삽입되거나 수정된 데이터는 테스트가 끝나면 모두 사라집니다. 따라서, 별도의 정리 작업이 필요 없게된다.. 테스트 격리성: 트랜잭션 범위 내에서 테스트가 실행되고, 롤백을 통해 테스트 이전 상태로 되돌아가므로, 테스트 간의 데이터 격리성이 자연스럽게 유지된다.이렇게 보면 무조건 트랜잭션 어노테이션을 사용하는 것이, AfterEach를 통한 수동 삭제보다 이점이 크다는 생각이 들지만, 테스트코드에만 트랜잭션이 잡혀있고 실제 프로덕션 코드에는 트랜잭션이 안 잡혀있을 수 있는 위험성이 있으므로 팀원 내부적인 공지나 개발자의 인지가 필요하다.이런 점을 놓치면 심각한 상황이 발생할 수 있다. 테스트코드는 통과했지만 실제 운영에는 버그가 생기는 것이다. 예를 들어, 주문을 생성하고 주문에 포함된 상품이나 재고를 업데이트하는 작업이 순차적으로 실행될 때, 중간에 예외가 발생하면 앞서 실행된 작업은 롤백되지 않아 장애가 있을 것이다. 3주차 회고Keep (만족했고, 앞으로도 지속하고 싶은 부분)테스트 코드의 기본적인 Given When Then 방식이 익숙해짐을 느꼈다. 더불어 JPA와 Spring Data JPA에 대한 개념을 다시 한번 다지는 기회도 갖을 수 있었다. 또한, 단위테스트와 통합테스트에 대한 개념이 실무에서는 어떻게 적용되어야 하는지 인지할 수 있었다..Problem (아쉬웠던 점)기본적으로 JUnit 과 AssertJ의 메서드에 아직 친숙한 느낌은 아닌 거 같다. 그러다보니 강의를 들으며 도메인을 따라가면서 코드까지 신경쓰는 것이 어려운 부분이 있었다. 천천히 다시 한번 강의를 들으며 다시 학습하는 과정이 필요해보인다. Try (다음에 시도해볼 점)세부적으로 여러가지를 알려주셔서 좋았다. 다음에는 개인프로젝트로 동시성문제에 대해서 좀 더 고찰해보는 시간을 가지고 싶다. 지나가면서 말씀해주신 ptimistic lock / pessimistic lock 에 대해서도 공부해보고 싶다.

백엔드워밍업클럽2기몰입하는개발자백엔드테스트코드TDD

Java Spring 기반의 Test와 TDD

Test테스트는 항상 통과해야하며, 예외를 포함한 최대한 많은 케이스를 다룰수록 좋다.테스트 종류Unit Test단위적인 기능을 개별적으로 테스트하는 것Mock행위 검증; 메소드의 리턴 값으로 판단할 수 없는 경우, 특정 동작을 수행하는지 확인Stub상태 검증; 객체의 상태를 확인하여 올바르게 동작했는지를 확인Fake객체의 행위를 모킹하는 것이 아닌 객체자체를 모킹하는 방식인메모리DB, Map 더미 데이터 등을 사용Integration Test상호의존적 환경 내에서 전반적인 흐름을 테스트 TDD 실습1단계 : 테스트 코드 작성 후 테스트 실행(실패)JPA Repository 또는 Fake Repository 생성Test@ExtendWith(SpringExtension.class) class BoardServiceTest { @Mock BoardRepository boardRepository; @Test void boardSave(){ // given long boardId = 1l; BoardRequestDto boardRequestDto = BoardRequestDto.builder().build(); // when BoardService boardService = new BoardService(); BoardResponseDto boardResponseDto = boardService.save(boardRequestDto); // then assertThat(boardId).isEqualTo(boardResponseDto.getBoardId()); } class BoardService { BoardResponseDto save(BoardRequestDto boardRequestDto){ return null; } } }BoardRequestDto 생성@Getter @Builder @NoArgsConstructor public class BoardRequestDto { private long writerId; private long categoryId; private String title; private String content; }2단계 : 비즈니스 로직 필요만큼 추가 후 테스트 실행(통과)Test@ExtendWith(SpringExtension.class) class BoardServiceTest { @Mock BoardRepository boardRepository; @Test void boardSave(){ // given long boardId = 1l; BoardRequestDto boardRequestDto = BoardRequestDto.builder().build(); // when BoardService boardService = new BoardService(); BoardResponseDto boardResponseDto = boardService.save(boardRequestDto); // then assertThat(boardId).isEqualTo(boardResponseDto.getBoardId()); } class BoardService { BoardResponseDto save(BoardRequestDto boardRequestDto){ BoardEntity boardEntity = BoardEntity.builder() .writerId(boardRequestDto.getWriterId()) .build(); BoardEntity savedBoard = boardRepository.save(boardEntity); return new BoardResponseDto(savedBoard); } } }  3단계 : 반복categoryId 검증과 같은 필수 로직을 하나씩 추가하며 테스트를 반복한다.4단계 : 확장도메인 메서드로 분리하거나 중복 로직을 메서드화하며 리팩터링한다.

TDDtest

<도서정리> 테스트 주도 개발 시작하기 - 최범균

TDD 단계: 레드 - 그린 - 리팩토링 테스트 선 작성 후 테스트를 통과시킬 만큼의 코드를 작성한다.(기능을 구현한다)마지막으로 리팩토링을 통해 코드를 다듬는다.테스트를 통해 개발의 범위가 정해진다. 테스트가 진행될수록 검증하는 범위가 넓어지면서 기능 구현이 점점 완성되어간다. TDD는 개발 과정에서 코드를 지속적으로 정리하므로 코드품질을 높이고 유지보수 비용을 낮춘다.코드 수정 후 테스트가 진행되므로 향후 올바르지 않은 코드가 배포되는 것을 방지할 수 있다.   테스트 코드 작성 순서 쉬운 경우(넓은 범위)에서 어려운 경우로 진행 예외적인 경우에서 정상인 경우로 진행 한 번에 많은 코드를 만들면, 그 사이 발생한 버그를 잡기 위해 많은 비용이 들어간다. 정해진 값 리턴 값 비교를 통해 정해진 값 리턴 다양한 경우 추가하면서 구현을 일반화 테스트를 먼저 작성하고 return 상수로 테스트 통과시킨 후에 비즈니스 로직 하나씩 추가하면서 테스트 매번 실행   대역 MockitomemoryRepository implements 리포지토리 인터페이스 - Map 사용하기@TempDir   기능명세 테스트할 기능을 생성하면서 클래스, 메서드, 파라미터 구성결과 검증하면서 리턴값 구성-> 테스트하면서 설계까지 진행됨클래스, 메서드, 변수명은 주석보다 코드 이해에 효과적이게 구성하기필요한만큼만 설계하기예외적인 상황, 복잡한 상황은 최대한 많이 기획자와 상의하고 대비하는 것이 효율적이고 완성도 높은 개발을 이끌어냄   리팩토링 기능 개발을 다 끝낸 후에 리팩토링하기또는 커밋 후에 리팩토링하기리팩토링은 클래스 분리, 메서드 추출, or문, count 로직 등 이용...파라미터 개수가 3개 이상이면 객체화하기 꿀팁 Assert 메서드를 테스트 클래스에 별도 생성해서 응용도 가능하다.로직을 중복 호출하지 않고, Assert 메서드에 로직을 넣어버려서 검증만 진행하도록 할 수도 있다. 중간에 예외처리를 하면, 조건문 중복 등 코드가 복잡해지기 때문에초반에 예외처리를 하는 것이 시스템 운영 중에 NPE를 만나지 않는 방법이다. before setUp으로 데이터를 미리 생성해놓는 것은 중복이 제거되는 듯이 보이지만,각 테스트마다 필요한 데이터가 항상 일정하지 않을 수 있음으로 각 테스트에서 데이터 생성하는 것을 추천한다. 검증 시 굳이 변수나 필드명으로 검증하는 것보다 기댓값을 실제값으로 넣는 것도 좋다.테스트에서는 직관적으로 확인할 수 있는 것이 좋기 때문이다. given으로 파라미터를 넘길 때는 값보다 타입으로 넘겨주자.(범용성) update와 같은 내부구현 검증이 필수적일 때는 내부구현 검증이 필요하다.하지만 웬만하면 실행결과 검증 중심으로 진행할 것.테스트 실패의 경우를 줄이기 위해서 검증해야할 부분을 명확히 하기 위해서이다.

TDDtest최범균junit도서

채널톡 아이콘