본문 바로가기
Study/Refactoring

리팩터링 4

by irerin07 2023. 8. 31.
728x90

4장

테스트 구축하기

리팩터링만으로는 부족하다. 뒷받침 해줄 테스트가 없다면 불가피하게 저지르는 실수를 잡아줄 수 없다.

4.1 자가 테스트 코드의 가치

테스트가 잘 확립되어 있으면 생산성이 급상승 한다.

테스트를 작성하기 가장 좋은 시점은 프로그래밍 시작 전이다.

  • 테스트를 작성하면서 원하는 기능을 추가하기 위해 무엇이 필요한지 고민하게 되어 구현보다는 인터페이스에 집중하게 된다.

테스트를 모두 통과한 시점이 개발 완료 시점임을 확실히 할 수 있다.

4.2 테스트할 샘플 코드

4.3 첫 번째 테스트

import org.junit.Test;
import static org.junit.Assert.*;

public class ProvinceTest {

    @Test
    public void testShortfall() {
        Province asia = new Province(sampleProvinceData()); //픽스처 설정
        assertEquals(5, asia.getShortfall()); //검증
    }

    @Test
    public void testAnotherCase() {
        Province europe = new Province(sampleProvinceData());
        assertEquals(10, europe.getAnotherValue());
    }
}

수많은 테스트를 실행했지만 실패하는 것이 없다면 테스트가 자신의 의도와는 다른 방식으로 코드를 다루는것은 아닌지 불안해진다.

이런 경우 일부러 코드에 오류를 주입하여 확인한다.

GUI테스트 러너의 핵심은 모든 테스트가 통과했냐는 사실을 빨리 알 수 있다는 데 있다.

4.4 테스트 추가하기

테스트는 위험 요인을 중심으로 작성해야 한다.

테스트의 목적은 어디 까지나 현재 혹은 향후 발생하는 버그를 찾는데 있다.

적은 수의 테스트로 큰 효과를 얻는 것이 제일 좋다.

import org.junit.Test;
import static org.junit.Assert.*;

public class ProvinceTest {

    @Test
    public void testShortfall() {
        Province asia = new Province(sampleProvinceData());
        assertEquals(5, asia.getShortfall());
    }

    @Test
    public void testProfit() {
        Province asia = new Province(sampleProvinceData());
        assertEquals(230, asia.getProfit());
    }
}

임시 값을 설정했다가 실제 값으로 대체하고, 오류를 심었다 되돌리는 패턴은 기존 코드를 검사하는 테스트를 추가할 때 흔히 쓰는 방식이다.

두 가지 테스트에서 중복되는 부분이 있다. 첫 줄에서 똑같은 픽스처를 설정하고 있다. 우선 바깥 범위로 끌어내 보자

describe('province', function() {
    const asia = new Province(sampleProviceData()); //이렇게 하면 안된다.

    it('shortfall', function() {
        assert.equal(asia.shortfall, 5);
    });

    it('profit', function() {
        expect(asia.profit).equal(230);
    });

});

이렇게 하면 일시적인 효과는 있겠지만 테스트 버그 중 가장 지저분한 유형인 ‘테스트끼리 상호작용 하게 하는 공유 픽스처’를 생성하게 된다.

import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ProvinceTest {

    private Province asia;

    @Before
    public void setUp() {
        asia = new Province(sampleProvinceData());
    }

    @Test
    public void testShortfall() {
        assertEquals(5, asia.getShortfall());
    }

    @Test
    public void testProfit() {
        assertEquals(230, asia.getProfit());
    }
}

이렇게 매번 픽스처를 생성하면 테스트 성능에 영향을 미칠까 걱정을 하지만 그런 경우는 거의 없다. 정말 문제가 있을때는 공유 픽스처를 사용하기도 하지만 그럴 경우 어떠한 테스트로 픽스처 값을 변경하지 못하도록 한다.

4.5 픽스처 추가하기

실전에서는 사용자가 값을 변경하면서 픽스처의 내용도 수정되는 경우가 흔하다.

보통의 간단한 세터는 버그가 생길 일도 별로 없기 때문에 테스트를 잘 안 하지만 조금 복잡한 세터의 경우 테스트해볼 필요가 있다.

@Test
    public void testChangeProduction() {
        asia.getProducers().get(0).setProduction(20);
        assertEquals(-6, asia.getShortfall());
        assertEquals(292, asia.getProfit());
    }

before에서 설정한 표준 픽스처를 취한 뒤, 테스트를 수행하고 해당 픽스처가 일을 기대한 대로 처리했는지 검증한다.

이 테스트는 it구문 하나에서 두 가지 속성을 검증한다.

일반적으로는 it구문 하나에서 한 가지 속성만 검증 하는 것이 좋다.

한 가지 이상의 속성을 검증할 경우 첫 속성 검증 실패 시 두 번째 검증이 실행이 되지 않으므로 실패 원인 파악에 유용한 정보를 놓칠 수 있다.

4.6 경계 조건 검사하기

모든 일이 순조롭고 사용자도 의도대로 사용하는 상황만 상정해서 테스트하면 안된다.

예를 들어 비어있는 컬렉션이 넘어오는 경우나, 숫자 형이면 0 혹은 음수인 경우를 검사해본다.

경계를 확인하는 테스트를 작성하면 특이 상황을 어떻게 처리하는게 좋을지 생각해볼 수 있다.

스스로 작성한 코드를 적으로 돌려서 의도적으로 망가뜨리는 방법을 모색한다.

실패는 검증 단계에서 실제 값이 예상 범위를 넘어갔다는 뜻이고 오류는 검증보다 앞선 과정에서 발생한 예외 상황을 말한다.

프로그램은 이런 예외 상황에서 에러 상황을 잘 처리하도록 해야한다.

에러처리 코드를 넣을 수도 있고, 의미 있는 오류 메시지를 출력할 수도 있다.

테스트를 작성할 때는 위험한 부분에 집중하도록 하자.

코드에서 처리 과정이 복잡한 부분이나 함수에서 오류가 생길만한 부분들을 찾도록 하자.

4.7 끝나지 않은 여정

테스트는 리팩터링 뿐만 아니라 그 자체로도 프로그래밍에 중요한 역할을 한다.

기능을 추가할 때마다 테스트를 추가함은 물론 기존 테스트도 다시 한 번 돌아보도록 하자.

자가 테스트의 목적은 누군가 결함을 심을 경우 테스트로 발견할 수 있다는 믿음을 갖게 하는 것이다.

테스트가 너무 많은 경우도 있다. 제품 코드보다 테스트 코드를 수정하는 데 시간이 더 걸린다면, 테스트 때문에 개발 속도가 느려진다 판단되면 테스트가 너무 과한 것은 아닌지 확인하자.

728x90

'Study > Refactoring' 카테고리의 다른 글

리팩터링 3  (0) 2023.08.31
리팩터링 2  (0) 2023.08.31
리팩터링 1  (0) 2023.08.31
리팩터링 12  (0) 2023.08.30
리팩터링 11  (0) 2023.08.29