TDD, 리팩터링에 대해

이건회·2023년 2월 14일
0

우테코

목록 보기
4/19

개요

우테코 사다리 타기 미션을 수행하기에 앞서 TDD의 개념에 대해 수강한 내용을 정리하기로 했다

TDD

프로덕션 코드보다 테스트코드를 먼저 구현하는 개념이다. 따라서 test first development라고도 한다. 프로그래밍 의사결정과 피드백 사이의 간극을 의식하고 이를 제어하는 기술이다. TDD는 테스트 기술 아니라 분석 기술, 혹은 설계 기술이다...고 하는데 사실 이렇게 말하면 뭔 소린지 잘 모르겠다...직접 진행을 하면서 이 개념을 확인해보자

TDD 작업방식 및 TDD를 하는 이유

  • 엔티티 클래스를 만들자마자 테스트 코드를 작성한다. 머릿속에 떠오르는 테스트를 쓴다.

ex)

class Lotto {
	//프로덕션 코드 생성
}

class LottoTest {
	
    @Test
    void min1Max45(){
    
    	new Lotto(0) //실패
        new Lotto(1) //성공
        new Lotto(45) // 성공
        new Lotto(46) //실패
        
    }
    
}
  • 실패하는 테스트를 만들고, 이를 성공하도록 만든다. 핵심은 실패하는 테스트를 "최대한 빠르게 통과시키는 것" 이다.

ex)

class Lotto {

	public Lotto(final int i) {
        throw new IllegalArgumentException();
    }
}

class LottoTest {
    @Test
    void min1Max45(){
        //실패 -> 성공
        assertThrows(IllegalArgumentException.class, () -> new Lotto(0));
        new Lotto(1) //성공
        new Lotto(45) // 성공   
        new Lotto(46) //실패
    }
}
  • 테스트를 분리하고, 새로운 조건에서 테스트를 한다.

ex)

class Lotto {

	public Lotto(final int i) {
    	if (i == 0) {
        	throw new IllegalArgumentException();
        }
    }
}

class LottoTest {
    @Test
    void 0은_로또번호_아님(){
        assertThrows(IllegalArgumentException.class, () -> new Lotto(0)); 
    }
    
    @Test
    void 1은_로또번호임(){
        assertDoesNotThrows(IllegalArgumentException.class, () -> new Lotto(1)); 
    }
}
  • 새로운 테스트를 계속 만들고, 조건을 계속 추가한다

ex)

class Lotto {

	public Lotto(final int i) {
    	if (i == 0 || i == 46) {
        	throw new IllegalArgumentException();
        }
    }
}

class LottoTest {
    @Test
    void 0은_로또번호_아님(){
        assertThrows(IllegalArgumentException.class, () -> new Lotto(0)); 
    }
    
    @Test
    void 1은_로또번호임(){
        assertDoesNotThrows(IllegalArgumentException.class, () -> new Lotto(1)); 
    }
    
    @Test
    void 45는_로또번호임(){
        assertDoesNotThrows(IllegalArgumentException.class, () -> new Lotto(45)); 
    }
    
    @Test
    void 46은_로또번호_아님(){
        assertThrows(IllegalArgumentException.class, () -> new Lotto(46)); 
    }
}

-> 가장 빠르게 문제를 해결하라. 실패를 성공으로 만들어라

그런데 애초에 그냥 1~45 범위를 잡고 예외처리 하면 될 것을 왜 이렇게 귀찮게 할까? TDD는 왜 그렇게 하라는 걸까?

위에 올라온 TDD의 정의를 다시 보자

TDD는 프로그래밍 의사결정과 피드백 사이의 간극을 의식하고 이를 제어하는 기술이다

-> 문제와 실패를 해결을 하는 과정에서 도메인의 특징을 알게 된다. 도메인에 익숙해지고 개념이 명확해지고, 설계가 확실히 될 때 까지 이런 방법으로 진행한다. 이 타이밍이 됐다고 생각하면 조금 빠르게 프로덕션을 구현한다.
-> 그렇기 때문에 아래와 같은 TDD의 정의가 나온 것이다

TDD는 테스트 기술 아니라 분석 기술, 혹은 설계 기술이다

-> 프로덕션 코드를 테스트하는 것이 아니다. 테스트를 실패하고 성공시키도록 하면서 도메인을 분석하고, 설계하며, 제어한다.

결국 TDD를 하는 이유는 다음 결론에 도달한다

  • 디버깅 시간을 줄여준다
  • 동작하는 문서 역할을 한다
  • 변화에 대한 두려움을 줄여준다

전체 설계를 한번에 할 수 없다. TDD를 하면서 조금씩 설계를 그려 나가자. 설계가 없이 진행하는 것이 아니라. 머릿속으로 어느 정도만 설계를 하고 시작하자

TDD를 쓰는 기준 정하기

TDD를 모든 개발 과정에 적용하기란 어렵다. 따라서 본인이 TDD를 쓰는 기준을 정하는 것이 좋다. 중요성을 잘 고려해 기준을 세우자

ex)

  • 우리 비즈니스의 핵심 로직인가
  • 문제 발생 시 위험이 큰가
  • 인풋과 아웃풋이 명확한가
  • 설계가 막막해서 시작이 어렵다
  • 테스트가 무조건 있어야 하는가

"제한된 시간과 자원 안에서 얼마만큼 할 것인가를 끊임없이 선택해야 한다"라고 한 코치님이 말씀해 주셨다

TDD 사이클

테스트 실패 -> 테스트 성공하도록 프로덕션 구현 -> 프로덕션/테스트 리팩토링 -> 테스트 실패(반복)

TDD 원칙

  • 실패하는 테스트를 만들기 전 까지 프로덕션 코드를 만들지 말자
  • 컴파일은 실패하지 않으면서 실행은 실패하지 않도록 실패 테스트를 만들자
  • 현재 실패하는 테스트를 통과할 정도로만 프로덕션 코드를 만든다

TDD 진짜 진짜 왜 해야하나????

  • 우리는 사람이 코드를 짠다. 본인의 코드가 100퍼센트 맞다는 보장이 없다. 특히 제대로 안 만든 결과가 엄청난 문제를 가져올 경우(ex. 돈...) 항상 확인 또 확인하자...이걸 확인 안하면 개발자들은 불안감에 빠질 수 밖에 없다.
  • 따라서 테스트 실패와 통과를 통해 심리적 불안감을 해결하자.
  • 처음부터 완벽한 설계가 아닌, 점진적으로 설계를 개선해 나갈 수 있다.
  • 과도한 설계에 따른 추가 비용을 해소하자 -> 저번 미션에서 이랬다...
  • 테스트가 없으면 서버를 시작해야 동작을 확인할 수 있다. 자동화된 테스트가 있으면 더 빠른 피드백이 가능하다. 버그를 찾는 시점이 빨라진다. 배포에 대한 부담을 줄이고 비즈니스 로직 구현 가능
  • 서비스 안정성이 높아진다.
  • 학습 효율이 높아저 개발자의 역량이 높아진다.

조금 지루한 대신, 문제가 생길 수 있는 불안함을 깨끗하게 해소하자!

미션이 끝나고 생각해볼 점

  • 내가 TDD, 리팩토링을 왜 하는가?
  • 기존 구현과 어떠한 차이가 있는가?
  • 어떠한 어려움을 겪었는가?
  • 어려움을 줄이려고 어떤 시도를 해볼 수 있는가?
profile
하마드

0개의 댓글