View
이번에 회사 인턴을 시작하고, 제일 처음으로 담당하게 된 미션은 테스트코드를 작성하는 것이였다. 난생 처음 테스트코드를 작성해보는 것이라 사실 좀 걱정을 했었다. 열심히 기존 코드들을 보면서 클린 아키텍처를 기반으로 나뉘어있는 레이어 위 기능들에 대해서 각각 테스트코드를 작성하고 합치는 작업을 공부하고 수행했고, 생각했던 것보다 내가 얻어가는게 많은 테스크였다고 생각이 들었다.
우선 테스트코드를 작성하기 위해서 프로젝트 내에서 다음의 내용들을 파악해야 했다.
- 어떤 구조로 프로젝트가 구성되어 있는가
- 어떤 패키지들을 사용하고 있고, 그 사용 방법은 무엇인가
- 프로젝트 내에서 사용하는 커스텀 타입들은 어떤게 있고, 내부 값들을 이용하려면 어떻게 접근해야 하는가
- 프로젝트에서 사용하는 다양한 API, 상수 등은 어떻게 관리되고 있는가
사실 테스트코드를 처음 짜보기도 했고, 팀에서 사용하는 다양한 CICD에 적응하기 위해서 추가적으로 더 많은 것들을 익혀야 했고, 지금도 익히는 중이다.
- 테스트코드 작성법 + 테스트코드 작성 패키지 사용 방법
- Push, PR, Merge에 대한 조건
- Github Actions 사용 방법
심지어 테스트코드를 작성하면서는 내가 벌써부터 프로덕트에 기여를 하고 있다는 생각이 들고 실시간으로 Test Coverage 퍼센트가 수치적으로 올라가는 것을 보면서 약간의 성취감도 들기 시작했다.
앞으로 내가 기능 구현을 맡게되면, 우선 테스트코드를 작성하고 이를 기반으로 로직을 작성한 뒤에, 데이터와 UI를 만들어 로직을 연결하는 방식으로 개발을 진행할 것이라 팀원분께서 말씀해주셨다. 나는 이게 TDD 프로세스를 따라 개발을 진행하게 된다는 걸로 이해를 해서, TDD에 대해 미리 조금 더 상세하게 공부를 해두고 회사에 가서 적용을 해보려고 한다!
Software Testing 이란?
내가 작성한 소프트웨어 (함수, 기능, UI, API의 스펙, 성능 등) 가 예상대로 작동하는지 확인하는 것. 목표에 따라서 다양한 종류의 테스트를 수행한다.
테스트 절차
기본적으로 거의 모든 테스트는 아래의 절차로 테스트와 개발을 수행한다.
- 작성한 코드에 대해 기대하는 바 (Expectation, Requirement)에 대해 테스트코드를 작성함
- 테스트를 실행했을 때 실패가 나온다면 Expectation에 맞춰 코드를 수정하기
- 테스트를 통과하면 끝!
Test Driven Development
테스트 주도 개발(TDD)는 테스트를 우선적으로 작성하면서 기능을 개발해나가는 개발 방법론이다.
아래 그래프처럼, 구현한 로직에 디테일한 개발 이론들을 적용하기 전에 미리 테스트를 작성하고, 이후에 리팩토링 같은 개선을 거쳐 유지보수하기 좋은 기능을 만들어내는 방법이다.
여기에서 테스트코드의 작성, 최소 기능의 로직 구현은 각각 대략 1분씩 수행하면서 테스트코드의 작성과 구현코드의 작성을 “동시에 수행”하면서 개발을 이어나가는 방법론이라고 보면 될 것 같다.
TDD의 장점
한 유튜브 영상에서는 TDD를 “생각 없이 코드부터 두드리는 것을 막아주는 방법론” 이라고도 표현한다. 우선적으로 내가 구현해야 하는 기능을 설계부터 수행하고나서 디테일한 구현에 들어가도록 강제하기 때문에, 내가 뭘 구현해야 하는지에 대해 좀 더 생각하고 작성할 수 있게 도와준다.
물론 테스트코드를 작성하는 것도 너무 귀찮고, 요구사항이나 명세가 변경됨에 따라서 테스트코드도 계속 유지보수를 해줘야하기에 개발에 추가로 Cost가 든다는 단점이 있다. 그럼에도 테스트코드를 먼저 작성하는 몇 가지 장점이 있다. 아마 TDD 프로세스를 직접 경험해봐야 장점들에 대해 피부로 느낄 수 있겠지만, 개념적으로 사고해봤을 때 아래의 이점들은 확실히 있을 것이라 생각된다.
- 내가 작성하고자 하는 기능에 대한 분석과 이해를 바탕으로 기능을 개발하게 된다
- 본격적인 로직의 구현을 들어가기 전에 기능의 명세(input, output)에 대해 정확하게 설계와 파악을 수행하기 때문에, 높은 이해도를 바탕으로 더 명확하게 구현이 가능하다.
- 인터페이스에 집중해 코드를 설계할 수 있다
- 어떤 input에 대해 어떤 output (or Exception)이 나와야 하는지에 대해서 설계를 하고 테스트코드를 우선 작성하기 때문에, “내부를 어떻게 구현할 것인가” 만큼이나 “어떻게 표현할 것인가”에 대해 고민할 수 있게 해준다.
- 사용자가 이 코드를 가져가 사용하는 시점에 더 이해하기 쉬운 코드가 무엇일지 고민
- 밀가루 110g == 밀가루 종이컵 한 컵 / 소금 3g == 소금 한 꼬집
- 더하기 연산에 대해서 add, plus, operation(+) 등 다양한 표현 방식이 있고, 이에 대해 고민
- 코드의 품질 향상, 코드 안정성 향상이 가능하다
- 테스트코드의 길이가 길어지면 책임 분리가 필요한 메서드라고 생각하고, 더 잘게 쪼갤 수 있음
- 예외 케이스 등에 대해 미리 설계한 대로 더 꼼꼼한 핸들링이 가능하다
- 리팩토링을 하기 쉬워진다
- 코드 변경에 대한 Side Effect를 테스트코드로 검사할 수 있어, 리팩토링 시 안정성이 높다.
- 개발 Cost가 낮아진다
- 테스트 코드를 이후에 작성하는 경우에는 테스트 통과 여부에 따라 개발 단계를 여러번 수행하게 되어 피드백 주기가 길어진다. 그러나, TDD에서는 테스트를 통해 결과를 미리 정해뒀기 때문에, 내부 로직에 대한 개발 단계는 한 번만 진행할 수 있다.
- 테스트 코드를 이후에 작성하는 경우에는 테스트 통과 여부에 따라 개발 단계를 여러번 수행하게 되어 피드백 주기가 길어진다. 그러나, TDD에서는 테스트를 통해 결과를 미리 정해뒀기 때문에, 내부 로직에 대한 개발 단계는 한 번만 진행할 수 있다.
- 작업량에 대한 파악이 쉬워진다
- 테스트코드에 대한 설계가 끝나면, 기능 개발을 완료하기 위해 얼마나 더 개발을 진행해야 할 지 빠르게 파악이 가능해진다.
- Over-Engineering을 방지해준다
- 내가 작성하는 로직에 대해서 좀 더 최적화를 하고싶은 개발자의 심리가 있지만, ‘여기까지 구현하면 Pass’ 라는 기준을 테스트코드를 통해 먼저 세워뒀기 때문에, Over-Engineering인지 아닌지를 판별하는 기준이 된다.
테스트코드 작성 시 유의할 점
구현 이후에 테스트 코드를 작성하려고 하면 내가 어떻게 구현을 했는지에 대해 테스트를 작성하게 된다. 이번에 회사에서 테스트코드 작업을 하면서도, 생각해보면 나는 로직의 if 문이 어떻게 분기가 되어있고 try catch로 어떻게 Exception을 처리하고 있는지를 살펴보고 나서, 여기에 맞게 테스트코드를 작성하려고 했던 것 같다. 이런 식으로 생각하고 테스트코드를 작성하게 되면, 작성된 테스트 케이스 외에 발생한 문제에 대해서는 테스트코드로 확인할 수 없다는 문제가 있고, 테스트코드를 작성하는 큰 의미가 없어지게 되어버린다.
그래서 TDD에서 테스트를 작성할 때에는 기능 구현에 대한 테스트가 아닌 클래스(객체)의 목적에 대해 테스트를 작성해야 한다. 즉, 인터페이스에 대한 테스트를 수행해야 한다.
흠… 🤔🤔🤔 경험적으로 내가 잘못하고 있었다는게 느껴지는구만.
또, 너무 원리 원칙적으로 TDD에 대해 울부짖는 것도 그리 건강해보이지는 않았다. 어찌되었든 프로페셔널 개발자라면 데드라인에 맞춰서 제품을 만들어내야하는데, 언제까지고 테스트코드를 먼저 작성해야 한다고 붙들고 있느라 마감을 지키지 못하게 되는 케이스에 대해서도 사람들이 많이 논하고 있는 것 같았다. 여기에 대해서는 “언제나 테스트코드를 먼저 작성하고 구현 코드를 작성해야 한다”는 강경 TDD 파와 “제품이 나오는 시점에 테스트코드가 작성되어 있으면 된다”는 온건 TDD 파로 나뉘는 것 같다. 또, 테스트 코드를 작성하는 것에 대해서는 언제나 환영이지만, TDD는 실제로 적용하기에 너무 어렵다는 의견도 자주 보였던 것 같다.
내 생각에는
개발계에서 새로운 방법론이나 기술을 도입하는 경우는 딱 2가지 이유 중 하나라고 나는 생각한다.
- 이걸 쓰면 Cost가 줄어든다 (시간과 돈의 절약)
- 이걸 쓰면 제품이 덜 고장난다 (안정성 향상)
객체지향을 도입했던 이유도 여기에 포함된다고 생각한다. 물론 협업하기 쉬워지고 인간의 사고방식에 가까운 설계 방식을 통한 개발비용 절감의 이유도 있었을 테다. 그치만 나는 객체 단위로 코드의 영역을 분리하고 외부에서의 멤버값과 메서드 접근을 제한해서 “코드를 못짜는 사람들이 코어 로직을 함부로 건드릴 수 없게 만들어 제품이 고장나지 않도록 하는 것이” 객체지향을 도입한 중요한 이유 중 하나였다고 생각한다.
객체지향과 똑같이, TDD를 도입하자고 주장하는 배경은 생각 없이 코드를 짜는 것을 막아 제품의 고장을 막기 위해서라고 생각한다. 물론 아주 프로페셔널들이 모여있다면 테스트코드를 작성하지 않아도 문제 없이 코드가 동작할 것이다. 그러나, 혼자 머릿속으로만 로직에 대해 검증하고 PR을 올린 못미더운 아마추어 개발자의 코드에 대해서 “이거 진짜 잘 작동해? 이거 너 생각하고 코드 짠 거 맞아? 이거 머지해도 제품 고장 안나는거 확실해?” 라고 말할 수는 없으니, TDD 라는 이름으로 바꿔서 “우리 이제는 생각하고 코드를 짜보아요~” 를 아마추어 개발자들에게 제시하고 있는 것 같다.
TDD에 대해서 반감을 가지는 사람들도 많은 것으로 보이는데, 아마 요런 이유가 아닐까 싶다. 일단 TDD의 프로세스 자체가 “생각하고 코드를 짜고 있다는 것을 테스트코드로 증명해라” 라고 난 생각한다. 그러나 이 사람들은 “TDD가 아닌 방식으로도 ‘생각하고 있음’을 증명할 수 있는데, 굳이 TDD 절차에 얽매여서 생각할 필요는 없다” 라고 말하고 있는 것으로 보인다. 물론 당연히 그럴 수 있다고 본다. 사람마다 고민하고 생각하는 방식은 다양하니깐.
몇몇 TDD 글에서는 종교인들의 불쾌한 포교활동에 의해 특정 종교에 반감을 갖게되는 것처럼, TDD 맹신론자들의 태클때문에 TDD 자체에 반감을 갖게 되었다는 글 들도 많이 보이는 것 같다. 나는 TDD 맹신론자들이 TDD ONLY만 주장하는 건 “자신이 생각하고 있음을 말할 수 있는 도구가 테스트코드를 작성하는 것 뿐이라 그렇지 않을까?” 라고 감히 추측해본다. 지금 내가 클린 아키텍처가 정말 Fancy 한 구조라고 생각하고, 여러 프로젝트에 클린 아키텍처를 적용하려고 하는 건 내가 클린 아키텍처 라는 도구만 사용할 줄 알기 때문이라 스스로 생각하고 있는데, 동일한 이유로 누군가는 생각하고 코드 짜는 방법을 TDD만 알고 있기 때문에 이를 예찬하는 것이 아닐까 싶다.
최근 글에서도 TDD에 대한 갑론을박이 오가는 것이 많이 보여진다. 개발계에서는 TDD가 뜨거운 감자인가보다. 그런 만큼 이에 대해 논의를 할 때는 아래처럼 좀 더 시야를 넓게 가지고 생각하고 말하려 노력해야 될 필요가 있을 것 같다. 아니면 그냥 소비적이고 공격적인 논쟁만 하게 될 것 같다.
“나는 TDD를 적용하면 테스트코드를 통해서 내가 생각하고 코드를 짰다는 걸 보여줄 수 있다고 생각하는데, 너는 어떻게 생각하고 코드를 짰다는 걸 나한테 보여줄거야?”
나는 이번에 회사에서 개발을 할 때, 내가 잘 파악하지 못한 구조에 대해서는 TDD를 적용해 개발을 한 번 진행해볼 의향이 있다. 사수 분들께서 “성민님이 클린 아키텍처에 대해서 이해하고 있으니 그 개발 방식에 따라서 도메인부터 시작해서 테스트코드 작성하고 개발하는 방식으로 data, presentation 레이어까지 한 번 개발해보자” 라고 말씀해주셨는데, 한 번 정도는 경험해보고 싶었던 프로세스라 너무 기대가 된다.
단순히 “테스트코드를 작성하고 개발한다”를 외치기만 하는 테스트무새가 되기보다는 “이걸로 내가 생각하고 코드를 짰다” 는걸 말하기 위함이라고 생각하면서 개발을 진행해봐야겠다.
샤라웃
https://youtu.be/Npi21gLIEZM?si=9icRPvTjAb4MbeRq
https://youtu.be/L1dtkLeIz-M?si=Kai0A1-PIdJAG4XI
'Develop' 카테고리의 다른 글
LLVM 컴파일러 (0) | 2024.07.12 |
---|---|
[후기] edwith 부스트코스에서 2번째 코드리뷰를 받았습니다 (0) | 2019.08.19 |
부스트코스에 프로젝트를 제출할 때는 (0) | 2019.08.15 |
[후기] edwith 에서 부스트코스 iOS 강의를 들으면서 느낀 점 (0) | 2019.08.13 |
[리뷰] edwith-부스트코스에서 코드리뷰를 받아봤습니다 (2) | 2019.08.01 |