스터디

리팩터링 2판 - 07. 테스트 구축하기

developerYoung 2023. 7. 18. 22:20
반응형

리팩터링은 분명 가치 있는 도구이지만, 정말 생각지못하게 실수를 저지르기 마련이에요.

그렇기때문에 그러한 부분을 견고하게 잡아줄 테스트 구축이 매우 중요하답니다.

이제 테스트를 통해 우리는 어떤 효율을 기대해야하는지 알아볼까요?!

 

자가 테스트 코드의 가치

모든 테스트를 완전히 자동화하고 그 결과까지 스스로 검사하게 만들자.

 

우리는 작성한 테스트코드를 수시로 확인해야해요. 테스트를 수시로 확인하면 우리가 어느 부분을 수정했기에 오류가 발생했는지 곧바로 체크할 수 있으니깐요. 실제로 기능 구현에 집중하는 시기에 테스트 코드를 작성했었다면, 화면을 보지 않아도 테스트 코드의 작동만을 통해서 우리는 정답을 알 수 있어요! 

 

실제로 최근 저도 저희 서비스의 공공데이터 영역의 테스트 코드를 작성했어요. 확실히 테스트 코드가 성공적으로 돌아간 이후로는 무언가 기능상 변경사항이 생긴다면, 실제 코드를 먼저 수정하는게 아니라 테스트 코드를 먼저 수정했어요. 그리고 실제 코드를 수정한 후 해당 테스트 코드가 성공적으로 완료된 것을 확인했을 때, 화면을 보지않고도 적용이 되었구나를 알 수 있었어요!


이에 더해서 중요한 비즈니스 로직(로그인, 권한별 페이지 노출 처리)을 담고 있는 페이지에선 더욱 테스트가 필요했어요. 매번 배포시 QA를 통해 정상적으로 작동하는지를 확인해야했지만, 테스트 코드를 통해 추가적인 QA가 필요없음을 확신할 수 있었어요!

 

우리는 결과적으로 테스트 코드를 만드는 이유가 버그 검출 도구임을 알아야해요. 수시로 확인함으로 우리가 어디서 버그가 생겼는지, 우리 코드의 버그가 존재하는지를 확인할 수 있도록 작성해야해요.

 

TDD(Test Driven Development) 테스트 주도 개발이란?

테스트를 작성 → 테스트를 통과하게 코드 작성 → 리팩터링

이러한 과정을 짧은 주기로 반복하는 것을 테스트 주도 개발이라고 해요.

테스트를 먼저 작성하게 되면서 우리는 단순 구현보다 기능을 위해 우리가 필요한게 무엇인지 고민하게 되면서 오히려 구현의 에러 분기점을 더 확실히 나눌 수 있어요.

 

첫 번째 테스트

이 챕터부터는 저자가 예시로 테스트를 말해주지만 중요한 부분만 코드로 설명하고 나머지는 인용글귀로... 대체할게요..!

실패해야 할 상황에서는 반드시 실패하게 만들자
자주 테스트하자. 작성 중인 코드는 최소한 몇 분 간격으로 테스트하고, 적어도 하루에 한 번은 전체 테스트를 돌려보자.
실패한 테스트가 하나라도 있으면, 리팩터링하면 안 된다.


테스트 코드 규칙!!

  • 테스트 코드에서 중복되는 부분을 최소화하자!
describe('getDurationTimeBy', ()=> {
	it('도보이용', ()=>{
		const data = publicSubway[0];
		expect(getDurationTimeBy(data.distance)).toBe("도보 18분 / 1.2km")
	})
	it('차량이용', ()=>{
		const data = publicSubway[0];
		expect(getDurationTimeBy(data.distance), '차량').toBe("5분 / 1.2km")
	})
})

위와 같은 중복되는 영역은 바깥 범위로 끌어내어 중복을 제거할 수 있어요!

 

describe('getDurationTimeBy', ()=> {
	const data = publicSubway[0];
	it('도보이용', ()=>{
		expect(getDurationTimeBy(data.distance)).toBe("도보 18분 / 1.2km")
	})
	it('차량이용', ()=>{
		expect(getDurationTimeBy(data.distance), '차량').toBe("5분 / 1.2km")
	})
})

하지만 이 부분에서 큰 흠이 있어요. 만약 공유하는 data의 값을 수정하는 테스트케이스가 있다면, 이후의 테스트케이스에선 변화된 값을 사용하기에 예상치못한 오류를 발생할 수 있어요.

그렇기때문에 아래와 같은 패턴으로 사용하는게 좋아요

 

describe('getDurationTimeBy', ()=> {
	let data;
	beforeEach(()=>{
		data = publicSubway[0];	
	})
	it('도보이용', ()=>{
		expect(getDurationTimeBy(data.distance)).toBe("도보 18분 / 1.2km")
	})
	it('차량이용', ()=>{
		expect(getDurationTimeBy(data.distance), '차량').toBe("5분 / 1.2km")
	})
})
  • beforeEach 구문은 각각의 테스트 전에 실행되며 data를 초기화 시켜줍니다
  • 즉, 데이터의 변화로 인해 결과를 예측할 수 없는 상황을 예방시켜주죠!!
  • 또한 describe 안에 모든 테스트케이스가 동일한 데이터로 시작한다는 사실을 알려줄 수 있어요!

 

 

경계 조건을 생각해보며 테스트해보자

과연 거리가 음수일 때, -100m라는 값을 유저입장에서 받고 싶을까요?

테스트 케이스를 작성하며 이러한 특이 상황을 고려하여 어떻게 처리할지 생각해볼 수 있어요!

 

describe('getDurationTimeBy', ()=> {
	let data;
	beforeEach(()=>{
		data = publicSubway[0];	
	})
	it('거리 음수일 때,', () => {
		data.distance = -100
		expect(getDurationTimeBy(data.distance)).toBe("도보 -3분 / -100m") // ??
		expect(getDurationTimeBy(data.distance)).toBe("도보 0분 / 0m") // ??
	})
})
반응형