kok202
구글의 소프트웨어 엔지니어링 (4/5)

2021. 9. 25. 04:42[공부] 독서/구글의 소프트웨어 엔지니어링

테스트

테스트 관련 챕터를 읽기 전 업무를 하면서 느꼈던 몇 가지 궁금증들을 해소하고 싶었습니다. 간략히 몇 개를 정리하면 아래와 같습니다.

  • 대규모 트래픽이 있는 환경을 테스트하려면 어떻게 테스트해야 할까? 더미 데이터를 만드는 것도 다양성 면에서 한계가 있을 텐데?
  • 테스트 케이스의 결과가 관측 불가능하다면 어떻게 테스트해야 할까? 예를 들어 시스템 외부의 이메일에 메일이 제대로 전송됐는지를 어떻게 판단할 수 있을까?
  • MSA 환경에서 작업하는 경우 내부 시스템끼리 빈번히 통신을 하게 되는데, 이런 경우는 어떻게 할까? faking이나 stubbing 만으로 진짜 다 처리가 가능할까?

이렇게 정리하고 책을 읽기 시작했습니다. 일부는 긍정적인 답변을 얻을 수 있었고, 일부는 아직도 궁금한체 남아 있었습니다.

 

테스트 문화를 전파하는 트로이 목마

구글도 테스트가 없던 시절이 있었다고 합니다. 그런 구글이 회사에 테스트 문화를 전파하기 위해 했던 다양한 노력들이 있었는데, 그중 가장 기억에 남는 것은 이 트로이 목마 전략이었습니다.

 

구글의 초기 엔지니어링 인력 중 상당수는 테스트를 거부했습니다. ... 중략... 하지만 2005년부터는 구글의 신입사원들을 위한 오리엔테이션 시간에 자동화된 테스트의 가치를 설명하는 1시간 정도의 수업이 포함되기 시작했습니다. 이 강의에서는 테스트가 주는 장점들을 소개했으며 생산성 향상, 문서 개선, 리팩토링 등 다양한 내용을 다루었습니다. 또한 테스트를 잘하는 법도 다루었습니다. ... 중요한 점은, 이 수업 시간에 모든 아이디어들이 회사의 표준 관행인 것처럼 이야기했었다는 것입니다. 신규 입사자는 자신들이 트로이 목마처럼 이용돼서 팀에 전파될 것이라는 사실을 전혀 몰랐습니다. 신규 입사자가 오리엔테이션을 거치고 팀에 합류하면서, 그들은 테스트를 작성하기 시작했습니다. 그리고 그렇지 않은 팀원들에게 왜 테스트를 작성하지 않냐고 물었습니다. ...

 


flaky 테스터와 brittle 테스트

테스트를 할 때 테스트가 깨져서는 안되는데 깨지는 케이스가 몇개 있다고 합니다. 이를 취약한 테스트라고 부르는데, 여기안에서도 flaky 테스트와 brittle 테스트로 구분합니다. 사전적인 의미로는 둘다 취약한, 부실한 이라는 뜻이지만 약간의 차이가 존재합니다. brittle 테스트는 관련 없는 변경에도 영향을 받아 깨지는 테스트를 말합니다. flaky 테스트는 테스트가 비결정적인 것을 의미합니다. 즉, 변경사항이 없는데도 불구하고 어쩔 때는 실패하고 어쩔 때는 성공하는 테스트를 의미합니다.

 


모노레포와 TAP(Test Automation Platform) train

앞선 포스팅에서 구글은 모노레포 방식으로 레포지토리를 관리한다고 하였습니다. 그리고 모노레포의 크기는 86TB라고 하였습니다. 이런 레포지토리에서 테스트 수백만 개를 돌리려 한다면 정말 긴 시간이 필요할겁니다. 그런데 만약 모든 코드 변경에 모든 테스트를 돌린다면 어떻게 될까요? 어마어마한 컴퓨팅 자원을 낭비하게 될 겁니다.

 

여기서 구글이 사용하는 테스트 전략들이 몇 개 소개됩니다. 가장 기본적으로는 코드 변경에 영향을 받는 테스트만 테스트를 돌린다는 전략을 사용합니다. 하지만 만약 대규모 리팩토링이 있었거나, 가장 기본이 되는 라이브러리에 변경이 있어서 사실상 모든 테스트를 돌려야한다면 어떻게할까요? 그리고 하루 사이에 대규모 변경(LSC: Large scale change)이 여러번 발생해서, 머지를 8번해야한다면 모든 테스트를 8번 돌려야 할까요?

 

이때 소개되는 전략이 TAP train이라는 전략인데, 여기서 말하는 train 은 훈련이라기보다 열차를 의미합니다. TAP train 방식은 아래와 같은 순서로 진행됩니다.

  1. 열차에 탑승한 각 변경을 테스트하기 위해, 랜덤하게 1,000개 테스트 표본을 선택하고 실행합니다.
  2. 1,000개의 테스트를 통과한 모든 변경 사항을 취합합니다. 그리고 이 모든 변경사항을 묶어 uber-change를 하나 만듭니다.
  3. 변경에 직접 영향을 받는 모든 테스트들을 실행합니다. 충분히 큰 LSC라면, 구글 레포지토리의 모든 테스트를 실행할 수 있습니다. 이 프로세스는 완료하는 데 총 6시간 이상이 걸릴 수도 있다고합니다.
  4. flaky 하지 않은 테스트에서 실패할 경우, 해당 테스트를 개별적인 LSC 변경으로 다시 실행시켜서, 어떤 변경으로 인해 문제가 발생했는지 확인합니다.
  5. TAP은 열차에 탑승한 각 변경 사항에 대한 보고서를 생성합니다. 보고서는 통과와 실패 대상을 모두 설명합니다. 그리고 LSC가 안전히 진행될 수 있다는 증거로 사용할 수 있습니다.

 


테스트 더블

외부 서버와 통신해야 하거나 응답을 데이터베이스에 저장하는 함수에 대한 테스트를 작성한다 할 때, 테스트 더블이라는 전략을 사용할 수 있습니다. 실제 구현체를 대신할 함수나 객체를 사용하는 것입니다. 책에서 비유 들기로는 영화에서 배우를 대신할 스턴트 맨과 유사하다고 합니다. 테스트 더블은 종종 mocking 을 의미하지만 테스트 더블은 좀 더 큰 개념이며 mocking은 구체적인 사례 중 하나라고 합니다.

테스트 더블에는 크게 3개의 전략이 있습니다.

  • Faking : 테스트를 위해 같은 인터페이스를 가진 가짜 구현체를 사용합니다.
  • Stubbing : 테스트를 위해 일부 함수에 어떤 매개변수가 주어지면, 어떤 값을 반환하도록 지정해서 사용합니다.
  • Interaction testing: 어떤 함수가 호출되었는지를 검사합니다

더불어 가능하면 Mocking을 남용하거나 오용하지 말라고 합니다. 잘못 사용하면 테스트가 불분명해지고, brittle 해지기 쉬우며, 효과적이지 못한 테스트가 만들어질 수 있기 때문에, 오히려 생산성이 크게 저하될 수 있다 합니다. 책에 따르면 실제 구현체에 의존하는 게 최고로 좋으며, Fake, Stubbing 순서로 사용하는 것이 좋다고 합니다.

 


Record/Replay 전략

현재 회사에서 작업 중인 백엔드는 특성상 외부 시스템과의 통신이 굉장히 많습니다. 이런 상황을 격리된 환경에서 테스트하려한다면 더블 객체가 필요한데, 일일히 더블 객체를 만드는 것은 너무나 힘들겁니다. 그리고 만에하나 더블 객체를 모두 만들었다 하더라도, 외부 시스템의 인터페이스이 일부 바뀐다면 이에 맞춰 수정해주어야하니 상황은 더 복잡해질 겁니다.

 

책을 읽던 도중 이러한 상황에서 쓸만해보이는 전략을 발견했습니다. Record/replay 전략입니다. 

 

Record/replay 는 실환경의 백엔드의 요청과 응답을 기록합니다. 그리고 이를 캐시합니다. 캐시된 데이터는 격리된 테스트 환경에서 사용할 수 있도록 합니다. 그리고 테스트 환경에서 같은 요청이 들어올 때 이를 replay 하도록 합니다. Record/replay 전략은 테스트의 불안정성을 줄이는 강력한 도구이기도 하지만, 테스트가 취약해진다는 단점이 존재한다고 합니다.


Chained 테스트

내부 시스템들이 긴밀한 협력을 해야 할 때, 이러한 시스템을 테스트할 수 있는 한 가지 방법은 chained 테스트입니다. chained 테스트는 구체적인 실행에 집중하는 게 아니라, 전체 시나리오를 표현할 수 있는 여러 개의 작은 통합 테스트를 만듭니다. 그리고 테스트 출력을 데이터 저장소에 저장하고, 이 값이 다른 테스트에 대한 입력으로 사용하게 합니다.

 


테스트할 때는 DRY 한 것보다 DAMP 한 것이 낫다

시스템 설계 원칙은 SOLID는 많이 들어봤어도 DRY와 DAMP라는 용어 처음 접해본 듯 합니다. 이외에도 다양한 원칙들이 있길래 찾아보고 정리해 봤습니다.

  • Cargo cult programming : 이해는 하지 않고 그냥 무작정 따라서 프로그래밍하는 것.
  • DRY - Don't Repeat Yourself : 똑같은 일을 두 번 하지 마라.
  • KISS - Keep it simple, stupid : 단순하게 하라.
  • YANGI - You Ain't Gonna Need it : 필요할 때 해라.
  • DAMP - Descriptive And Meaningful Phrases: 서술적이고 의미 있으며 구어적으로 작성해라.

그리고 테스트 코드에 한해서는 DAMP 원칙이 더 괜찮다고 합니다. 서술적인 내용이 중복되더라도 의사를 더 명확히 전달할 수 있다면 이를 허용하도록 합니다.

 

* Damp는 축축한이란 뜻이고 Dry는 건조한이란 뜻인데, 약간 억지로 맞춘 게 아닌가 생각합니다.