• 2021. 12. 24.

    by. 문익점

    반응형

    리팩터링 2판(마틴 파울러 저)을 읽고 작성한 리뷰입니다.

    4.1 자가 테스트의 가치

    4.1에선 테스트들의 좋은 점들을 나열한다. 테스트 코드의 목적은 미래에 발생할 수 있는 버그를 예방하기 위한 목적이다. 테스트코드는 작성한 코드가 의도한대로 제대로 값을 출력했는지 직접 확인하는 것이 아니라 컴퓨터가 확인하여 성공, 실패 여부를 출력하도록 한다. 이렇게 한다면 코드가 의도하는 대로 동작하는지도 확인 할 수 있는 것은 물론 리팩토링을 진행하는 과정에서도 계속해서 코드의 동작이 망가졌는지 아닌지도 확인이 가능하다.

    이 책에서는 테스트코드 작성시기를 프로그래밍을 시작 전에 가장 적합하다고 주장한다. 즉 테스트 코드를 미리 작성한 뒤 구현하는 TDD를 소개한다. 테스트를 작성하고 이 테스트를 통과 하도록 코드를 작성한다. 그 다음 그 코드들을 리팩토링한다. 리팩토링 과정에서 설계가 변경되도 테스트코드를 이용하여 의도대로 잘 동작하는 지에 대한 여부를 확인 할 수 있다.

    4.2 ~ 4.3 테스트 코드 작성

    이 책에서는 mocha와 chai를 이용하여 테스트 코드를 작성한다.

    생산 부족분 계산 테스트 코드

    desctibe로 블럭 단위로 테스트 생성한다. 공통된 테스트를 묶는 역할을 한다. 예를들어 province에 관련된 테스트면 이름을 "Province Tests"으로 짓고 안에서 디테일한 테스트 하나하나는 it 함수를 이용한다. 여기서는 생산 부족분 테스트 하나를 작성한다.

    describe("province tests", () => {
      it("shortfall", () => {
        const asia = new Province(sampleProvinceData());
        // 생산 부족분은 5로 기대 된다. 5면 테스트 성공 아니면 실패
        assert.equal(asia.shortfall, 5);
      });
    });

    chai 라이브러리에 존재하는 assert 함수를 이용하여 asia 픽스처의 생산 부족분은 5일 것이다.라는 테스트 코드를 작성하였고 이 테스트를 실행 한다면 실제로 부족분이 5면 success를 아니라면 fail를 출력한다.

    4.4 계속해서 테스트를 추가

    4.3에서 간단한 작성 방법을 알아봤다면 4.4에서도 계속해서 테스트 코드를 작성한다. 여기서 주의 할 사항 하나를 알려준다. 검증 하고 싶은 테스트가 2가지가 있는데 이익과 부족분 테스트를 진행한다고 가정 했을 때, const asia = new Province(sampleProvinceData())라는 공통된 코드가 존재한다. 그렇다면 이 코드를 최상단으로 빼면 될까? 결론만 말하자면 아니다.

    describe("province test", ()=> {
            const asia = new Province(sampleProvinceData())
        it("shortfall", () =>{
            // 생산 부족분은 5로 기대 된다. 5면 테스트 성공 아니면 실패
            assert.equal(asia.shortfall, 5)
        })
    
        it("profit", () => {
            assert.equal(asia.profit, 230)
        })
    })

    두 테스트 모두 asia라는 픽스처를 사용한다. 여기서 테스트를 실행 할 때 마다 픽스처를 생성하기 때문에 자원 낭비라 생각 해 상단으로 빼는 경우가 있다. 이는 첫 번째 테스트(shortfall)에서 픽스처가 변경 될 여지가 있기 때문에 추천되지 않는다. 그렇다면 어떻게 해야 될 까?

    let asia;
    beforeEach(()=>{
        asia = new Province(sampleProvinceData())
    })

    바로 beforeEach 함수를 사용하는 것이다. 이 구문은 테스트 실행 전 aisa 픽스처를 생성하기 때문에 각각의 테스트가 실행 될 때마다 테스트들은 각자의 새로운 픽스처를 가지게 된다.

    4.5 픽스처 수정

    여태까지는 픽스처만 가져와서 테스트하는 경우였지만 이젠 픽스처를 사용자가 직접 수정하였을 때의 경우 테스트한다. 즉 픽스처를 가져와서 직접 수정하는 경우이다.

    it("change production", () => {
        // 사용자가 직접 수정한다. (20으로 수정)
       asia.producers[0].production = 20;
       assert.equal(asia.profit, 292);
       assert.equal(asia.shortfall, -6);});

    이는 세터에서 복잡한 작업을 할 때 테스트한다. production의 세터는 복잡한 기능을 하고 있다.(Producer 클래스 참고)

    여기서는 aseert를 이용하여 검증을 두 번하고 있지만 책에서는 it함수, 즉 테스트 하나당 검증은 하나만 하는 것을 추천하고 있다.

    4.6 경계 조건 검사하기

    여태까지는 사용자가 개발자가 의도대로 사용하는 경우만 테스트를 진행 하였다. 이제는 이 의도(범위)를 벗어나는 경우인 경계 조건을 테스트 할 수 있어야 한다.

    책의 예시는 producers와 같은 컬랙션, 배열을 사용 할 때 이 값이 비어있는 경우이다.

    describe("no producers", () => {
      let noProducers;
      beforeEach(() => {
        const data = { name: "no producers", producers: [], demand: 30, price: 20 };
        noProducers = new Province(data);
      });
      it("shortfall", () => {
        expect(noProducers.shortfall).equal(30);
      });
      it("profit", () => {
        expect(noProducers.profit).equal(0);
      });
    });

    또한 음수가 들어가면 안되지만 음수 값이 설정된 경우도 있다. 수요(demand)와 같은 값은 음수가 되어선 안되지만 사용자에게 입력 받은 경우라면 빈 텍스트일수도 음수 일 수도 있다.

    it('negative demand', () => {
       asia.demand = -1
       expect(asia.shortfall).equal(-26)
       expect(asia.profit).equal(-10)
    })
    
    it('empty string demand', () => {
       asia.demand = ''
       expect(asia.shortfall).NaN
       expect(asia.profit).NaN
     })

    모두 반드시 의도한 대로 동작하는 지 테스트해야 된다.즉 오류가 생길 경계조건을 찾아서 집중적으로 테스트한다.

    검증단계(expect)에서 실제 값이 예상 범위를 벗어난 경우

    describe("string for producers", () => {
      it("", () => {
        const data = {
          name: "String producers",
          producers: "",
          demand: 30,
          price: 20,
        };
        const prov = new Province(data);
        expect(prov.shortfall).equal(0);
      });
    });

    보통 검증단계(expect)에서 의도한 prov.shortfall 값이 0이 아니라면 fail과 함께 0이 아니라는 메세지가 출력된다. 하지만 is not a function이라는 메세지가 출력된다.

    mocha는 검증단계(expect)에서 실제 값이 예상 범위를 벗어난 경우에는 fail로 처리하지만 다른 라이브러리는 더 자세하게 error로 처리하는 경우도 있다. 아무튼 이런 is not a function와 같은 메세지가 출력된 경우라면 expect 함수가 실행되기 전인 설정단계 (producers에 스트링으로 설정함)에서 오류가 발생한 경우이다.

    이러한 경우에는 어떻게 해야 될 까? 에러 사항을 처리 하도록 코드를 추가 할 수 있고 data 객체를 내부 코드베이스(신뢰 가능한 곳)에서 받는 경우라면 에러를 그대로 나둬도 된다. 여러곳의 중복된 유효성 검증 코드는 오히려 문제가 될 수 있다. 하지만 data 객체를 외부에서 가져온 경우라면 유효성 테스트 코드를 진행해야 된다.

    테스트 코드는 어느 수준까지 작성해야 될 까?

    테스트 코드가 아무리 많더라도 버그 없는 프로그램은 만들 수 없다는 말이 있다. 하지만 여태 4장까지 온 결과 테스트코드와 리팩터링이 생산성을 높혀주는 것은 사실이다. 테스트를 무분별하게 많이 작성하다보면 의욕이 떨어져서 나중에는 제대로 작성하지 않는다. 따라서 테스트 코드를 작성 할 때에는 경계 조건에 있는 경우, 복잡한 경우, 오류가 생길 만한 곳을 잘 판단하여 그 부분을 작성한다.

    느낀 점

    책에서는 리팩터링 전에 테스트 코드는 필수라고 한다. 테스트 코드를 어느 수준까지, 또한 어느 곳에서, 어떻게 작성해야 되는 지에 대한 감이 아예 없었다. 이 4장을 읽고나서 그 감을 잡는데 있어 아주 조금은 생긴 것 같다. 더 많은 실무를 적용해봐야 정확히 내 것이 될 것 같긴 하다.

     

    코드: https://github.com/choejoonkyung/refactoring/tree/ch4

    반응형

    '코딩' 카테고리의 다른 글

    리액트 살펴보기 - 정보  (0) 2021.12.29
    리팩터링 2판 리뷰 - 5장 6.5장  (0) 2021.12.28
    리팩터링 2판 리뷰 - 3장  (0) 2021.12.22
    리팩터링 2판 리뷰 - 2장  (0) 2021.12.22
    리팩터링 2판 리뷰 - 인트로 및 1장  (0) 2021.12.21