첫번째 북클럽 모임의 책은 '구글 엔지니어는 이렇게 일한다' 이다.
양이 상당하기 때문에, 각각 두개의 장을 골라 읽어 오기로 했다.
나는 그 중, 테스트 파트가 가장 궁금했어서 11장, 12장, 13장을 읽고 기억에 남는 것들을 정리하려고 한다. (13장은 읽었지만.. 양이 방대할것같아서 생략!)
11장에서는 구글 웹서버 제품을 예를 들어 테스트에 대해 언급하기 시작한다.
구글도 초기에 테스트를 작성하지 않았다. 하지만, 2005년도에 급격하게 소프트웨어가 복잡해지면서 릴리스때마다 버그 투성이였고, 다음 버전을 출시하기 위한 기간이 매우 길어졌다고 한다.
이를 해결하기 위해 테스트를 도입하였고 1년 후에는 긴급 배포건이 절반으로 줄어들었다고 한다. 현재 해당 제품은.. 거의 매일 릴리즈 되고 있다고 한다.
매우 공감하는 바였다. 나 또한 2023년 소프트웨어 마에스트로에서 실제 프로덕트를 개발해보면서, 수많은 기능을 추가하면서 버그가 쏟아져 나왔었다. 이 후부터는 테스트 코드 없는 삶은 상상할 수 없게 되었다..
크기와 범위이다.
크기는 테스트 케이스 하나를 실행하는데 필요한 자원 (메모리, 프로세스, 시간)을 의미한다.
범위는 검증하려는 특정 코드를 의미한다.
크기가 작은 테스트는 하나의 프로세스내에서 실행되는 테스트를 의미한다.
가장 엄격하지만, 가장 빠르고, 가장 결정적인 요소의 테스트이다. 구글에서는 항상 작은 테스트를 만들라고 권고한다.
중간 크기의 테스트는 하나의 기기에서 이뤄지는 테스트를 의미한다.
이 말이 조금은 애매하게 들릴지 모르겠는데, 여러 프로세스와 스레드 그리고 localhost 네트워트 호출과 같은 blocking 요소도 허용한다.
하지만 절대 외부와의 네트워크 호출은 허용하지 않는다. 외부는 '하나의 기기' 에서 벗어나는 영역이기 때문이다.
그렇다면 작은 테스트보다 어떤점을 주의해야할까? 이를 북클럽 모임의 심심풀이 질문으로 던져본다! 그리고 비결정적인 테스트가 의미하는 게 무엇일까?
큰 크기의 테스트는 테스트 대상과 여러 외부 시스템과의 호출을 허용한다.
E2E테스트와 동일하다고 생각하면 좋을 것 같다.
현재 재직중인 회사에서는 MSA환경에서 E2E자동화 테스트 툴이 있다. 하지만 참.. 이 큰 크기의 테스트는 비결정적으로 만드는 요소가 너무너무 많다... 어쩔 수 없는 한계인걸까??
단위 테스트 == 좁은 범위
통합 테스트 == 중간 범위
E2E 테스트 == 넓은 범위
이렇게 정의한다.
앞의 크기와 조금 혼동될 수 있는 부분이 있기는 하다.
혼동을 막기 위해 추가 설명을 하면, 메서드 하나를 검사하는 좁은 범위 테스트라도 중간크기가 될 수 있다고 책에서 말한다. 예를들어 웹 프레임워크는 HTML + Javascript 가 묶여서 배포가 되어, 하나의 좁은 범위의 테스트를 하더라도 웹브라우저를 구동시켜야만 하는 상황이 존재할 수 있다.
또 반대로, 중간 크기의 테스트를 진행했는데, DB나 네트워크를 모두다 Stubbing한다면..? 그럴 경우는 좁은 범위의 테스트라고 할 수 있을 것이다.
그 유명한 구글도 개발자들이 테스트를 당연히 생각할 수 있게 다방면으로 노력했다고 한다.
나는 나의 회사에서 그럴 수 있을까?
E2E테스트의 복잡성 말고, 작고 좁은 범위의 테스트를 더욱 늘렸으면 좋겠다..
솔직히 학생 ~ 주니어 레벨 단계에서는 단위 테스트가 가장 중요할 것 같다. 학생 때는 복잡한 시스템을 다루지도 않을 뿐더러, 단위 테스트가 모든 테스트의 근원이 되기 때문이다. 그렇다면 단위 테스트를 작성할 때 유의해야할 점은 무엇일까에 대해 궁금해서 더욱 열심히 읽었다.
깨지기 쉬운 테스트는 치명적이다. 이 책에서 말하는 '크기'도 작고, '범위'도 좁은 단위 테스트에서 깨지기 쉬운 테스트를 작성하는 것은 정말 치명적일 수 있다.
1%의 단위테스트가 실패한다고 가정하면 10000개에서 100개의 테스트가 실패한다는 뜻이니, 엔지니어 입장에서는 100개의 포인트가 디버깅 포인트가 되어버린다.
그렇기 때문에 깨지지 않는 테스트를 작성해야하는 것이 필수이다
다음과 같은 작업을 실행할 때 절대로 테스트가 깨지지 말아야한다.
다만, 한가지 "행위/기능 변경" 의 요소에서는 테스트가 깨질 수 밖에 없고, 우리가 의도적으로 변경하기 때문에 테스트를 수정해야할 것이다.
Access Modifier를 지원하는 언어들을 사용하면 공개 API / 비공개 API를 따로 작성한다.
보통 클라이언트가 호출하는 코드들을 public으로, 내부적으로 메서드를 추출하거나 코드를 작성하는 개발자가 직접 호출할 수 없게끔 만들어 놓은 private으로 작성하는데 우리는 이러한 의도를 이해하고, 테스트코드 작성시 실제로 호출되는 메서드와 클래스만 테스트 해야할 것이다.
즉, 테스트코드는 실제 서비스 사용자가 호출하는 시나리오를 모방해야 한다는 의미이다.
물론 public으로 open 한다 하더라도, 이게 꼭 클라이언트가 호출하는 코드라는 것을 의미하는 것은 아니다. 이러한 부분은 어디까지가 테스트 해야할 범위인지 판단하는 '경험'으로 커버해야하는 영역인 것 같다.
상호작용을 테스트하지 말고, 결과 값 (상태) 을 테스트 하자.
상호작용 테스트는 쉽게 말하면 "어떤 상황에서 이 함수가 실행 됐니?" 를 의미하는데, 이는 너무 비결정적이다. 내부적인 버그로 A함수가 실행됐지만, A함수 이후에 B함수를 잘못 넣어서 다른 결과값이 도출될 수 있기 때문이다.
예륻들어 계산기를 테스트하기 위해 계산기 인스턴스를 생성하려고 하는데,
calculator = new Calculator(내림기법적용, 소수점몇쨰자리까지표출, "taehoon");
int result = calculator.calculate(calculationTest());
assertThat(result).isEqualTo(5);
이런 정보가 들어가는 것은 좋지 않다는 이야기이다. 테스트를 읽는 사람에게 혼동을 준다. 그냥 계산기를 만드는 것. 그 행위 자체에 의미를 두고, 계산 하는 대상, 계산 결과 값을 명확하게 드러내야한다.
위의 테스트를 우리는 어떻게 바꿀 수 있을까? 다 같이 고민해보자!
만일 테스트메서드의 이름을 지을때 And이 들어간다면 어떻게 해야할까?
String nextPage = nav.goNext(); assertThat(nav.getUrl()).isEqualTo("/taehoon + nextPage);
10개월동안 MSA 서비스를 처음 경험하면서 테스트에 대한 참 많은 생각이 들었었다.
테스트는 유지보수 대상이어야만 한다.
기존의 Unit 테스트코드는 돌아가지 않는 경우가 다반사였다. 안되는 테스트 코드는 주석처리하고 돌리며, 심지어 이제는 테스트코드를 더 이상 작성하지 않는 문화가 자리잡힌 서비스들이 많다. E2E테스트 툴이 있기 때문일지는 몰라도.. 너무 고착화 되어버린 경우가 많다.
하지만, 생각해보면 MSA서비스라는 성격 때문에 Mocking하는 요소가 매우 많다보니 효용이 없을것이라고 생각할 수도 있다.
하지만, 이는 설계의 문제이기도 하지 않을까? Mocking이 많다는 것은 결국 내부적으로 강하게 의존성이 있을 수도 있고, 관심사 분리가 명확이 이루어지지 않는다는 것을 의미할 것이다.
그렇기에, 우리는 꾸준히 리팩토링 하고 적절한 Mocking을 통해 MSA환경에서도 단위테스트를 작성해야 함이 장기적으론 마땅하다. 서비스 안정성의 측면이든, 개발자의 커뮤니케이션 측면이든 말이다.
Bypass 성격의 서비스는 어떻게 테스트 해야 할까?
최근 Bypass성격을 가지고 있는 서비스를 맡게 되며 테스트의 필요성에 고려해보았다.
나는 이 경우에 테스트코드를 단 1도 작성하지 않았다. Bypass한다는 이야기는 서비스 로직이 없고 외부 시스템과 내부 시스템을 이어주는 징검다리 역할이다. 이 경우에는 E2E테스트를 도입하여 검증하는 방법이 베스트가 아닐까? 다른 사람의 생각도 궁금하다.
❗️실제 Flow: 외부 시스템 -> 내부 G/W -> Bypass서비스 -> 다른 서비스
❗️E2E 테스트 방법: 내부 G/W로 외부 시스템의 요청값을 호출 -> Bypass서비스 -> 다른 서비스