안녕하세요 개발자 doro입니다.
TDD(Test Driven Development)
는 개발자들에게 훌륭한 개발방법론이라고 소개되었음에도 불구하고 주변을 둘러봤을 때, 대부분 현업에서 실천하고 있는 사례를 많이 보진 못한 것 같습니다. 그 이유는 아마
- 테스트 코드를 짜는것보다 제품 코드를 좀 더 견고하게 다듬는 것이 중요하다고 생각하는 문화에서 개발을 하고 있다거나
- 테스트 코드를 짰음에도 제품 코드의 디버깅이 나아짐을 경험하지 못했다거나
- 제품 코드보다 테스트 코드를 먼저 짠다는 것이 납득이 안되거나
등 다양한 이유가 존재할 거라고 생각 되는데요.
그럼에도 불구하고 TDD
를 도입해 개발을 했을 때 얻을 수 있는 장점은 충분하다고 생각합니다. 설령 테스트 코드를 먼저 짜지는 않는다고 해도 테스트 코드를 염두하는 것 자체만으로 제품 코드에 많은 변화를 가져다 줄 수 있습니다.
단순히 테스트 코드와 제품 코드를 작성하는 순서를 바꾸는 것으로 끝나는게 아니라 어떤 관점으로 제품을 개발할 것인가에 대한 기조를 결정해주는게 TDD
의 가장 큰 핵심이라고 생각합니다.
이번 포스팅에서는 이런 패러다임의 변화가 어떤 장점을 가져다 주는지 소개해보려고 합니다.
테스트는 객체지향을 싣고
TDD
는 Test Driven Development
입니다. 테스트를 개발이 주도한다는 하나의 개발방법론입니다. 여태껏 테스트는 개발이 완료된 제품에 대해서 설계한 대로 동작을 잘 하는지를 확인하기 위해 사용되었는데, 개발을 하기도 전에 테스트 코드를 작성한다는게 좀 의아합니다.
하지만 테스트 코드는 기능 자체로도 용이하지만 테스트 코드를 작성할 수 있는 코드를 작성하는 것 자체로 매우 의미가 있습니다.
반대로, 테스트 코드를 작성하지 못하는 경우는 결합도가 높은 코드, Singleton
객체를 내부적으로 참조하고 있는 코드 등 객체지향 설계 원칙을 위반한 코드를 작성했을 때가 대부분입니다.
결국 테스트 코드를 작성하지 않더라도 테스트 코드를 작성할 수 있는 코드를 작성하는것 자체로 의미가 있게 되고, TDD
는 테스트 코드 부터 작성하게 되니 순서만 바꿨음에도 불구하고 제품 코드에 영향을 끼칠 수 밖에 없게 됩니다.
제품 코드를 작성한 이후에 테스트 코드를 작성하려고 한다면 리팩토링을 한 번 거쳐야 하는 경우도 있을 수 있으니 결국엔 운영하는 관점까지 고려한다면 TDD
가 오히려 시간적으로도 경제적인 개발방법론이라고 할 수 있겠습니다.
한 번 예시를 들어보겠습니다.
위의 탭은 Duckie
에서 사용되는 탭 컴포넌트입니다. 해당 탭의 이름들은 서버로부터 전달받는다는 가정을 해보겠습니다.
위의 탭 컴포넌트의 Composable
함수를 작성할 때 자연스럽게 해당 Response
값을 파라미터로 넘겨받으면 좋을 듯 한데요.
자, QuackMainTab
함수는 매우 명확합니다. 탭을 그리기 위해서 필요한 데이터를 서버로부터 내려받고, 이를 파라미터로 정의하였기 때문이죠. 이런 류의 코드는 제품을 개발하면서 자연스럽게 작성할 수 있는 생각의 흐름이 반영된 코드라고 할 수 있습니다.
하지만 이 코드는 여러 모로 개발자를 귀찮게 할거라고 생각이 듭니다.
가장 큰 문제는 객체들간의 필요없는 결합도가 생겼다는 점입니다. 앞으로 QuackMainTab
은 TabResponse
에 외부적인 변경점이 생길때마다 같이 관리해야하는 대상이 되어버렸습니다.
TabResponse
대신에TabResponseV2
라는 객체로 탭 정보를 내려줄게
라는 서버 API의 변화가 있게된다면 꼼짝없이 QuackMainTab
도 VCS의 diff에 걸려버리게 되는것이죠. 이는 Single Response Principle
(단일 책임 원칙)을 위배하게 되고, QuackMainTab
이 알아야 하는 것보다 많은 정보를 가지고 있게 되므로 필요없는 책임을 지게 되는 겁니다. 캡슐화에 실패한셈이죠.
그렇다면 어떻게 수정해야 할까요? 다시 한 번 QuackMainTab
의 역할에 대해서 생각해봐야 합니다. Composable함수는 주어진 데이터를 서버로부터 내려받아 화면에 렌더링 해야하는 View
의 역할만 가져야 합니다.
아래와 같이 리팩토링 한다면 어떠한 커스텀 객체와도 결합도가 사라지게 되며 UI를 그리는 코드 그 자체의 역할과 책임만 가지게 됩니다.
테스트 코드부터 작성했다면 애초에 QuackMainTab
을 설계할 때, TabResponse
라는 도메인 객체를 염두하지 않았을겁니다. 이는 객체의 역할을 설정할 때 좀 더 명확한 관점에서 객체를 설계할 수 있게 되며, 쓸데 없는 결합도를 만들지도 않았을 확률이 높아집니다.
많은 정보에 노출되있는 상태라면 역할과 책임을 잘못 설계할 확률이 높아집니다. 어쩌면 TDD
라는 개발방법론 자체에도 객체지향의 요소인 캡슐화가 숨어있다고도 느껴지는데요. 객체지향의 여러 가지 원칙들을 지키는데 도움이 되는 개발 방법론이라는 것을 알 수 있을 것 같습니다.
좀 더 깐깐하게
TDD
의 또 다른 장점은 자연스럽게 코너 케이스에 대해서 생각을 많이 하는 환경이 만들어지게 된다는 것입니다. 여러 가지 케이스에 대해 테스트 코드를 작성하다 보니 단순한 예상이 아니라 눈으로 직접 확인하며 좀 더 견고한 코드를 작성할 수 있게 됩니다.
이제 위의 탭을 가지고 테스트 코드를 작성해보려고 합니다. 어떤 특성을 테스트 해보면 좋을까요?
- 하단바의 색깔은
DuckieOrange
이고 크기는 탭의 텍스트에 따라 변경되며 텍스트의 양 끝으로 2dp씩 더 길어야합니다. - 탭이 선택되었을 때,
Typography
가bold
로 변경되어야 합니다. - 각 탭의 간격은 28dp로 일정해야 하합니다.
- 컴포넌트는 horizontal하게는 16dp만큼 패딩이 존재하며 centerVertically합니다.
- Divider는 컴포넌트의 Bottom에 위치하며 탭보다 양 옆으로 16dp만큼 더 존재해야 합니다.
등 이외에도 탭은 많은 UI특징들을 가지고 있습니다. 그렇다면 이러한 정보를 가지고 테스트 코드부터 만들어 볼까요?
그 다음에 실제 구현을 완성하여 위의 테스트 케이스를 실행했을 때, 모두 통과할 수 있었습니다.
이렇게 몇 가지 테스트 코드를 짜고 구현하면서 쉽게 들 수 있는 생각이 있는데요. 각 파라미터에 다양한 데이터를 넣어가며 테스트 케이스를 확인해볼 수 있다는 것입니다.
- 탭의 갯수가 화면을 넘어갈만큼 많아지면 어떡하지?
- 탭의 텍스트가 비정상적으로 길어지면 어떡하지?
- 탭의 텍스트의 크기가 안드로이드 시스템에 의해서 바뀌면 어떡하지?
실제로 데이터를 바꿔서 테스트 코드를 돌려보면 테스트 케이스에서 실패하는것을 알 수 있습니다.
데이터를 바꿨더니 테스트 코드가 통과하지 않는다는 것은 애초에 제품 코드를 잘못 짠것입니다. 객체의 역할이 여러 가지 사용자 경우에 대해서 모두 커버하지 못하는 것이죠.
우린 단순히 위의 테스트를 통과하기 위해 코드를 수정하는것을 넘어서 왜 미리 정의해둔 테스트 코드가 데이터가 바뀌었다고해서 통과되지 않는지 고민해야합니다.
저의 경우에는 탭이라는 컴포넌트에 대한 정의 자체가 잘못되었었고, 디자이너와 협의 후 재정의한 컴포넌트는 테스트 통과를 할 수 있게 되었습니다.
위와 같은 상황이 QA매니저에게 이슈라이징 되었더라면 커뮤니케이션 비용도 발생하고, 다시 코드를 디버깅해야하는 수고를 겪어야합니다. 많은 상황을 고려한다면 결국에 TDD
는 시간이 오래 걸리는 개발방법론이 아니라는 것을 알 수 있습니다.
추가적으로 더 걸리는 시간은 잘못 정의된 코드를 수정하는데 필요한 시간이므로 기획이 바뀐다면 나중에는 수정하는데 사용할 시간을 미리 사용하는 것 뿐이지요.
마치며
개발방법론 하나 바꿨다고 코드의 에러가 급격히 사라진다거나 성능이 월등히 좋아진다거나 하지는 않습니다. 하지만 객체지향 설계를 더 쉽게 할 수 있게 되고, 개인적으로도 어떤 환경에서 무슨 관점으로 내 코드를 피드백하고 설계하느냐는 자신의 실력 증진에 있어서 매우 중요한 포인트라고 생각하는데요.
이 포스팅에서는 단순히 UI테스트만을 가지고 예시를 들었지만 조금 더 깊숙히는 Recomposition
호출 횟수 체크를 통한 컴포저블 함수의 성능도 테스트할 수 있을 것 같고, 여러 가지 Unit Test들도 단순한 결과값 비교를 넘어서 코너케이스에 대한 테스트도 가능하다고 생각합니다.
위의 테스트들도 개발 초기부터 고려를 하게 된다면 더 나은 코드를 작성할 수 있을 것 같습니다.
사실 테스트 코드를 마음껏 작성할 수 있는 테크팀은 주변에서 보기 힘든게 현실인데요. 그렇다면 테스트 코드를 작성하지는 않더라도 테스트 코드를 작성할 수 있는 코드부터 작성해보는건 어떨까요?
감사합니다.