본문 바로가기
Test

TDD init

by oncerun 2022. 12. 10.
반응형

개발 방법론을 배우는 데 있어서 실습만큼 좋은 게 없는 것 같다. 

 

나는 java, Kotlin을 사용할 것이고 우선적으로 java를 기준으로 작성한 다음 Kotlin으로 변경할 것이다. 

이는 현재 Kotlin을 배우는 입장으로써 좋은 기초 코드가 될 것 같기 때문이다. 

 

JUnit5를 기준으로 사용한다. 이는 현재 자주 사용하는 스프링 부트의 기본 테스트 설정과 같고 기존 JUnit4를 기준으로 공부하고 싶지 않기 때문이다. 

 

이러한 프레임워크나 사용방법에 대해선 따로 정리를 할 것이며 일단은 예제 + ChatGPT로 사용방법만 빠르게 이해하면서 진행하자. 

 

이번 주말을 녹일 수 있는 좋은 공부 거리이다.

 

 

TDD

 

TDD는 개발의 시작을 테스트부터 시작한다고 한다. 구현 -> 테스트로 이어지는 흐름이 아니라 테스트 -> 기능으로 이어진다. 

 

즉 테스트를 먼저 작성함으로써 기능이 올바르게 동작하는지 검증하고 이후 테스트를 통과시키기 위해 개발을 진행하는 것이다. 

 

결국 이는 테스트 코드를 작성하는데 더 많은 힘을 쏟게 될 것이라는 것을 예상할 수 있다.

 

더 재밌는 건 컴파일 오류 나는 코드를 작성한다는 것이다. 

 

실제 Claculator 클래스가 존재하지 않아서 컴파일 오류가 발생하고 있다.

 

 

이 경우 고민할 수 있다. 

 

plus()라는 메서드 네이밍, 클래스의 네이밍, 파라미터에 대한 고민, 정적 메서드? 인스턴스 메서드? 등등.. 다양한 고민을 해야 한다. 

 

해당 테스트 코드는 다음과 같은 결정을 했다.

 

1. 정적 메서드

2. 메서드 네이밍은 plus로 정했다.

3. 파라미터는 두 개, 그리고 int 타입으로 정했다. 

4. 클래스 네임은 Calculator라고 정했다.

 

이후 assertEquals 메서드로 실행 결과가 올바른지 검증하는 코드를 추가했다.

 

이제 컴파일 에러는 없애기 위해 Calculator 클래스를 생성하고 0을 리턴하는 plus() 메서드를 추가한다.

 

public class Calculator {

    public static int plus(int num1, int num2) {
        return 0;
    }
}

 

그리고 테스트를 실행한다.

 

 

Expected 한 값은 3이지만 실제론 (Actual) 0을 반환했다는 것이다. 

이후 테스트를 성공시키기 위해 3을 리턴하는 메서드를 작성한다.

 

public static int plus(int num1, int num2) {
    return 3;
}

 

처음부터 num1 + num2를 하지 않고 3을 리턴하는 이유에 대해 설명한다면 TDD를 처음 접한다면 작은 단계를 차근차근 밟아 나가야 한다고 한다. 그렇지 않으면 TDD를 몸에 익히는데 어려움을 겪을 수 있다.

 

assertEquals(5, Calculator.plus(4,1));

해당 코드를 추가하고 해당 테스트는 실패하기 때문에 다음과 같은 상황을 풀어야 한다.

 

1+2, 4+1에 대해 처리할 수 있도록 코드를 수정해야 한다. 따라서 다음과 같이 메서드를 수정한다.

public static int plus(int num1, int num2) {
    return num1 + num2;
}

 

단순한 예제였지만 이러한 흐름대로 TDD는 개발한다고 한다. 

 

이러한 과정은 실제 머릿속으로 코드를 설계하는 것보다 직관적으로 알 수 있어서 더 좋은 부분이 있는 것 같고 설계하는 방식도 비슷한 것 같다. 

 

실제 다음과 같은 고민을 했다. 

 

테스트를 만들 때 시작은 어떤 테스트로 해야 할지 고민될 것이다.  이 경우 가장 쉽거나 가장 예외적인 상황을 선택하라고 조언한다. 

 

이는 테스트 코드를 통과하는 시간이 길어질수록 복잡성을 더 증가시키기 때문이라고 보인다. 테스트를 하는 횟수가 빠를수록 부분에 대한 테스트를  짧고 빠르게 하기 때문에 더 빨리 피드백을 받을 수 있는 의도로 볼 수 있다.

 

 

테스트 코드 정리

 

테스트 코드도 코드이기에 유지보수 대상으로 중복을 알맞게 제거하거나 의미가 잘 드러나게 코드를 수정할 필요가 있다. 

 

객체를 생성하는 부분에 대해 메서드로 추출하거나 필드로 제외하고 중복되는 검증 부분은 별도의 메서드로 추출하여 중복을 제거할 수 있다.

 

public class PasswordStrengthMeterTest {
    PasswordStrengthMeter meter = new PasswordStrengthMeter();

    @Test
    public void meetsAllCriteria_Then_Strong() throws Exception {
        assertStrength("asb!@32ABD", STRONG);
    }

    @Test
    public void meetsAllCriteria_except_for_Length_Then_Normal() throws Exception {
        assertStrength("ab12!@A", NORMAL);

    }

    @Test
    public void meetsAllCriteria_except_for_number_Then_Normal() throws Exception {
        assertStrength("abcd!@efg", NORMAL);

    }

    private void assertStrength(String password, PasswordStrength expStr) {
        PasswordStrength result = meter.meter(password);
        assertEquals(expStr, result);
    }


}

 

테스트 주도 설계는 정말 많은 테스트를 중간중간 실행한다.

 

코드의 리팩터링의 크기도 짧게 잡을뿐더러 해당 경우마다 테스트 코드를 실행한다. 

이러한 습관을 먼저 들이는 것이 매우 중요하다고 한다.

 

 

실제로 구현하는 과정을 살펴보면 다음과 같이 구현한다.

 

https://velog.io/@cjh8746/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A3%BC%EB%8F%84-%EA%B0%9C%EB%B0%9CTDD

 

이 과정에서 중요한 건 실제 테스트를 통과시킬 만큼 코드를 작성하고 리팩터링으로 마무리하는 과정을 반복한다는 것이다.

 

 

다시 한번 상기해보자.

 

1. 기능을 검증하는 테스트를 먼저 작성한다.

2. 테스트를 통과할 정도의 코드를 작성한다.

3. 통과 이후에는 개선할 코드가 있으면 리팩토링 한다.

4. 리팩토링 이후 다시 테스트를 실행해 기존 기능을 검증한다.

 

이 과정을 반복하면서 점진적으로 기능을 완성해나간다. 이러한 사이클이 바로 TDD의 흐름이라고 할 수 있다.

이러한 흐름은 코드를 리팩터링 하는 과정을 몰아서 진행하지 않음으로써 지속적으로 코드를 개선할 수 있다. 

이를 통해 코드 품질이 급격히 나빠지지 않게 막아주는 효과가 있다.

 

추가적으로 아까 내가 느꼈던 빠른 피드백에 대한 부분도 있다. 

새로운 코드를 추가하거나 기존 코드를 수정하는 것에 대해 테스트를 진행하여 검증을 즉시 할 수 있다는 점이다. 

이는 버그를 포함한 코드가 바로 배포되는 것을 방지할 수 있다.

반응형

'Test' 카테고리의 다른 글

Junit 5  (0) 2022.12.12
테스트 코드에 작성 순서가 있다고?  (0) 2022.12.10
TDD 시작  (0) 2022.12.10
JUnit  (0) 2022.11.08
데이터베이스 연동 테스트  (0) 2022.10.14

댓글