2024. 1. 4. 19:57ㆍ개인노트-인강
사이드 프로젝트:10개 기술스택으로 구현하는 풀스택 서버리스 프로젝트 with React
Part 3. 프로젝트 설계하기
Ch03. TDD - 테스트 주도 개발
# TDD(Test-Driven Development)를 활용하기
- "Test-Driven Development"의 약자 한국어로는 "테스트 주도 개발"
- 디자인 → 로직 구현 → 테스트 ❌
- 디자인 → 테스트 코드 → 로직 구현 ✅
- 소프트웨어를 동작시키기 위한 로직을 구현하기 전에 테스트 코드를 먼저 구현하는 것을 프로세스화 한 개발 방법(론 중의 하나이다.)
## 테스트 코드가 왜 중요한가요?
- 작성한 코드가 의도적으로 동작하는지 수시로 빠르게 검증할 수 있다. 매번 서버를 돌려서 수동적으로 input/output을 검증하는 비효율적인 방법에서 벗어날 수 있음.
- 리팩토링을 할 때 → 리팩토링 후에도 소프트웨어가 여전히 같은 기능을 제공할 수 있도록 안정망 역할
- 잘 작성된 테스트 코드는 소프트웨어의 명세서가 되기도 함.
기능이 정상 동작할 경우, 예외를 일으킬 경우 등 코드를 직접 읽기보다 가독성이 높은 human-language로 테스트 케이스를 읽는 것이 더 빠르게 이해할 수 있습니다.
👩🏫
저는 동료 코드 리뷰 시에도 테스트 코드를 먼저 읽으면서 해당 기능/객체에 대해 빠진 테스트가 있는지, 이 테스트들이 논리적으로 말이 되는지 보곤 합니다.
- 매 배포 전, 전체 테스트 코드를 돌려 품질이 보증된 소프트웨어를 일정하게 제공할 수 있다.
## 그렇다면 TDD는 왜 중요하죠?
- 테스트 코드를 먼저 작성하지 않으면, 의식의 흐름대로, 기능 단위로, 혹은 이곳 저곳.. 방대한 양의 코드를 작성해나가기 시작한다. 문제가 발생시 확인해야 할 코드의 범위가 넓어진다
- 테스트 코드를 먼저 작성하면, 예외적인 상황을 미리 고민하고 정리하는 과정을 통해 버그가 생기는 것을 사전에 방지 할 수 있다. 즉, 버그를 만들 확률 🔻
- 객체 지향적 설계도 가능. 각 객체를 테스트하는 관점에서 특정 객체가 어떻게 동작해야 할지, 객체 간 어떻게 메시지를 주고받아야 할지 먼저 논리적으로 생각해볼 수 있기 때문
- 테스트 코드를 작성하는 과정에서 기능 구현을 위한 설계 요소를 고민하게 되며 구조적으로 더 나은 코드를 생산할 수 있다
- 테스트 코드가 추가될 때마다 검증되는 범위가 넓어지므로, 소프트웨어의 품질을 높일 수 있다
test('그룹명을 입력하지 않고, 저장할 경우 에러 메세지 노출', () => {
render(<CreatingGroup/>)
const saveButton = screen.getByText('저장')
fireEvent.click(saveButton)
await waitFor(() => {
expect(getByText('그룹명을 입력해 주세요')).toBeInTheDocument()
})
})
## TDD는 어떻게 사용하나요?
1. 실패하는 테스트 작성
test('그룹명을 입력하지 않고, 저장할 경우 에러 메시지 노출', () => {
expect(false).toBe(true)
})
- 테스트 시나리오를 메소드화 한다는 것에 촛점을 맞춘다.
- 테스트 메소드명은 ‘어떤 것을 테스트하고자 하는지' 목적을 담자.
- 핵심 로직을 검증하는 단계가 아니므로, 우선 테스트는 실패하도록 한다.
2. 테스트 통과 시키기
test('그룹명을 입력하지 않고, 저장할 경우 에러 메세지 노출', () => {
render(<CreatingGroup/>)
const saveButton = screen.getByText('저장')
fireEvent.click(saveButton)
await waitFor(() => {
expect(getByText('그룹명을 입력해 주세요')).toBeInTheDocument()
})
})
- 테스트를 통과 시킬 만큼의 코드만 구현하는 과정.
- 목(Mock) 객체 활용
- 목(Mock) 객체 = 가짜 객체
- 소프트웨어란 결국, 객체 간의 통신을 통해 사용자에게 기능을 제공하는 것인데,
- 한 객체를 테스트 하기 위해서 엮인 모든 객체(혹은 외부와의 통신)를 다 initialize 하고 기능을 테스트 하기란 비효율 적임
- 객체 간의 의존성을 다 충족 시키는 것에 집중하다 보면 실제 객체의 기능 테스트 본질도 흐려짐
- 따라서 대상 객체에서 직접 참조하는 객체들은 mock 객체로 대체 → 발생하는 이벤트에 대해 대상 객체가 mock 객체와 어떻게 소통할 지 직접 지정할 수 있다 → 대상 객체를 테스팅 하는데에만 집중할 수 있다.
3. 리팩토링
- 프로덕션 코드의 구조를 개선하는 과정
- 2번 까지는 테스트를 통과시키기 위해 최소한의 코드를 작성했을 것이다. 이제는 객체 지향적으로, 가독성을 높일 수 있도록 하기 위해 구조적 개선을 하는 데에 집중
- 리팩토링 틈틈이 테스트 코드를 돌려 계속 성공 시킴으로써, 리팩토링 후에도 동일한 기능을 제공 보장
# 테스트 작성의 정석 - 의미 있는 테스트 작성하기
## 테스트의 종류
### 단위 테스팅(Unit testing)
- 하나의 모듈/컴포넌트/클래스가 기대한대로 동작하는지, 제공하는 기능들을 테스트
- 대상 컴포넌트에서 의존하는 일부 대상은 목(mock) 객체를 이용하여 테스트 하기 편한 환경을 구축할 수 있다
- 개발자가 작성
- 자동화된 테스팅
### 통합 테스팅(Integration testing)
- 두 개 이상의 모듈이 잘 연동/연결이 되었는지 테스팅. 모듈 간에 발생하는 에러 검증
- e.g. 3rd party API를 호출하면 어떤 응답을 기대하는지 테스팅
- 개발자가 작성
- 자동화된 테스팅
### E2E 테스팅(End-to-end testing)
- 실제 사용자가 이용하는 환경과 최대한 유사하게 만들어 사용자의 경험을 전반적으로 테스팅
- 사용자의 입장에서 시스템이 기능을 올바르게 제공하는지 테스팅
- e.g. 시나리오: 사용자가 ‘그룹 생성하기' 버튼을 클릭하면 해당 페이지로 리다이렉팅 된다.
- QA 조직이 따로 있을 경우, 개발자가 아닌 QA 전문가가 작성하곤 한다
- 자동화된 테스팅
### 인수 테스팅(Acceptance testing)
- 시스템이 주어진 요구사항을 잘 충족하는지 테스팅
- 자동화된 테스팅 ❌. 사람이 수동으로 테스팅
## 테스트 작성 순서
단위 테스트 ➡️ 통합 테스트 ➡️ E2E 테스트
## 테스트의 영역
- 프론트 엔드, 백엔드 등
## 의미 있는 테스트 작성하기
### 반드시 테스트 해야할 것
✅ 사용자 요구사항(user requirement)이 모두 테스트 케이스화 되어있는가?
✅ 백엔드의 인터페이스를 테스트할 경우, 잘못된 input을 입력했을 때, 예상된 응답을 내려주는가? (에러코드 vs Exception을 throw)
✅ 프론트엔드에서 사용자가 이용할 기능이 동작하는가?
- 기능 테스트에 초점을 맞춰야 합니다!
- 페이지에 중요한 버튼이 렌더링 되는가?
- 해당 버튼을 클릭하면 예상한대로 동작하는가?
### 테스트 하지 않아도 되는 것
❌ 모든 라인을 반드시 테스트 해야 겠다!
❌ 페이지가 반응성(responsiveness web)인지 테스트 해야겠다!
- e.g. “PC 에서는 버튼이 화면 중앙에, 모바일에서는 버튼 위치가 화면 하단에 있어야 한다”
## 마무리
구현하고자 하는 기능이 있을 때,
어디까지 테스트 케이스를 작성 해야 하는가? 에 대한 고민은 항상 있습니다.
고민하고 항상 ‘이게 맞는가? 왜 필요한가? 왜 필요하지 않은가?’에 대한 질문을 끊임없이 하세요.
여러분의 성장에 좋은 거름이 될 것입니다.
# 마무리 정리 Recap
## 시스템 설계
- 시스템의 요구사항을 충족하기 위해 필요한 아키텍처, 인터페이스 및 데이터를 정의하는 과정.
- 소프트웨어를 구성하는 요소들 관의 관계를 정의하고 동작 메커니즘을 표현하기 위한 구조체.
### 설계 유형
- 아키텍처 설계: 소프트웨어의 전체 구조를 high-level에서 기술. 구성 요소를 정의하고 요소들 간의 관계를 정의
- 자료구조 설계: 소프트웨어의 요구사항을 충족시키기 위해 필요한 요소들을 자료구조로 변환하여 설계하는 과정
- 인터페이스 설계: 사용자와 소프트웨어 간, 소프트웨어를 구성하는 구성 요소 간 어떻게 통신하는지 프로토콜과 주고받을 데이터 내용 등을 설계하는 과정
### 설계 과정
## 아키텍처 다이어그램
- 아키텍처 설계를 위해서는 아키텍처 다이어그램을 그릴 수 있습니다.
- 소프트웨어를 구성하는 구성 요소 간의 상호 작용 및 의존성을 high-level에서 가시화 한 다이어그램
- 1. 구성 요소 나열
- 클라이언트
- 서버/서비스
- 데이터베이스, 스토리지
- 2. 구성 요소 간 상호 작용 표기 ➡️ ⬅️
## 시퀀스 다이어그램
- 객체 간의 상호 작용을 시간 순으로 시각화 한 다이어그램
- 객체/참여자 나열(User, Web Client, DutchPay Server, Group)
- 사용자, 데이터베이스, 시스템/서비스, 클래스
- 객체/참여자 간 메시지를 순서대로 정의
- 동기 메시지 전송
- 비동기 메시지 전송
- 자체 메시지 전송
- 동기 반환
- 비동기 반환
- (옵션) 메시지를 전송하는 조건이나 반복이 필요할 경우 명시
- [condition] or Options = If
- Alt = If/else
- Loop = While
## 클래스 다이어그램
- 한 시스템을 구성하는 클래스들의 구조에 촛점을 맞춘, 속성(attribute), 메소드(method)를 시각화 한 다이어그램
1. 클래스 정의 ➡️ 속성 + 메소드
2. 클래스 간 관계 정의 ➡️ 상속 vs 조합
## 기술셋을 선정하는 기준
1. 시스템의 요구사항을 잘 충족시키기 위해 필요한 것들 리스팅
2. 후보군 조사 - 리서치 하는 시간을 충분히 가질 것!
3. 비교 테이블 생성 및 분석
4. 팀 내 토론 및 결정
## 더치페이 서비스에서 사용할 기술 스택 소개
### AWS Amplify
모바일/웹 어플리케이션을 빠르게 구성+빌드+배포+운영까지 모든 라이프사이클을 한 곳에서 관리할 수 있도록 통합해둔 AWS의 풀스택 개발 통합 솔루션 서비스
### React
컴포넌트 기반, 사용자 인터페이스를 만들기 위한 Javascript 라이브러리
React 컴포넌트 잘 설계하기
1. 페이지별로 컴포넌트를 정의한다
2. UI 디자인 기반, 공통된 요소들이 있는지 파악 후 컴포넌트 화
3. 사용할 만한 컴포넌트 디자인 패턴이 있다면 적용
- Container 패턴: UI를 렌더링하는데에 집중하는 컴포넌트, 데이터 불러오기 등의 비지니스 로직을 담아둘 Container 컴포넌트로 나눠 설계
- Provider 패턴: 컴포넌트 내 props drilling (프로퍼티 내리꽂기)을 방지하고자 생긴 패턴. [React Context](https://www.notion.so/3-Class-diagram-for-a3bc66c28d0242a59fe335617ceaf65b?pvs=21)나 Redux/Recoil과 같이 중앙 상태 관리를 이용하여 데이터를 저장해 두고, 필요한 모든 컴포넌트에서 이 데이터에 접근 가능하도록 한 패턴.
4. 각 컴포넌트에 최소한으로 필요한 props는 무엇일지 정의
- 어떤 데이터들을 컴포넌트들 끼리 주고받아야 할 지 생각해보기
- 각 컴포넌트 별로 state/props를 미리 생각해볼 것
5. 한 컴포넌트가 하나의 책임만 가지고 있는지 확인(Single Responsibility Principle)
- 더치 페이 예시 - 한 컴포넌트 내에서 그룹 생성하는 것 뿐만 아니라 그룹을 삭제하는 UI/로직 까지 담고 있다면 이 컴포넌트는 너무 많은 것을 핸들링 하려고 하는 것 ➡️ 쪼갭시다!
## TDD (Test-Driven Development)
소프트웨어를 동작시키기 위한 로직을 구현하기 전에 테스트 코드를 먼저 구현하는 것을 프로세스화 한 개발 방법
- 테스트 코드를 먼저 작성하면, 예외적인 상황을 미리 고민하고 정리하는 과정을 통해 버그가 생기는 것을 사전에 방지 할 수 있다. 즉, 버그를 만들 확률 🔻
- 구조적으로 더 나은 코드 생산 가능
- 소프트웨어의 품질을 높일 수 있음
### 테스트의 종류
단위 테스팅(Unit testing)
하나의 모듈/컴포넌트/클래스가 기대한대로 동작 하는지, 제공하는 기능들을 테스트
통합 테스팅(Integration testing)
두 개 이상의 모듈이 잘 연동/연결이 되었는지 테스팅. 모듈 간에 발생하는 에러 검증
E2E 테스팅(End-to-end testing)
실제 사용자가 이용하는 환경과 최대한 유사하게 만들어 사용자의 경험을 전반적으로 테스팅
사용자의 입장에서 시스템이 기능을 올바르게 제공하는지 시나리오 기반의 테스팅
인수 테스팅(Acceptance testing)
시스템이 주어진 요구사항을 잘 충족 하는지 수동으로 수행하는 테스팅.
## 의미 있는 테스트 작성하기
반드시 테스트 해야할 것
✅ 사용자 요구사항(user requirement)이 모두 테스트 케이스화 되어있는가?
✅ 정상적인 인풋이 들어 왔을 때 어떻게 응답하는가?
✅ 엣지케이스! 정상적이지 않은 인풋이 들어 왔을 때 어떻게 응답하는가?
✅ 사용자가 이용할 기능이 동작하는가?
- 기능 테스트에 초점을 맞춰야 합니다!
- 페이지에 중요한 버튼이 렌더링 되는가?
- 해당 버튼을 클릭하면 예상한대로 동작하는가?
'개인노트-인강' 카테고리의 다른 글
풀스택 서버리스 프로젝트 with React - 8. 기술셋 선정하기 (0) | 2023.12.22 |
---|---|
풀스택 서버리스 프로젝트 with React - 7. 프로젝트 설계 (0) | 2023.12.04 |
풀스택 서버리스 프로젝트 with React - 6. 프로젝트 기획 (0) | 2023.11.22 |
풀스택 서버리스 프로젝트 with React - 5. 프로젝트 기획 (0) | 2023.11.17 |
풀스택 서버리스 프로젝트 with React - 4. 프로젝트 기획 (0) | 2023.11.15 |