본문 바로가기
IT팁

대역 연습

by e-pd 2021. 10. 10.

최근 TDD 수업을 수강하면서 외부 라이브러리에 테스트에 대한 

질문과 답변을 보았다. 

 

최근 개발하던 코드가 생각나서 위의 지침에 따라 리팩터링을 해보기로 했다.

 

아래는 나의 리팩터링할 코드이다.

import com.xxx.xxx.utils.Randoms;

public CoinGenerator(int remainAmount) {
        this.coins = generateCoins(remainAmount);
    }

private int pickCoins(int remainAmount) {
        int pick = Randoms.pick(getCoinEntries());
        int subtractedAmount = remainAmount - pick;
        if (subtractedAmount >= 0) {
            this.coins.put(pick, this.coins.getOrDefault(pick, 0) + 1);
            return subtractedAmount;
        }
        return remainAmount;
    }

 

지금 현재 코드는 외부 라이브러리 Randoms을 의존하고 있고 숫자를 선택한다.

(참고로 Randoms.pick 기능은 다음과 같다.

List<Integer>내의 랜덤한 숫자를 한개를 반환하는 기능을 수행한다. 임의의 int 를 반환한다. 숫자 한개를 랜덤하게 반환한다.)

 

그리고 내가 구현하는 코드는 랜덤에 뽑힌 수만큼 Coin Map에 담는 것이다. 

문제는 현재 구조에서는 외부 라이브러리와 강하게 결합되어 있고, 차후 다른 라이브러리 교체등에 유연하게 대처하기 힘들다는 점이다.

 

리팩터링 시작

 

1. 라이브러리를 감싼 C1을 만든다.

 

import static com.xxx.xxx.utils.Randoms.*;

public class Randoms {
    
    public int pickNumber(List<Integer> candidates) {
        return pick(candidates);
    } 
}

 

2. 인터페이스를 추출한다.

public interface RandomNumber {
    int pickNumber(List<Integer> candidates);
}

public class Randoms implements RandomNumber {
    @Override
    public int pickNumber(List<Integer> candidates) {
        return pick(candidates);
    }
}

public class CoinGenerator {
    private Map<Integer, Integer> coins;
    private RandomNumber randomNumber;
    
    public CoinGenerator(int remainAmount, RandomNumber randomNumber) {
        this.randomNumber = randomNumber;
        this.coins = generateCoins(remainAmount);
    }
    
     private int pickCoins(int remainAmount) {
        int pick = randomNumber.pickNumber(getCoinEntries());
        int subtractedAmount = remainAmount - pick;
        if (subtractedAmount >= 0) {
            this.coins.put(pick, this.coins.getOrDefault(pick, 0) + 1);
            return subtractedAmount;
        }
        return remainAmount;
	}
}

 

3. 특정 테스트에서 사용할 C2 구현

테스트 폴더에서 인터페이스 구현체를 만든다.

원래는 랜덤한 숫자를 리턴했지만 이제는 대역을 통해서 테스트 값을 통제할 수 있게됐다.

public class RandomsImpl implements RandomNumber{

    @Override
    public int pickNumber(List<Integer> candidates) {
        return 1;
    }
}


class CoinGeneratorTest {

    @Test
    void generate() {
        CoinGenerator coinGenerator = new CoinGenerator(450, new RandomsImpl());

        Map<Integer, Integer> coins = coinGenerator.getCoins();
        assertThat(coins).isNotEmpty();
    }
}

 

추가적으로 스프링을 사용하면 스프링 프로파일 활용해 런타임 의존성을 교체할 수 있을 것 같다.

이번 리팩터링으로 인터페이스를 통한 추상화를 한 것이 마음에 든다.

 

 

추가적으로 강사님이 소개한 마틴파울러의 리팩터링에 소개된

Introduce Local Extension 로 설명해주었는데

아래 사이트에서 관련 개념 확인하면 좋을 것같다.

https://refactoring.guru/introduce-local-extension

 

Introduce Local Extension

Tired of reading? No wonder, it takes 7 hours to read all of the text we have here. Try our interactive course on refactoring. It offers a less tedious approach to learning new stuff. Let's see…

refactoring.guru

 

 

'IT팁' 카테고리의 다른 글

Feature toggle  (0) 2023.10.01
오라클 클라우드 무료사용하기  (0) 2021.08.12
FQCN (Fully Qualified Class Name)  (0) 2021.01.03
Server Side Rendering  (0) 2020.11.04
Bouncer Pattern  (0) 2020.08.19