본문 바로가기

회고

[끄적끄적] Effective software testing 적용기

Effective software testing

Effective software testing책의 내용을 프로젝트에 적용해가며 느꼈던 인사이트를 공유하는 회고성 글 입니다.

왜?

최근 TDD, ATDD와 같은 "테스트가 주도하는 개발" 방법론을 개발에 적용해 개발하고 있었다. 

이 방법론 덕분에 팀의 코드는 테스트하기 쉬워졌고, 높은 테스트 커버리지를 달성할 수 있었다. 심지어, 높은 커버리지(96퍼 였던거로 기억함)는 모든 기능이 정상적으로 동작함을 보장해주는듯 했다. 

 

이러한 생각이 깨지는데는 그렇게 긴 시간이 걸리지 않았다. 실제로 우리 팀은 96퍼나 되는 커버리지를 달성했음에도 mocking 처리된 데이터베이스로 인해 예상치못한 에러상황(보러가기) 을 겪은적이 있다.

 

우리의 테스트는 왜 실패했을까? 안정적인 서비스는 어떻게 구축할 수 있을까?

이러한 고민을 하던 찰나에, 우연히 "Effective Software Testing" 이라는 책을 알게 되었다. 이 책은 내 주언어인 자바로 작성되어 있고, 테스트를 철학적이 아닌 실용적으로 접근해서 크게 끌렸다. 무엇보다 가장 큰 매력은 최초 발행일이 2022년 4월 26으로 최근이라는 점 이였다.

 

어떻게?

이 책을 처음으로 필 당시, 다른 프로젝트를 막 시작하던 찰나였다. 나는 우리팀이 내가 이전에 한 프로젝트와 같은 문제를 겪지 않길 바랬는데, 그 방법을 이 책에서 찾고싶었다. 

 

또한, 이 책은 모든 부분이 테스트에 대한 인사이트의 연속이였다. 저자의 테스트에 대한 정수를 책에 모두 담고 있다고 생각이 들 만큼 많은 내용을 알려준다. 

 

그래서, 나는 책을 읽되, 당장 "우리팀의 테스팅 능력을 올릴 수 있는 인사이트를 얻는것" 에 집중하였고, 실제로 프로젝트에 적용하며 읽었다.

 

1. 커버리지 광신도에서 벗어나기

이전 프로젝트에서는 커버리지를 맹목적으로 따르고 있었고, 커버리지가 주는 안심은 우리를 버그로 이끌었다.

그렇다면, 테스트 커버리지는 측정할 필요가 없을까?

 

높은 테스트 커버리지는 소프트웨어가 완전히 검증되었음을 보장해주지 않는다. 하지만, 낮은 테스트 커버리지는 소프트웨어가 검증되지 않았음을 보장해준다.

 

즉, 우리는 커버리지에 휘둘리는것이 아닌 커버리지를 하나의 전략으로 사용해야했다.

 

이 책은 이러한 니즈를 충족시켜주는 여러 방법을 알려주었는데, 나는 그 중, 기존의 라인 커버리지룰에 더해 분기 커버리지룰을 도입했다. (이 외에도 코드를 더 안정적으로 만들 방법은 많았는데, 우리 팀의 리소스를 고려했을때, 최선의 선택은 분기 커버리지라고 생각했다.)

 

이런 커버리지 적용이 성공하기 위해선, 팀의 커버리지에 대한 리소스를 줄여야 한다고 생각했고, 이러한 부분을 자동화 하려고 노력했었다.

예를들어, 커버리지 측정을 위해, 불필요한 작업이 추가되고 이 스트레스가 커버리지가 주는 이점을 넘을때, 팀은 커버리지 측정을 버릴수도 있을것이라 생각했다.

 

sonarcloud에 jacoco를 연동해 커버리지 측정이 안되면 어디서, 왜 실패했는지 보여주고, gradle test 시에도 목표치가 넘지 않으면 test가 실패하도록 구성했다.

sonarcloud - PR에 message를 남겨준다

(이 당시 만들었던 coverage 측정 자동화 gradle 스크립트)

https://github.com/depromeet/na-lab-server/blob/main/gradle/jacoco.gradle

 

GitHub - depromeet/na-lab-server: 동료평가로 찾아가는 나의 커리어 브랜딩

동료평가로 찾아가는 나의 커리어 브랜딩. Contribute to depromeet/na-lab-server development by creating an account on GitHub.

github.com

 

2. 테스트의 본질 찾기

TDD 방법론의 이점을 설명할때, "TDD는 행위를 먼저 생각하게 해줘서 객체지향적인 코드를 만들게 도와줘요" 와 같은 의견을 많이 접했다. 나 또한, TDD를 적용한 주요 이유중 하나가 이러한 객체지향적인 이유였다.

 

하지만, 책에서는 조금 다르게 설명하는데, "TDD가 주는 이러한 장점은 모두 부수적인 것 이다. 테스트의 본질은 안정적인 소프트웨어를 만드는 것 이며 그 이상 그 이하도 아니다." (이러한 뉘앙스 였던것 같음) 

단순히, "TDD를 쓰면 좋다"는 생각에 빠져 테스트의 본질을 잊어버린채 개발을 해왔던거 아닐까? 라는 생각에 나를 돌아보게 되었고 가장 인상깊었던 구절이다. 

 

"어떻게" 라는 카테고리에 어울리지는 않지만, 이 인사이트를 얻게된 후 테스트를 대하는 마음이 달라진거 같아 작성해 봤다.

 

3. 효율적으로 테스트 하기

사진 출처 : https://www.headspin.io/blog/the-testing-pyramid-simplified-for-one-and-all

테스트는 효율적으로 작성되어야 한다. 그렇지 않으면, 테스트를 수행하는데 시간이 오래걸리고, 테스트를 작성하는 리소스도 많이 필요하게된다.

 

어떻게 하면 테스트를 효율적으로 작성하고 수행할 수 있을까? 답은 테스트 계층화에 있다.

위 테스트 피라미드 사진을 보면, 위로 갈수록 작성하기 어렵고, 수행하는데 많은 시간이 필요한 테스트라는것을 알 수 있다. 즉, 테스트는 위 계층에 따라서, 덜 중요한 로직은 피라미드 아래에 위치 시키고, 중요한 로직은 피라미드 위에 위치 시켜야 한다.

 

우리팀은 다음과 같이 계층을 나누고 테스트를 진행했다.

1. e2e : api를 사용하는데 필요한 가장 중요한 사용자 시나리오를 어떠한 Mocking없이 테스트한다.

(e2e 테스트에 대해서는 다른 포스팅에서 한번 더 다룰 예정이다.)

2. acceptance : 가장 먼저 작성되는 테스트로 외부 모듈(DB, Tomcat...)에 대한 Mocking을 허용한다.

3. integration : 하나의 모듈 전체 로직을 테스트하며, 모듈 밖의 요소는 Mocking 처리한다.

4. unit : 하나의 역할에 대해 테스트하며, 역할에 포함되지 않는 클래스는 Mocking 처리한다.

 

최종적으로 개발 프로세스는 다음과 같이 구성이 되었다.

인수테스트 -> 단위테스트 -> 코드 개발 -> 통합 테스트 -> e2e 테스트

아쉬웠던 점

1. 실제로 프로젝트에 적용하며 읽느라, 책을 꼼꼼하게 읽지 못한것 같다. 나중에 한번 더 읽어봐야할듯

2. 테스트를 적용하며, 복잡한 request class 생성을 자동화 하는 로직을 만들었는데, 책에서 이러한것을 해주는 라이브러리를 몇개 소개해준다. 다음에는 이것도 적용해봐야겠다.