why not

TDD(Test-Driven Development) 활용 (29일차) 본문

Today I Learn/환급 챌린지

TDD(Test-Driven Development) 활용 (29일차)

novem 2023. 3. 20. 06:00

<TDD>

1. TDD?

  • 'Test-Driven Development'의 약자
  • 한국어로는 '테스트 주도 개발'
  • '디자인 → 로직 구현 → 테스트 코드 작성' (일반적인 프로세스)
  • '디자인 → 테스트 코드 → 로직 구현' (TDD 프로세스)
  • 소프트웨어를 동작시키기 위한 로직을 구현하기 전에 테스트 코드를 먼저 구현하는 것을 프로세스화 한 개발 방법

테스트 주도 개발 주기

2. 테스트 코드가 중요한 이유?

  • 작성한 코드가 의도적으로 동작하는지 수시로 빠르게 검증할 수 있다.
  • 매번 서버를 돌려서 수동적으로 input/output을 검증하는 비효율적인 방법에서 벗어날 수 있음
  • 리팩토링을 할 때 → 리팩토링 후에도 소프트웨어가 여전히 같은 기능을 제공할 수 있도록 안정망 역할을 함
  • 잘 작성된 테스트 코드는 소프트웨어의 명세서가 되기도 함.
    • 기능이 정상 동작할 경우, 예외를 일으킬 경우 등 코드를 직접 읽기보다 가독성이 높은 human-language로 테스트 케이스를 읽는 것이 더 빠르게 이해할 수 있다.
      • e.g. 코드 리뷰 시에 테스트 코드를 먼저 읽으면서 해당 기능/객체에 대해 빠진 테스트가 있는지, 이 테스트들이 논리적으로 말이 되는지 체크
  • 매 배포 전, 전체 테스트 코드를 돌려 품질이 보증된 소프트웨어를 일정하게 제공할 수 있다.

3. TDD가 중요한 이유?

  • 테스트 코드를 먼저 작성하지 않으면, 의식의 흐름대로, 기능 단위로, 혹은 이곳 저곳.. 방대한 양의 코드를 작성해나가기 시작한다. 문제가 발생시 확인해야 할 코드의 범위가 넓어진다.
  • 테스트 코드를 먼저 작성하면, 예외적인 상황을 미리 고민하고 정리하는 과정을 통해 버그가 생기는 것을 사전에 방지 할 수 있다.
    • 버그를 만들 확률이 적어진다.
  • 객체 지향적 설계도 가능
  • 각 객체를 테스트하는 관점에서 특정 객체가 어떻게 동작해야 할지, 객체 간 어떻게 메시지를 주고받아야 할지 먼저 논리적으로 생각해볼 수 있다.
  • 테스트 코드를 작성하는 과정에서 기능 구현을 위한 설계 요소를 고민하게 되며 구조적으로 더 나은 코드를 생산할 수 있다.
  • 테스트 코드가 추가될 때마다 검증되는 범위가 넓어지므로, 소프트웨어의 품질을 높일 수 있다.
test('그룹명을 입력하지 않고, 저장할 경우 에러 메시지 노출', () => {
  render(<CreatingGroup/>)

  const saveButton = screen.getByText ('저장')
  fireEvent.click(saveButton)

  await waitFor(() => {
    expect(getByText('그룹명을 입력해 주세요')).toBeInTheDocument()
  })
})
  • 읽어보기TDD는 페어 프로그래밍을 할 때에도 유용함.
  • TDD를 실현하면, 엣지케이스를 빼먹기가 어렵다.

4. TDD는 어떻게 사용하나요?

4-1. 실패하는 테스트 작성

test('그룹명을 입력하지 않고, 저장할 경우 에러 메시지 노출', () => {
  expect(false).toBe(true)
})
  • 테스트 시나리오를 메소드화 한다는 것에 촛점을 맞춘다.
  • 테스트 메소드명은 ‘어떤 것을 테스트하고자 하는지' 목적을 담자.
  • 핵심 로직을 검증하는 단계가 아니므로, 우선 테스트는 실패하도록 한다.

4-2. 어떤 테스트를 작성해야 하나요?

  • 작성한 소프트웨어 요구사항을 충족하기 위한 기능을 테스트 케이스로 작성 한다.
  • 위 과정에서 필요한 클래스, 메소드의 입력/출력 결과값이 정해져야 한다.
  • 예외적인 케이스도 정리할 것.

4-3. 테스트 통과 시키기

test('그룹명을 입력하지 않고, 저장할 경우 에러 메시지 노출', () => {
  render(<CreatingGroup/>)

  const saveButton = screen.getByText ('저장')
  fireEvent.click(saveButton)

  await waitFor(() => {
    expect(getByText('그룹명을 입력해 주세요')).toBeInTheDocument()
  })
})
  • 테스트를 통과 시킬 만큼의 코드만 구현하는 과정.
  • 목(Mock) 객체 활용
    • 목(Mock) 객체 = 가짜 객체
    • 소프트웨어란 결국, 객체 간의 통신을 통해 사용자에게 기능을 제공하는 것인데,
    • 한 객체를 테스트 하기 위해서 엮인 모든 객체(혹은 외부와의 통신)를 다 initialize 하고 기능을 테스트 하기란 비효율 적임
    • 객체 간의 의존성을 다 충족 시키는 것에 집중하다 보면 실제 객체의 기능 테스트 본질도 흐려짐
    • 따라서 대상 객체에서 직접 참조하는 객체들은 mock 객체로 대체 → 발생하는 이벤트에 대해 대상 객체가 mock 객체와 어떻게 소통할 지 직접 지정할 수 있다 → 대상 객체를 테스팅 하는데에만 집중할 수 있다.

4-3. 리팩토링

  • 프로덕션 코드의 구조를 개선하는 과정
  • 2번 까지는 테스트를 통과시키기 위해 최소한의 코드를 작성했을 것이다. 이제는 객체 지향적으로, 가독성을 높일 수 있도록 하기 위해 구조적 개선을 하는 데에 집중
  • 리팩토링 틈틈이 테스트 코드를 돌려 계속 성공 시킴으로써, 리팩토링 후에도 동일한 기능을 제공 보장

 

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

* 필수 삽입 링크 : http://bit.ly/3Y34pE0

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr