-
수년 전 공부했던 TDD의 기억Etc 2021. 7. 14. 23:29
수년전에 공부했었던 TDD..
다시 어딘가 찾아보지 않고 머릿속에 있는 기억만을 한번 꺼내보려 합니다.
머릿속에 있는 기억을 꺼내서 확실한 부분인지 검증하거나 수정하지 않고 그대로 한번 적어보겠습니다.
많은 오류가 있을 수 있으니 재미삼아 봐주시면 감사하겠습니다.
그리고 다음 게시글에서는 확실한 내용만을 정리해서 올려보겠습니다.
Given When Then 템플릿이 먼저 생각 나네요.
Given - 상황이 주어졌을 때
When - 어떤 무언가의 행동을 한다.
Then - 그랬을 때의 예상 결과는 이렇다.
Given 에는 input과 output의 기대값을 적었었고
When 은 실제 Feature 코드를 호출했고 결과 까지 받았습니다.
Then에서는 When 에서 발생된 결과를 Matcher를 사용해서 검증했던 기억이 납니다.
Matcher에는 많이 쓰는 hamcrest, assertj가 있는데 수년전 당시 구글 트렌드로 검색하고 블로깅을 했을 때 느꼈던 것은
assertj가 이제는 좋다고들 하지만 구글 트렌드에서는 아직도 hamcrest를 많이 쓰고 있었습니다.
그 이유는 hamcrest에 익숙한 개발자들이 굳이 assertj를 쓸 이유가 없기 때문이라는 내용을 봤던 기억이 나고
만약 matcher 라이브러리를 처음 쓰기 시작하는 개발자라면 코드어시스턴스가 되는 assertj를 써서 생산성을 높이는게 좋을것 같다 라고 이해했었습니다.
다음..
naming 을 하자면 addTest 라고 간단히 지을 수 있지만
shouldBeAbleToAddTwoNumbers 라고 지을 수도 있겠습니다.
그리고 Test라는 걸 명시하기 위해 Test 까지 접미사로 붙여 shouldBeAbleToAddTwoNumbersTest 라는 이름으로 지을 수도 있을 겁니다.
그리고 Given When Then 템플릿을 공부하면서 BDD에 대한 키워드에 대해서도 읽어봤던 기억이 납니다.
행동..비헤이비어. 라는 정도 생각나네요.
그리고 Given When Then 템플릿이 마틴파울러의 블로그에 있던 기억도 나고 마이크로소프트에서 TDD에 대한 정리가 되어 있던 내용을 봤던 기억도 납니다.
그리고 Mock 객체 뿐만 아니라 Spy..., 또 하나 있었는데 이런 것도 생각이 납니다.
단위테스트 코드의 이름을 짓는 방법은 보통 실제 feature에 대한 이름 뒤에 Test를 붙여서 지을 수 있지만,
should 로 시작해서 ~해야 한다. ~할 수 있다. 라는 이름으로 지어야 한다고 했던 기억이 납니다.
should 말고 can으로 시작할 수도 있던것 같은데 should로 naming 했던 기억이 납니다.
하지만 should로 지었을 때 영어를 능숙히 하지 않는 이상 결국 번역기를 돌리지 않으면 바로 알아보지 못하는 제약이 있었습니다.
우수한 팀들은 어떻게 할까요? 궁금했는데, 저는 JUnit5를 쓴다면 @Display 에 한국말로 잘 써놓고 테스트 코드 메서드의 이름은 [Feature]Test 로 하면 어떨까 라는 생각을 해봤습니다.
하지만 그렇게 했을 때 추측이지만 발생할 만한 이슈 중에 하나 코드커버리지 Jacoco 같은 라이브러리를 썼을 때 호환이 잘 되는지 모르겠네요.
Jupiter(junit5)의 @Display와 호환되서 그 부분까지 알아보기 쉽게 Report를 만들어 줄지는 모르겠습니다.
만약에 그런 부분이 기대 이상으로 호환되지 않는다면 [Feature]Test 라는 이름보다 should로 시작하는 이름이 더 좋지 않을까 라는 생각도 하게 됩니다.
그리고 TDD는 실패 -> 성공 -> 리팩토링 그린 레드 블루의 순환이라 할까요 반복이라 할까요.
그리고 테스트가 설계를 이끌어 간다라는 말도 기억 납니다.
TDD.. 테스트가 주도하는 개발을 하게 될 경우 FM으로 해본다면 순서는 이렇습니다.
현재 테스트하려는 기능과 요구사항이 파악된 상태라 가정했을 때
1. 우선 @Test 를 붙여서 테스트 코드를 만든다. 아무것도 없는 껍대기.
2. Test실행. Test는 성공한다.
3. 테스트하고자 하는 클래스를 인스턴스화 한다. 하지만 실제 클래스는 코드조차 없다. 내 머릿속에 있는 클래스일 뿐. 컴파일 오류. 레드!!
4. 컴파일 오류를 없애기 위해 테스트하고자 하는 클래스를 실제 코딩한다. 아무것도 없는 껍대기에 불과하다. 컴파일은 성공하고 의미 없지만 테스트를 돌렸을 때 테스트는 성공한다.
5. 테스트 하고자 하는 클래스의 메서드를 호출해본다. 메서드는 만들어져 있지 않다. 컴파일 오류다. 레드!!
6. 컴파일 오류를 없애기 위해 메서드를 만든다. 메서드는 이름만 있는 단지 컴파일 오류를 없애기 위한 내용은 없는 코드이다. 컴파일 오류는 없어지고 의미없지만 테스트를 실행하면 성공한다.
7. 이제 클래스도 있고 메서드도 있다. 이제는 해당 메서드의 기능에 대한 케이스에 대해 생각하고 input과 output 을 하나 정해본다. 그리고 메서드에 input값을 전달하고 실행했을때의 결과 값을 검증해본다. 메서의 로직은 단 하나도 코딩하지 않았기 때문에 메서드를 호출해 봤자 null 아니면 0 같은 값이 리턴될 뿐 테스트는 실패한다.
8. 테스트를 성공시키기 위해 내가 원하는 결과 값을 메서드의 리턴값에 하드 코딩한다. 그리고 테스트를 실행한다. 테스트는 성공한다. 하지만 아무런 로직이 없고 다른 케이스를 하나 만들어서 테스트하면 테스트는 실패한다.
9. 두가지 테스트를 모두 성공시키기 위해 기능에 대한 로직을 구현한다. 정확히 구현했다 치고 테스트를 실행하면 테스트는 성공한다.
10. 코드가 너무 지저분하다. 테스트 코드는 Given When Then 템플릿을 써서 리팩토링 한다. 그리고 테스트를 실행한다. 테스트는 성공한다. 만약 실패하면 원인을 찾아 리팩토링 하고 성공할 때까지 반복한다.
11. 발생 가능한 테스트 케이스를 더 추가한다. 테스트를 실행한다. 테스트는 실패한다.
12. 다시 리팩토링 한다. 테스트를 실행한다. 모든 테스트가 성공한다.
13. 너무 많은 케이스를 하나의 테스트코드에 넣다 보니 for문도 들어가고 지저분하다. 라이브러리의 힘을 빌려본다. junit5(Jupiter)의 @parameterizedTest 와 @MethodSource 를 사용해서 지저분하게 나열되어 있던 input, output(기대값)을 별도 메서드로 분리시킨다. 다시 테스트를 실행한다. 테스트는 성공한다.
...
위에 적은 13번까지. 이해가 안가게 적은 내용도 있지만 결국 실패하는 테스트를 성공할 때까지 그리고 깔끔한 코드가 될때까지 리팩토링 테스트 실행 리팩토링 테스트 실행을 반복하는 것입니다.
그리고 너무 세부사항까지 테스트하게 되면 테스트 코드가 너무 많은 프로덕션 코드를 의존하게 되어 운영 및 유지보수의 비용이 오히려 더 증가하는 불상사가 발생된다고 알고 있습니다.
그래서 어떤 곳에서는 interface만을 테스트해야 한다고도 합니다.
그리고 상황에 맞게 세부사항이지만 정말로 테스트해야 할때는 세부사항마져도 테스트 하는데 이 테스트를 하기 위해서는 세부사항의 접근제한자가 private인 경우 reflection code를 쓸 수도 있지만 이 경우 위험하기 때문에 라이브러리의 힘을 빌려 쓰는것이 좋고, 또한 테스트를 위해 private이 아닌 protected로 접근제한자를 두는 경우도 있다 들었습니다.
위험하다 한 이유는 접근제한자의 역할 자체가 있는데 private 한 메서드에 접근하기 위해 access 를 강제로 true로 줘서 테스트 했었던 기억이 납니다. 결국 private이라 쓰고 private 하지 않게 개발한 것과 다름 없다 생각이 드네요.
그런데 라이브러리를 써서 private에 접근할때도 ... 라이브러리 내부적으로는 reflection 코드를 쓰는게 아닐까 의심스럽긴 하지만 직접 분석해 보지 않았으니 당장에 할말은 없네요.
여러가지 내용을 종합해 보면 결국 얼마나 실용적일 것인가 얼마나 Rule에 의존할 것인가 환경과 상황에 맞게 조율할 수 밖에 없다 생각을 하게 됐습니다. 엄청나게 많은 시간과 인력과 비용이 있다면 100퍼센트의 코드커버리지를 달성하는 것도 좋을것 같습니다. 하지만 그게 아니라면 주어진 자원에 맞게 커버리지를 낮춰야 하지 않을까도 생각해 봅니다.
아! 참 이건 수년전이 아닌 최근의 기억이긴 하지만..
최근 토스의 한 개발자분이 코드커버리지에 대한 내용을 유튜브에 올린 걸 봤는데 코드커버리지 100퍼센트 라는 목표로 테스트 코드를 작성하셨고 정말 100퍼센트의 코드 커버리지를 달성하였고, 또한 코드커버리지 100퍼센트의 테스트코드를 전체 실행할 경우 많은 시간이 소요되는데 이 부분에서 성능을 최적화(테스트 라이브러리를 커스터마이징 하셨다 한것 같은 기억이..) 하여 수초내에 100퍼센트의 코드 커버리지를 달성한 전체 테스트가 실행되는 어마어마한 일을 달성하셨다 했습니다.
우선! 여기까지 조금 적어봤습니다.
'Etc' 카테고리의 다른 글
AWS에서 Redis Master Slave 설정 (0) 2024.07.15 Slack Bot으로 Slack Channel에 메세지 보내기(Java) (0) 2023.04.02 rfc7231 - 4.2 Common Method Properties (일부-멱등성) (0) 2022.08.18 Visual Studio Code 에서 Git 사용법 (0) 2021.06.25 IETF와 RFC (0) 2021.06.02