오늘은 리팩토링 챕터 4 테스트 구축하기를 읽어보겠습니다. 책을 지금까지만 읽어도 테스트 코드가 리팩토링에 중요한 요소인 것을 알 수 있습니다. 리팩토링을 하는 것은 겉보기 동작은 그대로 유지한 채 내부의 구현을 개선하는 작업입니다. 동작이 동일하다는 것을 검증할 수 있다면 훨씬 더 자신있게 내부 구현을 수정할 수 있습니다. 리팩토링에 대한 열망은 항상 있으나, 자신있게 진행했던 적은 많지 않았습니다. 항상 변경에 대한 두려움을 가지고 있었고 잘 작동하는 코드를 굳이 개선할 필요는 없다는 의견에 꽤나 마음이 쏠려 있었습니다. 이번 챕터를 읽으면서 두려움을 지루함으로 바꾸는 계기가 되면 좋겠습니다.
1. 자가 테스트 코드의 가치
실제로 코드를 설계하는 시간 보다 디버깅 하는 시간이 훨씬 더 많이 소요됩니다. 테스트 코드를 작성하면 버그가 발생했을 때 어느 부분을 수정할 지 빠르게 감지할 수 있습니다. 결과적으로 개발시간을 단축시킬 수 있다고 합니다. 이런 경험을 일부 라이브러리 작성할 때는 체감할 수 있었습니다. 하지만 프로덕션 코드를 작성할 때는 대부분 테스트 없이 QA 에만 의존하고 개발하고 있었습니다. 처음 테스트 도입을 하는 것이 귀찮고 고통스럽지만 그 시간을 이겨내야겠다는 생각을 했습니다. 개인앱에도 테스트를 먼저 도입해보려 합니다. 개발한지 몇 년 지난 코드들도 있어서 테스트 코드를 추가해보는 연습을 할 수 있을 것 같습니다.
테스트를 작성하기 가장 좋은 시점은 프로그래밍을 시작하기 전이다.
얼마전에 작은 과제를 할 기회가 있었는데, 테스트 코드를 작성하는 것 또한 목표에 있었습니다. TDD 책을 읽었던 경험을 떠올리며 실패하는 테스트 코드를 먼저 작성했습니다. 실패하는 테스트 코드가 때로는 컴파일 실패일 수도 있습니다. 테스트 코드를 제일 처음 작성하게 되면 컴파일 실패를 만나게 됩니다. 컴파일 실패를 만나게 되니 명확하게 컴파일 실패를 고치는데 포커스를 맞출 수 있었습니다. 그 다음 단계로는 내부를 자신있게 수정할 수 있었습니다.
리팩터링에는 테스트가 필요하다. 그러니 리팩터링하고 싶다면 테스트를 반드시 작성해야 한다.
테스트 코드가 없는 경우 테스트 코드를 추가하고 리팩토링을 하면 된다는 것...!
2. 테스트할 샘플 코드
UI, 영속성, 외부 서비스 연동과는 관련 없는 가장 쉬운 코드 부터 테스트
3. 첫 번째 테스트
각각의 테스트가 실패하는 모습을 최소한 한 번씩은 직접 확인해본다. 이를 위해 내가 흔히 쓰는 방법은 일시적으로 코드에 오류를 주입하는 것이다.
테스트를 추가했는데 곧 바로 통과한다면 안심하기 보다는 적절하게 실패하는지 확인해보는 것이 중요함
자주 테스트 하라는 말이 있습니다. iOS 앱 개발에서 테스트 하는 경우 앱이 실행되는 것 까지 기다린 뒤에 테스트 코드가 실행되어서 테스트 하는 리듬을 잃어버리는 경우가 많습니다... 이거 뭔가 방법이 있을 것 같은데 한번 찾아봐야겠습니다. 알고보니 UI 테스트가 아닌 경우 Host Application을 None으로 설정해서 앱을 실행하지 않고도 테스트를 할 수 있습니다. 그래도 시뮬레이터는 실행되네요ㅎ 아마도 테스트가 실행될 기반 OS가 필요해서 시뮬레이터가 실행되는 것 같습니다. stackoverflow.com/a/37432762
4. 테스트 추가하기
명심하자! 테스트는 위험 요인을 중심으로 작성해야 한다! 테스트의 목적은 어디까지나 현재 혹은 향후에 발생하는 버그를 찾는데 있다.
완벽하게 만드느라 테스트를 수행하지 못하느니, 불완전한 테스트라도 작성해 실행하는 게 낫다.
책에서 '픽스처'라는 용어가 나오는데 궁금하니 한번 알아보겠습니다. 픽스처는 테스트를 위해 설치하는 요소를 의미합니다.
픽스쳐 (Fixture) : 고정 설치된 물건이라는 뜻처럼, 테스트 케이스 실행 전후에 항상 실행하는 함수를 의미합니다. 실제로는, 테스트 함수를 실행하기 위해 필요한 환경을 미리 구축하거나(setup) 실행 후 리소스를 정리하는(teardown) 함수, 그리고 이와 함께 사용되는 사용자 데이터(data)로 구성됩니다. 참고로, GLib에서는 각 테스트간 의존성을 피하기 위해 모든 테스트 케이스를 실행할때마다 매번 픽스쳐를 새로 구성하는 방식(fresh fixture)을 사용합니다.
출처: https://lethean.github.io/2010/02/12/using-glib-test-framework/
XCTestCase에도 픽스처를 위한 템플릿 메소드가 있습니다.
책에서는 위와 같은 템플릿 메소드를 사용하지 않았는데도 픽스처라고 부르고 있습니다. Given - When - Then 단계에서 Given을 위해 세팅하는 코드를 픽스처라고 부르면 되는게 아닐까 추측해 봅니다.
불변임이 확실한 픽스처는 공유하기도 한다. 그래도 가장 선호하는 방식은 매번 새로운 픽스처를 만드는 것이다.
describe('provide', function() {
const asia= new Province(sampleProvinceData()); // 이렇게 하면 안 된다.
it('shortfall', function() {
expect(asia.shortfall).equal(5);
});
if('profit', function() {
expect(asia.profit).equal(230);
});
});
책의 예제에서 이렇게 공유 가능한 픽스처로 보일지라도 새롭게 만드는 것을 추천합니다. 테스트 순서에 따라 결과가 달라지기도 하고, 디버깅이 어렵게 만들기 때문입니다.
describe('provide', function() {
let asia;
beforeEach(function() {
asia = new Province(sampleProvinceData());
});
it('shortfall', function() {
expect(asia.shortfall).equal(5);
});
if('profit', function() {
expect(asia.profit).equal(230);
});
});
대신 beforeEach를 통해서 각 테스트 실행전에 새롭게 픽스처를 생성하는 것을 추천합니다. 이렇게 하면 클래스 생성에 따라 테스트가 오래 걸릴 수 있지만 인식할 수 있는 만큼 느려지지는 않습니다.
책에서는 Mocha 라이브러리를 통해서 테스트를 작성했는데, Swift에서도 많이 사용하는 github.com/Quick/Quick 과 문법이 비슷한 것 같습니다. 아마 Quick도 간접적으로 영향을 받아서 만들어지지 않았을까 추측해봅니다.
5. 픽스처 수정하기
beforeEach에서 표준 픽스쳐를 세팅하고 각 테스트에서 픽스처를 수정해서 사용해야하는 경우도 있습니다.
it('change production', function() {
asia.producers[0].production = 20; // 픽스처 수정
expect(asia.shortfall).equal(-6);
expect(asia.profit).equal(292);
});
expect 구문이 2개가 있는데, 보통은 1개만 있다고 합니다. expect 구문이 2개가 있으면 앞쪽 검증이 실패했을 때 뒷쪽 검증은 실행되지 않기 때문입니다. 해당 부분에서는 긴밀하게 관련되어 있는 부분이라서 예외로 2개의 expect 구문이 놓여져 있습니다.
6. 경계 조건 검사하기
문제가 생길 가능성이 있는 경계 조건을 생각해보고 그 부분을 집중적으로 테스트하자.
TDD 관련해서 전수열님의 발표를 들은 적이 있었는데, 그 때도 이런 엣지 케이스를 테스트 하는것에 집중했던 기억이 납니다. 예를들어 숫자를 10으로 테스트 하고 있었다면, 99, 999, 9999 ... 이런식으로 자릿수가 바뀌는 경우도 제대로 작동하는지 테스트하면 보다 다양한 상황에 대처할 수 있는 코드를 작성할 수 있습니다.
나는 의식적으로 프로그램을 망가뜨리는 방법을 모색하는데, 이런 마음 자세가 생산성과 재미를 끌어올려준다.
본인의 코드에 테스트를 하면서 망가뜨려보는 것. 작성한 코드를 애지중지 하는 것 보다 훨씬 더 유용한 방법인 것 같습니다.
나는 항상 테스트 스위트부터 갖춘 뒤에 리팩터링하지만, 리팩터링하는 동안에도 계속해서 테스트를 추가한다.
테스트는 항상 발전시켜 나가는 것임을 염두해두고 있어야겠습니다.
7. 끝나지 않은 여정
테스트 용이성(testability)을 아키텍처 평가 기준으로 활용하는 사례도 많다.
지금 사용하고 있는 아키텍처의 역할 분배와 편리함에만 주목했던 것 같은데, 테스트 용이성도 함께 체크해보면 좋을 것 같습니다. 주력으로 사용하는 아키텍처가 테스트하기 좋으면 테스트 코드를 작성하는 것도 쉬워질 것으로 예상됩니다.
버그를 발견하는 즉시 발견한 버그를 명확히 잡아내는 테스트부터 작성하는 습관을 들이자. 아주 중요한 습관이다!
버그 리포트를 받으면 가장 먼저 그 버그를 드러내는 단위 테스트부터 작성한다.
이번 챕터에서는 리팩토링을 위한 테스트 코드 작성에 대해 알아봤습니다. 토이 프로젝트에서 좀 더 많은 테스트를 연습해봐야겠다는 생각이 들었습니다. 책의 사례들만 봤을 때는 테스트를 통해 결과적으로 더 안정적인 코드를 얻을 수가 있었습니다. 직접 경험해보고 회고할 수 있는 날이 오면 좋겠습니다.
'CS > Refactoring' 카테고리의 다른 글
[Refactoring] Chapter 3, 4 과제 (0) | 2021.04.17 |
---|---|
[Refactoring] Chapter4를 Swift로 따라해보기 (0) | 2021.04.17 |
[Refactoring] Chapter 3 요약: 코드에서 나는 악취 (0) | 2021.04.16 |
[Refactoring] Chapter2 과제 (0) | 2021.04.13 |
[Refactoring] Chapter2 요약 (0) | 2021.04.13 |