해당 내용은 위의 책의 내용을 제가 이해한대로 정리해둔 내용입니다.
이번 포스팅에선 Chapter04 ~ Chapter05 의 내용에 대해 다룹니다.
- Chapter04 테스트 구축하기
- Chapter05 리팩터링 카탈로그 보는법
Chapter04 테스트 구축하기
리팩토링의 전제 조건으로 자가 테스트 코드(self-testing code)를 강조합니다. 리팩토링은 프로그램의 겉보기 동작을 보존하면서 내부 구조를 개선하는 작업이기 때문에, 동작이 보존되었는지 확인할 수 있는 견고한 테스트가 필수적입니다.
리팩터링을 제대로 하려면 불가피하게 저지를 실수를 잡아주는 견고한 테스트 스위트(test suite)가 뒷받침 돼야한다.
좋은 테스트를 작성하는 일은 개발 효율을 높여 준다.
* 개인적으로 본인도 아직은 테스트를 작성하고 효율이 좋아지는 경험을 해보지 못했습니다.
책에서는 모든 테스트를 완전히 자동화하고, 그 결과까지 자동으로 검사하는 테스트 스위트를 구축할 것을 권장합니다.
- 회귀 버그(regression bug) 방지: 이전에 잘 작동하던 기능이 새로운 변경사항으로 인해 작동하지 않게 되는 상황을 빠르게 감지합니다.
- 프로그래밍 리듬 유지: 코드 작성 → 테스트 실행 → 결과 확인 → 반복이라는 빠른 피드백 루프를 형성합니다.
- 작업 진척 확인: 새로운 기능을 개발할 때 테스트 통과 여부로 진행 상황을 명확히 확인할 수 있습니다.
테스트 주도 개발(TDD)
- 실패하는 테스트 작성: 구현하려는 기능에 대한 테스트를 먼저 작성합니다.
- 테스트가 통과하도록 코드 작성: 가장 단순한 방법으로 테스트를 통과시킵니다.
- 리팩토링: 코드를 개선하되, 테스트가 계속 통과하는지 확인합니다.
이 방법론은 설계 품질 향상, 문서화, 회귀 방지 등의 이점을 제공합니다.
실패해야 할 상황에서는 반드시 실패하게 만들자.
테스트 작성의 실질적 지침
- 준비(Arrange): 테스트에 필요한 객체를 생성하고 설정합니다.
- 실행(Act): 테스트 대상 기능을 실행합니다.
- 검증(Assert): 예상한 결과가 나왔는지 확인합니다.
function sampleProvinceData() {
return {
name: "Asia",
producers: [
{name: "Byzantium", cost: 10, production: 9},
{name: "Attalia", cost: 12, production: 10},
{name: "Sinope", cost: 10, production: 6},
],
demand: 30,
price: 20
};
}
// 책의 예제와 유사한 테스트 구조 (Mocha 사용)
it('province shortfall', function() {
// 준비(Arrange)
const asia = new Province(sampleProvinceData());
// 실행(Act) 및 검증(Assert)
expect(asia.shortfall).equal(5);
});
Mocha 대신 Jest 사용하기
책에서는 Mocha와 Chai를 테스트 프레임워크로 사용하고 있지만, 저는 현업에서 더 널리 사용되는 Jest를 활용하여 동일한 개념을 적용할 것입니다. 테스트 러너와 검증(assertion) 라이브러리가 통합되어 있어 사용이 간편합니다.
동일한 테스트를 Jest로 작성하면 다음과 같습니다
// 위 예제를 Jest로 변환
test('province shortfall', () => {
// 준비(Arrange)
const asia = new Province(sampleProvinceData());
// 실행(Act) 및 검증(Assert)
expect(asia.shortfall).toBe(5);
});
픽스처(Fixture) 활용
- 테스트 간 일관성 유지: 동일한 기본 조건에서 테스트를 실행하여 결과의 신뢰성을 높입니다.
- 설정 코드 중복 제거: 여러 테스트에서 동일한 설정 코드를 반복하지 않도록 합니다.
- 테스트 가독성 향상: 복잡한 설정 세부 사항이 아닌 테스트 동작 자체에 집중할 수 있게 합니다.
describe('province', function() {
let asia;
beforeEach(function() {
asia = new Province(sampleProvinceData());
});
it('shortfall', function() {
expect(asia.shortfall).equal(5);
});
it('profit', function() {
expect(asia.profit).equal(230);
});
});
픽스처와 TDD의 관계
테스트 주도 개발(TDD)에서 픽스처는 특히 중요합니다. 테스트를 먼저 작성하는 TDD 방식에서 잘 설계된 픽스처는:
- 테스트 작성을 더 빠르게 해줍니다.
- 테스트 의도를 더 명확하게 표현해줍니다.
- 코드의 사용 패턴을 미리 정립하여 더 나은 API 설계로 이어집니다.
책에서 강조하듯, 좋은 테스트 픽스처는 테스트를 더 명확하고 유지보수하기 쉽게 만들며, 이는 결국 더 나은 리팩토링으로 이어집니다.
경계 조건 테스트
빈 컬렉션: 컬렉션이 비어 있을 때의 동작 검증
it('no producers', function() {
const noProducers = [];
const data = {
name: "No producers",
producers: noProducers,
demand: 30,
price: 20
};
const province = new Province(data);
expect(province.shortfall).equal(30);
});
잘못된 입력: 유효하지 않은 입력에 대한 처리 검증
it('string for producers', function() {
const data = {
name: "String producers",
producers: "",
demand: 30,
price: 20
};
const province = new Province(data);
expect(province.shortfall).equal(0);
});
테스트 코드의 품질 관리
책에서는 테스트 코드도 프로덕션 코드만큼 품질 관리가 중요하다고 설명합니다:
- 테스트 코드의 가독성: 테스트 이름과 구조가 의도를 명확히 전달해야 합니다.
- 중복 제거: 공통 설정과 검증 로직은 도우미 함수로 추출합니다.
- 테스트 독립성 유지: 각 테스트는 다른 테스트에 영향을 받지 않아야 합니다.
- 테스트 실행 속도: 느린 테스트는 개발 주기를 느리게 만들기 때문에 빠르게 실행되어야 합니다.
버그 수정 프로세스
버그를 발견했을 때의 권장 프로세스
- 버그 재현 테스트 작성: 버그를 드러내는 실패하는 테스트를 먼저 작성합니다.
- 테스트가 통과하도록 수정: 최소한의 변경으로 테스트를 통과시킵니다.
- 다른 테스트 확인: 새 수정사항이 다른 기능을 깨뜨리지 않았는지 확인합니다.
테스트 대역(Test Double) 활용
복잡한 의존성을 가진 코드를 테스트하기 위한 방법으로 다음과 같은 테스트 대역을 소개합니다:
- 더미 객체(Dummy): 전달만 되고 실제로 사용되지 않는 객체
- 스텁(Stub): 호출에 대해 미리 준비된 응답을 제공하는 객체
- 스파이(Spy): 호출 여부와 방법을 기록하는 객체
- 모의 객체(Mock): 기대된 호출을 검증하는 스텁
- 가짜 객체(Fake): 실제 구현보다 단순하지만 동일한 동작을 하는 객체
테스트 범위와 밸런스
- 단위 테스트와 통합 테스트의 균형: 작은 단위의 테스트와 더 큰 단위의 통합 테스트를 적절히 조합합니다.
- 모든 코드를 테스트하려 하지 않기: 핵심 로직과 복잡한 부분에 테스트 노력을 집중합니다.
- 표현력 있는 테스트 작성: 테스트는 코드 사용 방법을 보여주는 문서 역할도 합니다.
Mocha 대신 Jest 사용하기
책에서는 Mocha와 Chai를 테스트 프레임워크로 사용하고 있지만, 저는 현업에서 더 널리 사용되는 Jest를 활용하여 동일한 개념을 적용할 것입니다.테스트 러너와 검증(assertion) 라이브러리가 통합되어 있어 사용이 간편합니다.
Jest Getting Started https://jestjs.io/docs/getting-started
Getting Started · Jest
Install Jest using your favorite package manager:
jestjs.io
테스트와 리팩토링의 관계
테스트와 리팩토링의 상호보완적 관계를 강조합니다:
테스트는 리팩토링을 가능하게 하고, 리팩토링은 더 좋은 테스트를 작성할 수 있게 해준다.
Chapter05 리팩터링 카탈로그 보는 법
리팩토링 기법 카탈로그를 어떻게 활용하는지 간략히 설명하는 짧은 장입니다
여기서 카탈로그 항목의 구성과 용도를 소개합니다.
카탈로그 항목의 구성
각 리팩토링 기법은 다음과 같은 구조로 설명됩니다:
- 이름(Name): 기술 용어 사전에 등재할 정도로 중요한 이름으로, 개발자 간 의사소통을 위한 어휘입니다.
- 개요(Summary): '무엇을' 리팩토링하는지 설명하고, 변경 전후 코드의 기본 아이디어를 스케치로 보여줍니다.
- 동기(Motivation): '왜' 리팩토링을 해야 하는지와 '언제' 하지 말아야 하는지를 설명합니다.
- 절차(Mechanics): '어떻게' 리팩토링을 수행하는지 단계별로 설명합니다. 실수 없이 리팩토링할 수 있도록 돕는 체크리스트입니다.
- 예시(Example): 실제 동작하는 간단한 코드를 통해 리팩토링 과정을 보여줍니다.
리팩토링 카탈로그를 사용할 때 다음 사항을 유의
- 절차를 순서대로 따르기: 각 단계를 차례대로 수행하고, 테스트로 확인하며 진행합니다.
- 상황에 맞게 응용하기: 절차는 지침일 뿐, 상황에 맞게 유연하게 적용해야 합니다.
- 리팩토링 이름 사용하기: 팀원과 대화할 때 "함수 추출하기"와 같은 리팩토링 이름을 사용하면 의사소통이 명확해집니다.
결론
4장 "테스트 구축하기"와 5장 "카탈로그 보는법"을 공부하면서, 많은 개념과 원칙을 접했지만 솔직히 말해 아직 이 모든 것이 실제로 얼마나 유용한지는 의문이 남습니다. 특히 테스트 코드에 대해서는 그 중요성을 이론적으로 이해했지만, 실무에서 시간 압박 속에 테스트 코드 작성이 과연 가치 있는 투자인지 확신이 서지 않는 상태입니다.
테스트 코드가 리팩토링의 필수 전제 조건이라고 주장하지만, 실무에서는 종종 '테스트는 나중에'라는 생각으로 미뤄지곤 합니다. 테스트 코드 작성에 드는 시간과 노력이 실제로 어떤 이점으로 돌아오는지, 이론이 아닌 경험으로 확인하고 싶습니다.
이어지는 장들에서는 실제 코드와 예제를 다루며 리팩토링과 테스트의 실질적인 가치를 확인해 볼 예정입니다. 단순히 개념을 이해하는 것에서 나아가, 직접 테스트 코드를 작성하고 리팩토링 기법을 적용해보면서 체계적인 리팩토링 기법들이 실제 코드 품질 향상에 얼마나 기여하는지 경험해볼 예정입니다.
'개발' 카테고리의 다른 글
[리팩터링 2판] JavaScript 리팩터링 도서학습 #4 (4) | 2025.04.24 |
---|---|
[리팩터링 2판] JavaScript 리팩터링 도서학습 #3 (3) | 2025.04.17 |
[리팩터링 2판] JavaScript 리팩터링 도서학습 #1 (5) | 2025.03.23 |
모듈 페더레이션(Module Federation) 이해하기 (6) | 2025.01.26 |
댓글