테크코스 합류 전 TDD, 테스트 코드가 무엇인지는 알고 있었지만 TDD는 하지도 않았을 뿐더러, 테스트 코드는 매번 작성방법의 기준이 달랐었고 작성하더라도 왜 작성해야 하는지 이유가 명확하지 않은 상태에서 작성했었다. 그때는 테스트 코드는 당연히 작성해야하니깐 작성해야 겠다는 생각을 했었던 것 같다.
테크코스 합류 후, 그리고 지금 레벨 1을 마치는 시점에서 TDD, 테스트 코드를 꾸준히 수행한 결과 부족하지만 TDD, 테스트 코드의 목적과 나만의 작성 기준을 만들 수 있었다.
TDD, 왜 권장하는가?
이전에 TDD로 설계를 하지 않을 경우 보통 다음과 같은 절차를 통해서 기능이 구현되었었다.
1. 기능에 맞는 설계를 고민하고 각 클래스에 어떤 역할을 지정할지 고민한다.
2. 설계와 대략적인 구현 고민을 끝내면 코드를 작성하면 구현을 시작한다.
3. 구현이 종료되면 테스트 코드를 작성하면서 구현에 문제가 있는지 검토한다.
이런 방법은 기능을 완성하는데는 큰 문제가 없었지만 몇가지 문제점이 있었다.
1. 문제가 생길 경우 어디서 문제가 발생했는지 일일히 디버깅을 하면서 찾아야 했다.
2. 만약 기능의 문제가 생겨서 수정이 필요한 경우 해당 로직과 연관되어 있는 모든 로직과 테스트 코드를 수정해야 한다.
3. 기능이 완성되기 전까지 해당 기능이 100% 옳게 동작한다는 보장을 개발자가 받지 못한다.
결국, 이러한 문제들은 구현 로직의 퀄리티를 떨어트리고 개발 시간을 증가시키는 요인 중 하나였다.
하지만, TDD를 적용하면 해당 문제점들을 100% 까지는 아니지만 많이 해소시킬 수 있다.
TDD는 구현 로직을 먼저 작성하는 것이 아니라 테스트 코드를 먼저 작성하고 구현 로직을 작성한다. 구현 로직이 없는데 어떻게 테스트가 가능할까 의구심이 들 수 있지만 여기서 테스트를 먼저 작성한다는 의미는 테스트 코드를 먼저 작성하고 그 테스트 코드를 통과시키기 위한 개발을 한다는 의미이다.
TDD를 수행함으로써 우리는 다음과 같은 이점을 얻을 수 있게 된다.
1. 필요한 부분까지만 기능 구현
TDD는 테스트 코드를 작성하고 그 코드를 통과시키기 위한 개발을 수행한다. 즉, 작성된 부분까지만 통과할 만큼만 구현을 진행하게 되는 것이다. 아직 추가하지 않은 테스트를 고려해서 구현하지 않기 때문에 과도한 기능 개발을 사전에 막을 수 있으며 각 메서드별 책임을 분리하기에도 훨씬 수월하다.
2. 점진적 리팩토링 사이클을 통해서 더 좋은 코드 도출
TDD는 테스트 코드 작성 -> 기능 구현 -> 리팩토링 과정을 거치게 된다. 마지막 리팩토링 과정을 거처가면서 중복된 코드들이 제거되고, 복잡한 코드들은 조금 더 깔끔하게 정리가 되게 된다.
3. 놓치는 테스트 코드를 방지
프로덕션 코드를 개발이 끝난 후, 테스트 코드를 작성하게 되면 중간에 놓치는 테스트 케이스가 빈번하게 발생한다. 테스트 코드는 성공, 실패 케이스 둘 다 작성 해야하기 때문에 기능 개발 후 테스트 코드를 작성하게 되면 그 수가 매우 많아서 개발자가 실수로 놓치는 테스트 상황이 발생할 수 있게 된다. TDD는 작은 단위부터 성공, 실패 케이스를 단계적으로 작성하기 때문에 놓치는 케이스 없이 기능 개발이 가능하다.
이러한 이점들을 통해서 TDD를 적용하는 것이 기존의 프로덕션 코드 작성 후 테스트 코드를 작성하는 것보다 훨씬 더 효율적으로 개발을 할 수 있게 도와준다는 것을 미션을 수행하면서 느낄 수 있었다.
레벨 1의 미션을 하면서 정리된 TDD 작성 플로우
1. 먼저 요구사항을 바탕으로 대략적인 기능 목록을 정리한다.
2. TDD로 가장 작고 쉽게 구현이 가능할 것 같은 도메인 하나를 선정한다.
3. TDD로 구현하다가 테스트하기가 어렵고 복잡한 경우 작성된 코드를 제거한다.
4. 더 쉽고 작은 단위의 도메인을 찾아 TDD로 구현을 시작한다.
5. TDD로 구현이 가능하면서, 개발자가 만족하는 수준의 도메인 객체라 생각하면 리팩토링 및 추가 구현을 진행한다.
6. 해당 도메인의 리팩토링이 적절하게 완료되면 1차 커밋을 진행한다.
7. 앞에서 구현한 객체를 활용하는 더 큰 단위의 기능을 정하고, 해당 도메인을 찾아 구현을 시작한다.
8. 이후 앞선 과정을 반복한다.
해당 플로우가 정답이라 할 순 없지만 앞으로도 있을 미션들을 수행해 보면서 조금씩 다듬어갈 생각이다.
더 좋은 테스트 코드 작성하기
TDD를 수행함에 있어서 결국 테스트 코드는 빠질 수 없는 부분이다. 그렇다면 이런 테스트 코드를 잘 작성해야 할 것인데 어떻게 잘 작성할 것인가에 대한 고민이 들 수 밖에 없다. 일단 "왜 테스트 코드를 작성해야 할까"에 대한 이유를 알면 좀 더 잘 작성하는 방법을 결론 지을 수 있지 않을까해서 작성하는 그 이유를 생각해보았다.
1. 기능의 명세
제 3자가 테스트 문서만 보더라도 프로덕션 코드의 기능을 유추할 수 있어야 한다. 테스트 코드는 개발자 작성한 프로덕션 코드가 잘 동작하는지 확인하는 역할도 수행하지만 제 3자(협업 하는 사람, 프로젝트에 투입된 인원)들이 어떤 기능들이 있는지, 이러한 기능들을 모여 최종적으로 사용자에게 어떤 기능을 제공하는지 유추하는 역할을 수행할 수 있다.
2. 보호받는 코드
새로운 기능은 반드시 추가될 수 밖에 없다. 이때 새로 추가된 기능으로 인해 기존 기능에 문제가 생기는 상황이 발생할 수 있다. 테스트 코드는 이러한 문제를 사전에 방지 할 수 있다.
3. 과감한 리팩토링
시간이 지나거나 상황에 따라 작성된 코드에 리팩토링이 필요할 경우가 반드시 발생한다. 이때 테스트 코드가 작성되어 있지 않을 경우 기존 잘 동작하는 코드를 변경하는 것이 개발자에게 부담으로 작용될 것이다. 테스트 코드는 이러한 부담감을 덜어주는 안전장치의 역할을 수행할 수 있다. 변경된 프로덕션 코드를 기존의 테스트 코드를 통해 검증함으로써 잘 리팩토링 되었는지를 확인할 수 있다.
4. 편리함
테스트 코드가 없다면 잘 동작하는지 어떻게 확인해야 할까? 개발자가 일일히 프로그램을 실행하면서 확인을 해야 할 것이다. 또한 기능을 수행하는데 여러 시나리오가 있다면 그 시나리오에 맞게 값을 일일히 넣어주어서 확인을 해야 할 것이다. 테스트 코드는 이러한 문제들을 해결해 줄 수 있다.
이러한 이유들을 통해서 이때까지 테스트 코드를 작성해 왔다고 생각한다. 그렇다면 이러한 조건을 만족하기 위해서 어떻게 하면 더 좋은 테스트 코드를 작성할 수 있을까?
해당 방법이 정확하지 않을 수 있지만 현재는 다음과 같은 규칙들을 통해서 테스트 코드를 작성하고 있다.
1. @DisplayName을 통해서 테스트에 기능의 명세를 담자
@DisplayName을 활용하여 해당 테스트 코드를 통해 전달하고 싶은 기능을 확실하게 명세하자.
기능의 명세를 통해서 테스트가 성공, 실패 했을 경우 개발자가 코드를 굳이 보지 않더라도 어떤 부분에서 성공했는지, 실패했는지를 확실하게 알 수 있게 되는 효과를 얻을 수가 있다.
2. 전체적인 설계(기능)을 테스트 하라
구현을 테스트 하는 것이 아닌 하나의 기능을 테스트 하는 것이 중요하다.
가령 `자동차를 생성할 수 있다`라는 요구사항이 있다면 해당 로직의 구현을 테스트 하는 것이 아닌 `자동차는 이름을 가져야 한다`, `자동차는 중복된 이름을 가질 수 있다`, '자동차의 이름의 길이는 1 ~ 5 사이여야 한다` 와 같은 설계를 테스트 해야한다.
그 이유는 해당 로직의 상세한 구현을 테스트 하게 될 경우 추후 리팩토링 상황이 발생하였을 경우, 해당 테스트 로직까지 전부 변경되어야 하기 때문이다.
이러한 관점으로 접근했을때 private 메서드 역시 테스트를 할 필요가 없다는 결론이 나오게 된다. 전체적인 설계(기능)을 테스트하는 것에 중점을 두어야 하기 때문에 private 테스트를 reflection등을 통해서 수행할 필요가 없으며 간접적으로 테스트가 이루어진다는 결론을 얻을 수 있다.
3. 독립적인 테스트를 작성하라
테스트는 서로 독립적이여야 한다.
만약 테스트가 서로 연결되어 있고, 순서를 보장해야 한다면 상황에 따라 문제가 발생할 가능성이 크고 테스트가 매번 통과한다는 보장을 하기 어렵다. 항상 테스트 작성시에 각 테스트 코드는 연결성이 없어야 함을 인지하고 작성하자.
TDD, 테스트 코드를 작성하는 방법은 각 개발자 마다 다양하고 해당 글에서 소개한 방법보다 더 좋은 방법이 있을 것이라 생각한다. 다만 본인이 개발을 하면서 작성 방법을 스스로 깨닫고, 본인만의 스타일로 채화시키고, 팀과 맞춰나가는 과정이 가장 중요하다고 생각을 한다.
'Woowacourse > 레벨1' 카테고리의 다른 글
[레벨 1 회고] 전략패턴을 사용해보며 (0) | 2022.04.18 |
---|---|
[레벨 1 회고] 정적 팩터리 메서드 / 생성자와 부생성자 (0) | 2022.04.17 |
[레벨 1 학습] git 정복하기 (0) | 2022.02.19 |
댓글