
책을 읽게 된 배경
최근 코드리뷰를 받으면서 시간이 많이 지연되는 경험을 했다. 분명히 동료의 코멘트는 훌륭했다. 하지만 뭔가 마음속으로는 그러면 내 코드가 잘못된걸까? 어떻게 해야 좋은 코드 품질을 유지하고, 팀원이 준 코멘트에 수긍하며 모두가 코드에 대해 같은 생각을 가질 수 있을까? 고민하게 되었다.
또 엔지니어로서 내가 생각한 이 해결방법이 맞을까? 다른 사람들은 어떻게 할까? 더 좋은 방법이 있지 않을까 고민하곤 했다. 그리고 비효율적인 업무들을 마주칠때 더 효율적이고 생산성을 높일 수 있는 방법들이 궁금했다. 이 책을 통해 구글의 뛰어난 엔지니어들이 전하는 노하우를 배울 수 있을 것 같다.
책을 조금 읽은 지금은 위의 질문들에 답을 할 수 있을 것 같다.
- 내 코드가 잘못된걸까?
- 맞다. 동료와 충분한 논의 없이 작성된 코드는 잘못된 코드다. 동료가 의문을 제기한다면 작성된 코드와 코드리뷰의 목적과 방법에 대하여 공감대를 이룰 때까지 논의해야한다. 이 공감을 이루지 못하면 계속해서 충돌이 발생할 것이며 사이가 틀어질 것이다.
- 어떻게 해야 좋은 코드 품질을 유지할까?
- 자동화 테스트. 내가 작성했던 PR에는 테스트가 들어있지 않았다. 테스트가 없다면 동료는 이 코드가 어떤 버그를 발생시킬 지 알 수 없다. 좋은 테스트로 동료를 설득시키는 것이 PR 작성자의 임무이다. 테스트 코드만 보고도 이 코드가 어떻게 동작할지 쉽게 이해 할 수 있어야 한다.
- 어떻게 하면 팀원이 준 코멘트에 수긍하고 코드에 대해 같은 생각을 가질 수 있을까?
- 둘은 코드 리뷰의 목적과 방법에 대한 공감이 부족하다고 생각된다. 코드 리뷰의 목적과 방법을 다시 논의하여 합의에 이르면 서로 그 기준에 대해 수긍할 수 있고 같은 시선에서 코드를 평가할 수 있을 것이다.
- 또 서로 존중하고 신뢰하는 사이가 아닌걸로 보인다. 겸손, 존중, 신뢰를 베이스로하여 팀워크를 더 강화해야한다. 부드럽게 비평하는 방법과, 비평을 잘 받아들이는 방법을 배워야 한다. 솔직한 대화를 할 수 있도록 조직에 심리적 안전을 갖추는 것도 필요해보인다.
책 소개
이 책은 700여 페이지, 4만원 중후반대의 꽤 두꺼운 책이다. 그만큼 많은 것들을 배울 수 있는 책이라 생각한다. 두꺼운 책은 처음부터 정독하기보다 내가 흥미를 가진 부분부터 조금씩 갉아먹으며 영역을 확장해나가면 더 좋을 것 같다. 가장 궁금했던 코드 리뷰, 생산성, 협업 측면의 주제를 먼저 읽어보려고 한다.
참고로, 알라딘앱에서 1만7천원으로 중고 서적을 구매했다. 책상태가 최상이라 새책과 다름 없다. 나는 e북을 더 선호하긴 하는데, 책을 정리하면서 보기에는 실물책이 괜찮은 것 같다.
그럼 렛츠고~🚀
초반
와... 정말 훌륭한 책이다. 지금까지 고민해왔던 여러 문제들을 명확하게 해결해주고 있다. 지금은 9장 코드 리뷰 섹션을 보고 있는데 정말 디테일하게 코드 리뷰의 목적과 방법을 제시해준다.
다음 챕터로는 2장 팀워크, 11장 테스트, 23장 지속적 통합(CI)를 보려고 한다. 지금껏 골머리를 앓아왔던 문제들을 명쾌하게 해답을 제시해줄 것 같은 확신이 든다. 이 책을 마스터하면 훌륭한 멘토가 될수 있을 것 같다. 빠르게 꾸준히 책을 읽어서 어서 빨리 내것으로 체득하고싶다. 너무 신나고 재밌다. 예~ 😗
중반
지금까지 2장 팀워크, 3장 지식 공유, 5장 팀 이끌기, 9장 코드 리뷰, 11장 테스트, 12장 단위테스트를 읽었다. 좋은 소프트웨어 엔지니어가 되기 위해 필요한 하드 스킬과 소프트 스킬을 골고루 배울 수 있었다. 이 책을 읽기 전과 읽고 난 후의 가장 큰 차이점은 내 목표가 바뀌었다는 것이다. 나는 IC로서 테크리드가 되는 것을 꿈꿔왔었다. 하지만 나는 매니저가 더 적합하고 잘 할 수 있을 것 같다고 생각이 변했다. 앞으로는 뛰어난 소프트웨어 매니저가 되기 위해서 노력할 생각이다. 또 일하면서 정말 해결하기 어려웠던 갈등 해결과 동기부여에 대한 구글 엔지니어들의 20년 어치의 고민과 해답을 얻을 수 있었다. 이 책을 작성해준 구글 엔지니어에게 무한한 감사를 전하고 싶다. 🙇♂️
2장. 팀워크 이끌어내기
- 자신을 조망하는데서 시작해야한다.
- 사람은 완벽하지 않다. 종종 실수를 하는데 그마저도 일관되지 못하다. 내면에 서식하는 버그를 먼저 이해해야한다.
- 핵심 주제는 소프트웨어 개발은 "팀의 단합된 노력의 결실"이라는 점이다.
- 협업이 성공하려면 겸손, 존중, 신뢰라는 핵심 원칙에 맞게 자신의 행동을 바로잡아야한다.
핵심 정리
- 고립되어 일하는 것에 유의한다. 함께하면 더 좋은 설계를, 더 빨리 만들 수 있다. 협업을 통해 올바른 일을 하고 있는지, 제대로 하고 있는지, 이미 해놓은 일이 아닌지 확인해야한다.
- 사회적 관계의 세 기둥: 겸손, 존중, 신뢰. [겸손]: 당신은 모든 것을 알지도, 완벽하지도 않다. 겸손한 사람은 배움에 열려 있다. [존중]: 함께 일하는 동료를 진심으로 생각한다. 친절하게 대하고 그들의 능력과 성취에 감사해한다. [신뢰]: 동료들이 유능하고 올바른 일을 하리라 믿는다. 필요하면 그들에게 스스로 방향을 정하게 해도 좋다.
- 내가 가장 현명한 사람이라는 쓸데없는 자존심은 버리고 겸손한 자세로 조직의 자존심과 성취를 높이기 위해 노력하라.
- 올바르게 비평받고 비평하는 방법을 배워야한다. 동료가 개선할 방법을 알려주는 데 이를 공격한다고 받아들이지 말라. 상대를 지적하고 바꾸려하지 말고 내가 어렵다고 느끼는 점을 부드럽게 말한다.
- 비난하지말자. 실패를 통해 배우고 다음 단계로 나아가는 기회로 삼는다. 비난 받지 않을 것이라는 심리적 안정감이 있어야 실패를 숨기지 않는다.
- 의견 충돌이 발생할 경우 인내심을 갖는다. 서로를 존중하고 신뢰하면서 협업할 수 있는 새로운 방법을 찾는다.
- 마음을 열고 받아들이자. 팀에 고집을 굽히지 않는 사람이 있으면, 다른 팀원들은 그에게 더 이상 귀 기울이지 않고, 그를 장애물 취급하며 피해다닐 것이다. (내 주변에 사람들이 없다면 내가 이런 사람이 아닌지 진지하게 고민해보라)
내 코드를 숨기고 싶어요
- 불안감. 사람들은 자신의 진행중인 작업물을 다른 사람이 보고 판단하는 것을 두려워한다.
- 불안감은 사실 더 큰 문제의 징후이다.
천재 신화
- 한사람이 성공시킨게 아니라 커뮤니티가 함께 노력해 이룬 결실이다.
- 리누스, 귀도 반 로섬, 빌 게이츠, 스티브 잡스. 이들 모두는 커뮤니티를 이끌어 집단적 성과물의 상징이 되었다.
- 마이클 조던의 천재성은 팀과 협동하는 방식에 있다.
- 여러분은 천재가 아니다. 천재는 정말 정말 드물다.
- 천재든 아니든 사회성이 부족한 사람은 팀원으로 적합하지 않다.
- 우리를 미래로 이어주는 핵심은 다른 사람과 얼마나 잘 협력하느냐이다.
숨기는 건 해롭다
- 실패의 위험성을 키운다. 성장 잠재력을 낮춘다.
- 자신이 올바로 걷고 있음을 어떻게 확인할 수 있을까요?
- 함께하면 더 좋은 설계를 만들고, 더 빨리 만들 수 있다.
조기 감지
- 아이디어를 완벽히 다듬어질 때까지 숨기는건 엄청난 도박이다.
- 초기 설계는 실수가 있기 쉽다.
- 바퀴를 다시 발명하거나 협업의 이점을 얻지 못한다.
- 협업을 하면 더 빠르다.
- 우리는 올바른 일을 하고 있는지, 제대로 하고 있는지, 이미 해놓은 일이 아닌지 확인해야한다.
- 초기 단계에 실수를 할 확률이 높다. 따라서 조기에 피드백을 받아야한다.
- 일찍 실패하고, 빨리 실패하고, 자주 실패하라.
버스 지수
- 몇명의 팀원이 버스에 치이면 프로젝트가 망하는지를 나타내는 지수.
- 높을 수록 프로젝트가 유지될 가능성이 높다.
- 지식 공유를 통해 버스지수를 높여야한다.
- 결혼, 팀이동, 퇴사, 병가 등의 예기치 못한 사정이 생길 수 있다.
- 최소한 각 책임 영역마다 2차 담당자를 두고 제대로된 문서를 갖추면 프로젝트의 미래가 보장된다.
- 혼자 일하면 느리다.
- 혼자 검색으로 얻는 지식은 사람의 경험을 대체할 수 없다.
- 공동의 지혜를 얻을 수 있다.
- 엔지니어링은 어렵다. 동료가 필요하다.
진척 속도
- 자주 컴파일해야 오타나 오류를 빨리 찾을 수 있다. 빠른 피드백이 중요하다.
- 데브옵스 철학: 가능한 한 일찍 피드백하고, 일찍 테스트하고, 보안과 프로덕션 환경을 초기부터 고려한다.
- 원점 회귀(shift left): 문제를 빨리 찾을 수록 고치는 비용이 낮아진다.
- 계획이나 설계 변경이 필요한 시점을 즉시 알려줄 피드백 루프는 바로 팀 플레이다. 눈이 많아야 프로젝트가 탈선하지 않고 옳은 방향으로 나아간다.
- 동굴에 갇혀 일하면 세상이 바뀌어 있을것이다. 결국 가치 없는 결과물만 남게 된다.
- 아직 모르는게 많은 팀원이 여러분에게 질문하기조차 어려운 문화라면 분명 문제이다.
- 집중과 활발한 소통의 균형점을 찾아야한다.
결론은, 숨기지 말자
- 홀로 일하기는 함께 일하기보다 더 위험하다. 잘못된 일에 시간을 낭비할 가능성을 걱정해야한다.
모든건 팀에 달렸다.
- 고독한 장인은 매우 드물다. 초월적인 업적을 홀로 이루어내지 못한다.
- 위대한 팀은 슈퍼스타를 잘 활용하며, 개개인의 성과보다 더 큰 성과를 만들어낸다. 하지만 위대한 팀을 만들기는 매우 어렵다.
- 비전을 공유하고, 역할을 나누고, 다른 이로부터 배우고, 멋진 팀을 만들자.
- 고기능 팀(사회적 인지 기능이 높아 팀과 조직의 공통 목표를 잘 인지하여 최선의 결과를 얻기 위해 주도적이고 적극적으로 노력하는 팀)은 황금처럼 귀한, 성공으로 가는 열쇠이다.
사회적 상호작용의 세 기둥
- 기둥1: 겸손(humility): 당신은 모든 것을 알지도, 완벽하지도 않다. 겸손한 사람은 배움에 열려 있다.
- 기둥2: 존중(respect): 함께 일하는 동료를 진심으로 생각한다. 친절하게 대하고 그들의 능력과 성취에 감사해한다.
- 기둥3: 신뢰(trust): 동료들이 유능하고 올바른 일을 하리라 믿는다. 필요하면 그들에게 스스로 방향을 정하게 해도 좋다.
세 기둥이 중요한 이유
- 사회적 관계의 힘을 과소평가하지 말라. 좋은 사회적 관계는 다른 사람이 나를 기꺼이 돕게 만든다.
- 사람을 속이거나 뒤에서 조정하는게 아니라, 일이 진행될 수 있도록 관계를 형성하라.
- 관계는 프로젝트보다 오래 지속된다.
- 동료들과 끈끈해지면 여러분이 필요할 때 기꺼이 자신들의 수고를 마다하지 않을 것이다.
겸손, 존중, 신뢰 실천하기
자존심 버리기
- 잘못된 방법: 자신이 가장 현명하다고 생각하기. 논의 주제를 시작하고 마무리 짓기. 제안마다 사사건건 첨언하기.
- 대신 집단적 자존심 찾기. 팀의 성취와 단체의 자부심을 높이려 노력하라. 팀보다 자기 홍보에 관심을 두는 사람을 거부하라.
- (편한 복장 예시) 일을 더 매끄럽게 진행할 수 있도록 시스템이 당신의 일을 어떻게 도울 수 있을지 연구하라. 그러면 시스템이 당신의 바람대로 움직이게 하는 방법을 터득할 것이다. 그렇지 않으면 평생 시스템과 작은 전쟁들을 치러야 할 것이다.
비평하고 비평받는 법 배우기
- 코드 리뷰 예시. 처음부터 팀의 코드를 지적하면 안된다. 팀의 불안감을 더 세심히 살피고 코드 리뷰 문화를 소개하는 방법을 써야했다. 독단적으로 행동하지말고 팀과 미리 논의하라.
- "건설적인 비평"과 "성향에 대한 공격": 건설적 비판은 프로젝트에 도움이 되며 개선을 위한 지침을 준다. 상대방을 진심으로 생각하고 개선되길 바라야 한다. 동료를 존중하고 공손하게 비평하는 법을 배워야 한다.
- 자신도 비평을 잘 수용할 줄 알아야 한다. 자신의 기술에 겸손해야 함은 물론, 상대를 나를 진심으로 생각하며 절대 나를 어리석다고 생각하는게 아님을 믿어야한다.
- 자존심을 내려놔라. 동료가 개선할 방법을 알려주는 데 이를 공격한다고 받아들이지 말길 바란다. 자신을 믿고 동료를 믿어라.
- 누군가에게 잘못했다라고 하면 안된다. 무언가를 고치라고 요구해서도 안된다. 다른 사람과 다르다고 비난하면 안된다. 이렇게 하면 상대는 즉시 방어 자세를 취하고 감정적으로 반응할 것이다.
- 상대가 아닌 자신을 겸손하게 낮춰라. 상대가 틀린게 아니라 여러분이 코드를 이해하는데 문제를 겪고 있는 것이다.
- "저기 이 부분의 제어 흐름이 좀 헷갈리네요. 혹시 xxx패턴을 적용하면 더 명확해질까요? 나중에 관리하기 쉬워질지도 모르겠네요"
- 내가 생각한 부드러운 대화 방법:
리뷰어: (변경 대안 제시) 제가 이 부분이 잘 이해가 안되네요. 이렇게 해보면 더 이해하기가 쉬워질까요?
작성자: (수긍 및 의도 확인)좋은 방법이네요. 이렇게 해봤는데 어떤가요?
(도움 요청) 제가 이부분에서 어려움을 겪고 있어서 도움이 필요합니다. 도와주실 수 있나요?
(정중한 거절 및 대안 제시)의견 주셔서 감사합니다. 이러한 이유로 이렇게 하는건 어떤가요?
빠르게 실패하고 반복하기
- 실패는 선택이다. 가끔씩 실패하지 않는다면 충분히 혁신적이지 않거나 충분히 위험을 감수하지 않은 것이다.
- 실패를 "배우고 다음 단계로 넘어갈 수 있는 절호의 기회"로 생각한다. 동일한 일을 반복해서 실패한다면 실패한게 아니라 무능한 것이다.
비난 없는 포스트모템 문화
- 실패한 근본 원인을 분석하여 문서로 남기는 것이 실수로부터 배우는 핵심이다.
- 이를 포스트모템 문화라고 한다. 포스트모템 문서가 사죄, 변명, 지적으로 채워지지 않도록 주의한다.
- 무엇을 배웠는지와 앞으로 무엇을 바꿀지가 담겨야 한다.
- 팀이 이를 실천하는지를 확인해야한다.
- 사건의 개요, 사건을 인지하고 해결에 이르기까지의 타임라인, 사건의 근본 원인, 영향과 피해 평가, 문제를 즉시 해결하기 위한 조치 항목(소유자 명시), 재발 방지를 위한 조치 항목, 해당 경험에서 얻은 교훈
인내심을 기르자
- 버그를 찾기 위해 짝프로그래밍을 하는 예시
- 두 개발자의 성향이 달라서 많은 충돌이 발생했다. 하지만 둘은 오랜 세월 서로를 신뢰하고 존중해왔었다. 그래서 인내심을 잃지 않고 새로운 협업 방식을 찾아냈다.
- 인내심과 새로운 협업 방식을 찾고자 하는 의지 덕분에 좋은 결과를 낼 수 있었다.
마음을 열고 받아들이자
- 다른 이로부터 배우는 데 열려 있을수록 여러분의 영향력도 커진다.
- 결점이 많은 사람일 수록 더 강한 척 한다. (좋지 않은 의미로)
- 주변 사람들이 아무리 설득해도 고집을 굽히지 않는 사람이 끼어있는 팀을 상상해보라. 다른 팀원들은 그에게 더 이상 귀 기울이지 않고, 그를 장애물 취급하며 피해다닐 것이다. 여러분도 이런 사람이 되길 원치는 않을 테니 '다른 사람이 내 생각을 바꿔도 괜찮아'라는 생각을 항상 기억하길 바란다.
- 엔지니어링은 본질적으로 트레이드오프이다.
- 여러분이 항상 옳을 수는 없다. 그러니 새로운 증거가 나타나면 여러분의 생각을 바꿔야 한다.
- 첫 발을 내딛거나 결정 사항을 엄숙히 공표하기 전에 주변의 말을 들어봐야한다. 나중에 계속 말을 바꾸면 사람들이 여러분을 우유부단하게 볼 것이다.
- 결점은 나약함을 드러내는 것이며 신뢰를 깨뜨릴것이라는 생각은 잘못된 생각이다. 실수했다거나 자기 역량을 넘어선 일임을 인정하는 것은 장기적으로 지위를 더 확고히 해줄 것이다. 결점을 드러낸다는 것은 겸손을 겉으로 표현하는 일이며, 책임을 지고 의무를 다 하려는 의지의 표출이다. 그리고 다른 이들의 의견을 신뢰한다는 신호이기도 하다.
- 그 결과 사람들은 당신의 솔직함과 용기를 존중하게 될 것이다. 때때로 최선의 말은 '저는 잘 모르겠습니다'일 경우도 있다.
구글답게 하기
- 구글은 사람을 대하는 구글만의 '겸손, 존중, 신뢰'의 원칙이 있다.
- 그런데 '구글답게' 라는 표현이 너무 개인적으로 사용되고 그 의미가 차츰 변질되었다.
- '구글다움'의 기준을 명확히 해서 문제를 해결했다.
- 강력한 리더십을 보이고 '겸손, 존중, 신뢰'를 드러내는 태도와 행동 등을 정의했다.
모호함을 뚫고 번창한다
- 끊임없이 변화하는 환경 속에서도 상충하는 메시지와 방향에 잘 대처하고, 합의를 이끌어내고, 문제에 대한 진전을 이룰 수 있다.
피드백을 소중히 한다
- 피드백을 주고받을 때 품위와 겸손을 유지하고 개인과 팀의 발전에 피드백이 주는 가치를 이해한다.
저항(항상성)을 극복한다
- 다른 이들이 저항하거나 관성 때문에 움직이지 않으려 하더라도 야심 찬 목표를 세우고 밀고 나아간다.
사용자를 우선한다
- 구극 제품의 사용자 입장에서 생각하고 존중하며 그들에게 가장 도움되는 행동을 추구한다.
팀에 관심을 기울인다
- 동료들의 입장에서 생각하고 존중하며 팀의 결집을 위해 누가 시키지 않더라도 적극적으로 돕는다.
옳은 일을 한다
- 모든 일에 강한 윤리 의식을 갖고 임한다. 팀과 제품의 진정성을 지키기 위해서라면 어렵거나 불편한 결정을 내릴 수 있어야 한다.
마치며
- 소프트웨어를 떠받드는 토대는 '제대로 작동하는 팀(고기능 팀)'이다
- 모든 일은 홀로 이뤄낸게 아니다.
- 소프트웨어 조직이 오래 지속되려면 겸속과 신뢰, 그리고 팀을 중심으로 한 존중에 뿌리를 둔 건강한 문화를 갖춰야 한다.
- 스프트웨어 개발은 근본적으로 창의적인 일이므로 위험을 감수할 줄 알아야 하고 때로는 실패할 줄도 알아야한다.
- 실패를 인정하려면 건강한 팀 환경이 반드시 갖춰져 있어야 한다. 비난받지 않을 것이라는 심리적 안정감이 중요하다.
3장. 지식 공유
핵심 정리
- 심리적 안전은 지식 공유 환경을 조성하기 위한 토대이다.
- 사람은 아는 것보다 모르는게 훨씬 많다. 초심자일수록 질문을 두려워한다. 질문은 배움의 기회이다. 다양한 방법으로 쉽게 질문할 수 있는 조직 분위기를 만들어야 한다.
- 작게 시작하라. 질문하고 기록하라.
- 직원들이 전문가와 문서화된 자료 모두로부터 필요한 도움을 쉽게 얻을 수 있도록 하라.
- 자기 자신, 소속 팀, 소속 조직을 넘어, 자신의 전문 지식을 가르치고 전파하는 사람들을 격려하고 보상하는 체계적인 제도를 마련하라.
- 만병통치약을 없다. 지식 공유 문화를 뿌리내리고 강화하려면 여러 가지 전략을 조합 해야 한다. 또한 조직에 가장 적한한 조합은 시간이 지나면서 달라질 수 있다.
5장. 팀 이끌기
핵심 정리
- 전통적인 의미에서의 '관리'는 하지 말라. 리더십, 영향력, 팀을 위한 봉사에 집중하라.
- 리더는 크게 관리자(매니저)와 테크 리드로 나뉜다.
- 관리자는 성과, 생산성, 행복을 책임진다.
- 테크 리드는 제품의 기술 결정, 아키텍처, 우선순위, 성능을 책임진다.
겸손, 존중, 신뢰의 분위기를 조성하려 힘써라.
- 섬기는 리더십을 갖춰라. 팀의 장애물을 치워주고, 팀이 합의에 이르도록 도와주고, 자신의 손을 더럽히는데 주저하지 말라. 관리자가 해야할 '관리'는
팀의 기술적, 사회적 건강을 관리하는 것이다.
- 가능하면 위임하라. DIY(Do It Yourself. 혼자서 하려고 하는 것) 자제하기!
- 팀이 무엇에 집중하는지, 그리고 방향과 속도에 특히 신경 써라.
- 좋은 패턴
- 관리자는
무슨 일을 할지 고민하라. 어떻게는 팀을 믿고 맡겨라.
- 자존심 버리기: 팀을 신뢰하고 마이크로매니징 하지 않기. 관리자는 잘못된 결정을 내리기도 하고 모든 문제의 답을 알고 있지도 못한다.
- 마음 다스리기: 부하는 항상 리더를 쳐다본다. 평정심 유지하기. 스스로 해결하도록 도와주기. 질문하기(고무 오리 디버깅).
- 촉매자 되기: 합의를 이끌어내기.
- 장애물 치우기: 정답을 아는 사람을 알려주기.
- 선생이자 멘토 되기: 멘토가 되기 위해 필요한 것: 팀의 프로세스와 체계에 대한 경험, 다른 이에게 무언가를 설명해주는 능력, 멘티에게 도움이 얼마나 필요한지를 측정하는 능력.
- 명확한 목표 세우기: 제품의 방향을 제시하고 직원이 다른 방향으로 끌지 않도록 방향을 잡아주기.
- 정직하기: 정보를 공유하기. 비밀 내용은 '말할 수 없다', 모르면 '잘 모른다'라고 하기. 칭찬 샌드위치 사용하지 않기. 직접적인 피드백을 할때는 메시지를 정확하게 왜곡 없이 전달하기. 상대를 존중하고 공감하고 친절하게 대하기.
- 행복한지 확인하기: 팀원들의 복지를 챙기고, 인정해주고, 만족하는지 확인하기. 모두 하기 싫어하는 일은 표로 정리해서 공평하게 분배하기. 휴가나 소풍 등의 보상 챙겨주기. 면담 마지막에 '뭐 필요한 거 없어요?' 물어보기
- 회사 밖에서의 행복에 신경 써주기. 경력 관리 챙겨주기. 스스로를 성장 시키고 일에 재미를 느끼게 해주기.
- 위임하되, 곤란한 일은 직접 처리하기
- 여러분을 대신할 사람을 찾기
- 방치하지 않기, 빠르게 조치하기
- 혼란으로부터 팀을 보호하기
- 팀에 공중 엄호를 해주기
- 팀이 잘하고 있다면 칭찬하기
- 실패해도 쉽게 되돌릴 수 있는 일에는 '해보세요'라고 말하기
- 사람은 식물과 같다. 누구에게 무엇이 필요한지 결정하고 제공하기.
동기부여와 방향지시
- 내적 동기부여: 자율성(하고 싶은 일, 주인 의식), 전문성(배움의 기회 제공), 목적(노력의 의미, 성취)
9장. 코드 리뷰
- "밑바닥부터 만들고있다면 분명 잘못하고있는거야." 중복확인이 선행되어야한다. 코드 작성 전에 관련 그룹과 대화를 나눠봐야한다.
- 코드 리뷰 과정 자체가 기존 결정을 다시 논의할 기회로 보아선 안된다.
핵심 정리
- 코드베이스 전반의 정확성, 이해 용이성, 일관성 보장 등 코드 리뷰가 주는 이점이 많다.
- 여러분의 가정에 대해 항시 다른 사람의 검토를 받도록 하고, 코드를 읽는 사람의 입장에서 최적화한다.
- 전문가다움을 지키면서 중요한 피드백을 받을 기회를 제공하라.
- 코드 리뷰는 지식을 조직 전체에 공유하는 데도 중요한 역할을 한다.
- 코드 리뷰 프로세스를 확장하려면 자동화가 아주 중요하다.
- 코드 리뷰 자체가 변경 이력이 되어준다.
코드리뷰 승인의 기준
- 정확성, 이해 용이성. 동료의 LGTM.
- 코드 소유자의 승인. 테크 리드나 기술 전문가. OWNERS 파일
- 가독성 승인. 코드 스타일과 표준
코드정확성
- LGTM은 Looks Good To Me의 약자로 완벽하진 않아도 내가 보기에 괜찮다는 의미이다.
- 평가가 주관적이지 않도록 일반적으로 작성자가 선택한 방식을 존중해준다. 리뷰어의 선호를 강요하지 않는다. 이해하기 더 쉽거나 대안이 있을 경우 제시한다.
- 코드가 개선되었다면 변경을 승인한다. 코드 리뷰의 속도를 높인다.
- 중점은 이해하기 쉽고, 시간이 흘러도 의미가 통할것이라는 점
코드 이해 용이성
- 코드는 작성되는 횟수보다 읽히는 횟수가 몇배는 많으므로 이해하기 쉽게 작성되는게 매우 중요하다.
코드 일관성
- 같은 패턴, 기능적으로 다소 부족하더라도 이해하기 쉬운 덜 복잡한 코드
심리적 문화적 이점
- 코드는 조직의 공동 소유물
- 코드리뷰라는 중제 제도가 없으면 사소한 비판으로 서로의 감정이 상할 수 있음
- 내 코드를 평가받는다는 생각에, 리뷰 직전 한번 더 코드를 살펴보게 됨
지식 공유
- 제안, 신기술 소개, 조언을 통해 리뷰어가 작성자에게 도메인 지식을 전파하도록 이끌어준다.
- 양방향으로 정보가 교환되며 지식 공유를 촉진시킨다.
- 코드 리뷰는 지식을 퍼뜨리기에 완벽한 기회이다.
- 새로운 패턴을 알리는 자리로도 자주 활용된다.
- 코드베이스의 변경 이력을 기록하는 역할도 한다.
- 코드 리뷰와 변경 이력을 통해 훨씬 많은 엔지니어에게 지혜를 전달한다.
모범 사례
공손하고 전문가답게
- 신뢰와 존중의 문화
- 리뷰는 작성자와 리뷰어 모두에게 배움의 기회
- 작성된 코드는 팀의 소유임. 팀의 입장에서 생각해보기
작게 변경하기
- 큰 변경을 지양함. 리뷰어에게 거부권이 있음
- 작은 변경은 200줄
- 빠른 리뷰 가능, 버그 부담을 줄여줌
변경 설명 잘쓰기
- 커밋 제목, 설명을 잘 써야 나중에 이력을 찾기 쉽다.
리뷰어는 최소한으로
- 한명이 코드 승인의 기준 3가지 역할을 모두 수행하는게 베스트.
- 리뷰어가 추가되면 얻는 가치보다 비용이 훨씬 빠르게 증가한다.
가능하면 자동화
코드 리뷰 유형
그린필드 코드리뷰
- 잡초만 무성한 허허벌판에서 시작하는 공사를 빗댐.
- 새로운 코드나 프로젝트를 대상으로하는 설계리뷰
- 강도 높은 설계 리뷰가 필요함
- 코드의 지속 가능함을 보장, 설계문서 검토, 테스트 확인, 소유자 배정, 주석과 문서, CI
동작 변경, 개선, 최적화
- 대부분의 코드 변경
- 꼭 필요한 변경인지, 코드를 개선하는지
- 죽은 코드나 낡은 코드를 제거하는지
버그 수정, 롤백
- 버그 수정과 기능 변경을 분리해야함
- 섞이면 리뷰가 복잡해지고 회귀 테스트나 롤백이 어려워짐
- 테스트도 함께 수정하여 버그가 재발할 시 알려주도록 해야함
- 버그가 발생한 이유는 테스트가 부족했거나 가정이 잘못됨. 리뷰어가 테스트 업데이트를 챙겨야함
- 변경을 작게 가져가야 롤백에 유리함
리팩토링, 대규모 변경
- 대규모 리팩토링은 기계가 자동으로 만들어줌
- 리뷰어 자신의 코드에 집중하기. 변경 검토는 이미 끝났기 때문
- 수정 범위 확장 지양. 변경 작게 유지.
- 댓글 지양. 변경사항이 많기때문에 댓글이 조금만 달려도 개선 의지를 위축 시킬 수 있음
11장. 테스트 개요
- 테스트는 버그가 몰래 숨어들어 고객을 놀라게 하는 사태를 막아준다.
- 테스트로 얻을 수 있는 것: 버그 잡기, 소프트웨어가 변화할 수 있도록 지원하기
- 테스트 체계가 잘 갖춰져 있다면 변화를 두려워하지 않는다.
- 테스트를 소프트웨어 개발의 핵심 역량으로 취급할 수 있다.
- 테스트를 작성하는 행위가 시스템의 설계도 개선해준다.
- 테스트는 설계에 관해 많은 이야기를 해준다. 데이터베이스에 너무 강하게 묶여 있는지, API가 필수 유스케이스를 지원하는지, 시스템이 극닥적인 상황들에 잘 대처하는지 등을 확인 할 수 있다.
- 자동 테스트를 작성하면 문제를 개발 주기의 초반에 잡아낸다. 그 결과 모듈화가 더 잘되어 미래의 변화에 훨씬 유연한 소프트웨어가 만들어진다.
핵심 정리
- 자동 테스트는 소프트웨어를 변경할 수 있게 해주는 토대이다.
- 테스트를 확장하려면 반드시 자동화해야한다.
- 테스트 커버리지를 건실하게 유지하려면 균형 잡힌 테스트 스위트가 필요하다.
- 네가 그것을 좋아했다면(중요하다고 생각했다면) 테스트를 준비해뒀어야지 (비욘세 노래 제목 패러디)
- 조직의 테스트 문화를 바꾸는 데는 시간이 걸린다.(테스트를 강제하기보다 테스트가 훌륭한 아이디어임을 입증하는 데 초점을 맞춘다. 스스로 깨닫고 테스트를 작성하게 만들어야한다.)
테스트를 작성하는 이유
- 자동 테스트는 무엇인가? 입력, 출력을 통해 시스템이 기대한 대로 동작한 것인지를 판단하는 것
- 이런 간단한 테스트가 수백 수천 개 모이면 테스트 스위트가 되어 의도한 설계대로 잘 작동하는지 이야기 할 수 있다.
- 코드가 커지면서 테스트가 일관되지 못하거나, 느려지는 문제가 나타나게된다.
- 테스트는 엔지니어에게 신뢰를 줄 때만 가치가 있다. 테스트가 생산성을 떨어뜨리고 그 결과를 믿을 수 없다면 엔지니어는 테스트를 신뢰하지 못하고 우회 방법을 찾으려 할 것이다.
- 나쁜 테스트는 없는 것 만 못하다.
- 테스트는 좋은 제품을 빠르게 만들 수 있게 해주고 서비스의 안전을 보장하는데 핵심적인 역할을 한다.
- 품질이 떨어지는 제품을 만들면 필연적으로 나쁜 결과를 초래한다. 품질과 테스트는 매우 중요하다.
- 이러한 교훈으로 구글은 테스트를 엔지니어링 문화의 중심에 두고 있다.
구글 웹 서버 이야기
- GWS는 검색 서비스의 관제 시스템 같은 아주 중요한 서비스이다. 2005년 GWS의 덩치가 커지면서 생산성이 급격히 떨어졌다.
- 릴리스마다 버그가 넘쳐났고 팀원들은 불안해했고 프로덕션 환경에서만 작동하지 않는 경우도 많았다. 롤백도 해야했다.
- 이 문제를 해결하고자 테크 리드는 엔지니어 주도의 자동 테스트를 정책 차원에서 도입했다.
- 모든 코드 변경에는 지속해서 실행할 수 있는 테스트가 반드시 포함되어야 했다.
- 정책 도입 1년 만에 긴급하게 코드를 수정해 배포하는 건수(버그 핫픽스)가 '절반'으로 줄었다.
- 테스트는 생산성과 자신감을 높여줬다.
- 현재 GWS는 수만개의 테스트를 보유하고 있으며 거의 매일 릴리스되고 있다.
- 제품 결함 해결을 프로그래머의 능력에만 의존해서는 안된다.
- 프로젝트가 커져 팀원이 많아지면 결함이 생기기 마련이다. 한달에 버그를 단 하나밖에 만들지 않는 뛰어난 엔지니어 100명이 일하는 경우, 매일 5개의 버그를 만들어 내는 셈이 된다. 설상가상으로 버그를 고치다가 다른 버그를 만들기도 한다.
- 최고의 팀은 집단 지성을 팀 전체의 이익으로 환원하는 방법을 찾아낸다. 개별 엔지니어가 작성한 테스트는 팀 전체에 공유되고, 이 자동테스트는 개인이 디버깅을 하는것과 하늘과 땅만큼 큰 비용 차이를 만들어낸다.
오늘날의 개발 속도에 맞는 테스트
- 제품이 커지면 수동 테스트는 불가능해진다. 모든 언어, 국가, 기기별로 수동 테스트하는 것을 상상해보라
- 테스트의 해법은 단 하나, 바로 '자동화' 뿐이다.
작성하고, 수행하고, 조치하라
- 자동 테스트는 '테스트 작성', '테스트 수행', '실패한 테스트에 대한 조치' 3가지 활동으로 이루어진다.
- 과거의 품질보증(QA) 프로세스와 달리, 오늘날의 개발자들은 자신의 코드를 검사하는 자동 테스트를 작성하고 수행하는데 능동적이고 핵심적인 역할을 한다.
- 오늘날의 시스템의 규모와 배포 속도를 따라잡으려면 모든 엔지니어가 테스트도 함께 개발해야만 한다.
- 좋은 테스트를 작성하는게 중요하다. 이어지는 12~14장에서 자세히 살펴본다.
- 테스트를 수시로 자주 실행해야한다. 자동 테스트의 핵심은 같은 동작을 끊임없이 반복하는 것이다.
- 자동 테스트는 다양한 환경에서 수행될 수 있도록 '모듈화'하기도 좋다. 환경에 맞춰 재활용 가능하다.
- 개발이 한창인 제품은 필연적으로 테스트 실패를 겪는다.
- 테스트 프로세스가 얼마나 효과적이냐는 테스트 실패를 어떻게 처리하느냐에 달렸다.
- 테스트가 실패하면 수 분내로 해결하도록 하는 팀이라면 제품과 테스트를 더 신뢰하며 오류를 빠르게 퇴치할 수 있다.
- 건실한 자동 테스트 문화는 모두가 테스트를 작성하고 공유하도록 장려한다. 테스트들을 자주 실행한다. 테스트가 실패하면 곧바로 조치해야 테스트를 신뢰하고 계속 이어갈 수 있다.
테스트 코드가 주는 혜택
- 테스트 문화가 확실하게 뿌리 내린 조직을 경험해보지 못한 개발자는 테스트를 작성하면 생산성과 속도가 높아진다고 생각하기 어렵다. 오히려 반대로 생각한다.
- 하지만 구글은 테스트에 투자하는게 생산성을 향상시키는 중요한 이유를 발견했다.
디버깅 감소
- 당연하게도, 테스트를 거친 코드는 결함이 적다.
- 여기서 중요한 사실은 결함 대부분이 테스트 단계(서브밋 전)에서 고쳐지기 때문에 그 코드의 존속 기간 전체로 봤을때 결함이 줄어든다는 점이다.
- 테스트를 한 번 작성해두면 값비싼 결함을 예방해주고, 짜증나는 디버깅에서 해방시켜주는 식으로 지속해서 혜택을 준다.
자신 있게 변경
- 모든 소프트웨어는 변경된다. 좋은 테스트들로 무장한 팀은 자신감 있게 리뷰하고 수용할 수 있다.
- 행위가 달라지지 않는 변경인 리팩터링은 (이상적으로는) 기존 테스트를 수정할 필요조차 없다.
더 나은 문서자료
- 소프트웨어 문서자료는 신뢰할 수 없는 것으로 악명이 높다.
- 내용이 낡았거나, 누락되는 등 해당 코드를 잘 대변하지 못한다.
- 명확한 테스트는 실행 가능한 문서와 같다.
- 테스트는 명확하고 간결해야지만 문서자료로서의 역할을 훌륭히 수행할 수 있으니 명심하자.
더 단순한 리뷰
- 구글에서는 최소 한명이 리뷰를 승인해야된다. 이때 정확성, 극단 상황, 오류 상황 등 다양한 측면을 검사해주는 테스트가 준비되어 있다면 리뷰어는 코드 검증 시간을 크게 줄일 수 있다.
- 테스트를 읽고 테스트가 통과하는지만 보면 되기 때문이다.
사려 깊은 설계
- 새로 작성한 코드의 테스트를 작성하는 일은 설계가 잘 되었는지 시험하는 행위이다.
- 테스트하기 어려운 코드는 너무 많은 역할을 짊어지거나, 의존성을 관리하기 어렵게 짜여졌기 때문일 가능성이 크다.
- 잘 설계된 코드라면 모듈화가 잘 되어있다. 다른 코드와 강하게 결합되지 않고 특정 역할에 집중해야한다.
- 테스트를 통해 설계 문제를 조기에 바로잡는다면 훗날 고생을 덜 한다.
고품질의 릴리스(배포)를 빠르게
- 자동 테스트 스위트를 갖춘 팀은 새로운 버전을 릴리스할때 불안에 떨지 않는다.
- 구글에서는 많은 프로젝트가 매일 새로운 버전을 프로덕션 환경으로 릴리스한다. 수천 명의 엔지니어가 수천 번의 코드 변경을 하는 큰 프로젝트들도 마찬가지다.
- 자동 테스트 없이는 상상할 수 없는 풍경이다.
테스트 스위트 설계하기
- 구글이 초기에 배운 교훈: 초기에 엔지니어들은 시스템 규모의 큰 테스트를 작성했다. 하지만 작은 테스트와 비교하여 느리고 신뢰도가 낮고 디버깅도 어렵다는 사실을 깨달았다. 결국 고통을 줄이고자 하는 욕구가 엔지니어들을 점점 더 작은 테스트를 작성하도록 이끌었다.
- 하지만 '작다'는 것의 정확한 의미를 찾기까지 많은 논의가 필요했다. '작다는 건 단위 테스트인가?' '통합 테스트에서 작다는 어떤 의미인가?' 등의 논의 끝에 모든 테스트 케이스에는 두 가지 독립된 요소가 있다는 결론에 이르렀다.
- 바로 크기와 범위이다. 크기는 테스트 케이스 하나를 실행하는 데 필요한 자원이다. 메모리, 프로세스, 시간 등이다.
- 범위는 검증하려는 특정한 코드 경로(code path)를 뜻한다.
- 크기와 범위는 연관되어있지만 다른 개념이다.
테스트 크기
- 구글에서는 모든 테스트를 크기 기준으로 분류하며, 엔지니어들에게 주어진 기능 조각을 검사하는 가능한 한 작은 테스트를 작성하라고 독려한다.
- 테스트 크기의 기준은 어떻게 동작하고, 무엇을 하고, 얼마나 많은 자원을 소비하는지로 평가한다.
- 쉽게 설명하면, 작은 테스트는 프로세스 하나에서 동작하고, 중간 크기는 기기 하나에서, 큰 테스트는 자원을 원하는 만큼 사용해 동작한다.
- 구글에서 실제 사용하는 구분은 small, medium, large, enormous로 총 네 가지이다. 이 책에서 큰 테스트는 대부분 enormous 테스트에 해당한다.
- 구글은 전통적인 용어인 '단위 테스트'와 '통합 테스트' 대신 이 정의를 사용한다.
- 우리가 테스트 스위트에 바라는 품질은 바로 '속도'와 '결정성'이기 때문이다. (여기서 결정성이란 항상 정답이 결정되어있는 상태를 의미하는 것 같다. 테스트가 중간에 랜덤하게 다른 결과가 나오는 경우가 없다.)
- 작은 테스트는 거의 항상 더 빠르고 더 결정적이다. 또한 작은 테스트가 되기 위한 제약들 덕에 테스트를 더 빠르고 결정적이게 만들기도 훨씬 쉽다.
- 테스트가 커질수록 제약들이 완화된다. 예를 들어 중간 크기 테스트는 더 유연하지만 비결정적일 위험이 크다. 그래서 큰 테스트는 가장 복잡하고 검증하기 어려운 시나리오에만 제한적으로 활용한다.
작은 테스트
- 제약이 가장 엄격하다.
- 가장 중요한 제약은 단 하나의 프로세스에서 실행되어야 한다는 것이다. 프로그래밍 언어에 따라서는 하나의 스레드로 좁히는 경우가 많다. 즉 테스트도 테스트 대상 코드와 같은 프로세스에서 실행되어야 한다는 뜻이다. 서버를 두고 독립된 테스트 프로세스에 연결해 수행하는 방식도 허용되지 않는다. 또한 데이터베이스와 같은 제3의 프로그램을 수행해서도 안된다.
- sleep, I/O 호출 같은 블로킹 호출을 사용해서는 안된다. 네트워크와 디스크에도 접근할 수 없다는 뜻이다. 그래서 블로킹 호출을 수반하는 대상을 검사하는 테스트 코드는 테스트 대역을 사용해야한다. 테스트 대역은 강한 의존성을 가벼운 인프로세스 의존성으로 대체해주는 수단이다. (최고의 실행 속도를 위해 블로킹을 유발하는 비동기 테스트를 원천적으로 배제하기 위한 제약들로 보인다.)
- 이러한 제약들의 목적은 테스트를 느려지게 하거나 비결정적인 원인들로부터 떼어 놓는 것이다. 작은 테스트는 CPU가 수행할 수 있는 최고 속도로 실행된다. 이 제약들이 실수를 막아주는 보호막 역할을 해준다.
- 제약이 너무 과하다고 느껴진다면 작은 테스트 수백 개로 이루어진 스위트가 하루 종일 실행되는 환경을 생각해보라. 그중 불규칙한(비결정적인, flaky) 테스트가 단 몇 개만 있어도 원인을 찾아 헤매느라 생산성이 급격하게 떨어질 것이다. 구글 규모에서는 이런 문제 하나가 테스트 인프라 전체를 중단 시킬 수도 있다.
- 구글은 테스트 범위와 상관없이 가능하면 언제나 작은 테스트를 작성하라고 권한다. 전체 테스트 스위트를 빠르고 안정되게 만들어주기 때문이다. 작은 테스트와 단위 테스트의 차이는 12장에서 더 자세히 설명한다.
중간 크기 테스트
- 작은 테스트의 제약들을 따르기는 어렵지만 매우 유용한 테스트를 중간 크기 테스트로 작성한다.
- 중간 크기 테스트는 여러 프로세스와 스레드를 활용할 수 있고, 로컬 호스트로의 네트워크 호출 같은 블로킹 호출도 이용할 수 있다.
- 단, 외부 시스템과의 통신은 여전히 불허한다. 말하자면 단 한 대의 기기에서 수행되어야 한다.
- 여러 프로세스를 사용할 수 있게 되면서 할 수 있는 일이 크게 늘어난다. 예를들어 데이터베이스 인스턴스를 실행할 수 있게 되어 더 현실적인 설정 하에서 검증할 수 있다. 또한 웹 UI와 서버 코드의 조합도 테스트 할 수 있다. 예를들어 웹 앱 테스트에 웹 드라이버 도구를 이용할 수 있다. 웹 드라이버는 실제 브라우저를 조작하는 도구이다.
- 불행히도 유연성이 커지면 테스트는 느려지고 비결정적이 될 가능성이 높다.
- 여러 프로세스에 걸쳐 있거나 블로킹 호출을 하면 운영체제나 서드파티 프로세스에 의존하게 된다.
- 그래도 중간 크기 테스트는 원격 통신을 불허함으로써 어느 정도 보호막은 유지하고 있다. 원격 통신은 속도를 떨어뜨리고 비결정성을 높이는 가장 독보적인 원흉이다.
- 이처럼 안전장치가 완벽하지 않으므로 작은 테스트를 작성할 때보다 훨씬 주의해야 한다.
큰 테스트
- 큰 테스트는 로컬 호스트 제약에서 해방되어 여러 대의 기기를 활용할 수 있다.
- 더 유연해지는 만큼 위험도 늘어난다.그래서 구글은 큰 테스트를 몇 가지 용도에 한정해서 활용한다.
- 한가지는 전체 시스템의 종단간(e2e)테스트이다. 종단간 테스트는 코드 조각이 아닌 설정을 검증하는게 주된 목적이다.
- 또 한가지는 테스트 대역을 사용하는 게 불가능한 레거시 컴포넌트를 테스트하는 경우이다.
- 큰 테스트를 작은 테스트나 중간 크기 테스트와 분리하여, 빌드나 릴리스 때만 수행되도록 하여 개발 워크 플로에 영향을 주지 않도록 한다.
사례 연구: 불규칙한 테스트는 비싸다
- 수천 개의 테스트가 하루 종일 일주일 내내 실행된다고 상상해보자. 테스트당 실패 확률이 0.1%라도 하루에 1만 번 수행한다면 평균 10번씩은 실패 원인을 조사해야한다. 그만큼 생산적인 일을 할 시간은 줄어든다.
- 실패 시 다시 수행되도록 하는 방법도 있지만, 언젠가는 진짜 원인을 찾아 해결해야한다. 재수행은 이 시기를 잠시 늦춰줄 뿐이다.
- 불규칙한 테스트는 테스트의 신뢰를 저하시킨다. 엔지니어들은 테스트가 실패하더라도 더는 신경 쓰지 않는다.
- 좋은 자동 테스트 인프라라면 이런 비결정적인 동작을 엔지니어가 쉽게 파악하고 완화하는 데 도움을 줘야 한다.
테스트 크기와 무관한 공통 특성
- 모든 테스트는 밀폐(hermetic)되어야 한다. 즉, 셋업, 실행, 테어다운(철거)하는데 필요한 모든 정보를 담고 있어야 한다.
- 테스트 수행 순서 같은 외부 환경에 관해서는 가능한한 아무것도 가정하지 않아야 한다. 예를 들어 공유 데이터베이스에 의존해서는 안된다.
- 이런 제약 때문에 더 큰 테스트를 작성하기가 더 어려워지지만, 테스트 각각을 격리하려는 오력을 멈추면 안된다.
- 테스트는 확인하려는 행위를 수행하는 데 필요한 정보만을 포함해야한다. 테스트가 깔끔하고 간결하면 테스트가 의도대로 동작하는지를 검증해야 하는 리뷰어의 어깨를 가볍게 해준다. 깔끔한 코드는 테스트 실패 원인을 진단하는 데도 도움이 된다. 테스트는 무엇을 검사하는지가 명확해야 한다.
- 테스트의 정확성 검사는 사람이 직접 해야한다. 따라서 테스트에서는 조건문이나 순환문 같은 제어문을 쓰지 않는 게 좋다. 복잡한 테스트 일 수록 버그가 숨어들 가능성이 커지며, 실패원인을 찾기 어려워진다.
- 테스트는 오직 실패했을 때만 다시 들여다본다는 사실을 기억하라. 지금껏 한 번도 들여다본 적 없는 테스트가 어느날 실패하여 바로잡아야 한다고 생각해보라. 테스트 코드도 누군가 읽었을 때 부끄럽지 않도록 직관적으로 작성하라.
테스트 범위
- 테스트 범위란 주어진 테스트가 얼마나 많은 코드를 검증하느냐를 말한다.
- 좁은 범위 테스트(보통 단위 테스트)는 독립된 클래스나 메서드같이 코드베이스 중 일부 로직을 검증하도록 설계된다.
- 중간 범위 테스트(보통 통합 테스트)는 적은 수의 컴포넌트들 사이의 상호작용을 검증하도록 설계된다.
- 넓은 범위 테스트(e2e테스트)는 시스템 사이의 상호작용, 여럿을 조합해 실행하면 나타나는 예기치 못한 동작을 검증하도록 설계된다.
- 범위가 좁다고 할 때는 실행 되는 코드가 아니라 검증되는 코드의 양이 기준이다.
- 하나의 클래스를 테스트하는 과정에서 다른 여러 클래스를 의존하거나 참조하는 경우는 흔하다. 일부 다른 테스트에서는 모의 객체를 활용하여 테스트 대상 시스템 바깥 코드가 실행되는 일을 피하기도 한다. 하지만 구글은 딱히 실행할 수 없는 상황이 아니라면 실제 의존성를 끊지 않는 편을 선호한다. 이 주제는 13장에서 더 깊이 논의한다.
- 좁은 범위 테스트라고 작은 테스트인건 아니다. 예를 들어 데이터베이스와 파일 시스템을 모두 모의 테스트로 대체할 경우 큰 테스트도 좁은 테스트일 수 있다. 웹 프레임워크는 HTML과 자바스크립트를 묶어 배포되는 경우가 많다. 그래서 UI 요소의 경우 실행 경로 하나만 테스트하려고 해도 반드시 브라우저까지 실행해야한다.
- 구글은 되도록 작은 테스트를 추가 추구하며, 마찬가지로 좁은 범위 테스트를 추구한다.
- 구글은 비즈니스 로직 대부분을 검증하는 좁은 범위의 테스트가 80%, 둘 이상의 구성요소의 상호작용을 검증하는 중간 범위의 통합 테스트가 15%, 전체 시스템을 검증하는 종단간 테트스가 5% 정도가 되도록 한다.
- 단위 테스트는 빠르고 안정적이며 범위를 극적으로 좁혀줘서 적은 노력으로도 모두 식별하게 해준다. 기초를 아주 튼튼하게 다져준다. 또한 실패 원인을 빠르게 진단할 수 있다.
- 주의해야 할 안티패턴이 두 가지 있다.
- 아이스크림 콘 패턴: 엔지니어들이 종단간 테스트를 많이 작성하고 통합 테스트나 단위 테스트는 훨씬 적게 작성한다. 이런 테스트는 일반적으로 느리고 신뢰할 수 없으며 고치기도 어렵다. 급하게 프로덕션으로 이전하느라 부채를 미처 해결하지 못한 프로젝트에서 자주 나타나는 안티패턴이다.
- 모래 시계 패턴: 종단간 테스트와 단위 테스트는 많지만 통합 테스트가 적다. 모래시계 패턴은 아이스크림 콘보다는 낫다. 구성요소들이 강하게 커플링되어 각각의 인스턴스를 독립적으로 만들어낼 수 없을 때 나타난다.
- 구글이 추천하는 테스트 구성 비율은 엔지니어링 생산성과 제품 신뢰도라는 두가지 핵심 목표를 고려해 정한 것이다. 풍부한 단위 테스트는 개발 초기 빠르게 신뢰도를 높여준다. 더 큰 테스트들의 역할은 제품의 온전성 검사이다(버그를 잡는 주된 수단이 아니다).
- 물론 여러분은 구성 비유을 다르게 가져가길 원할 수 있다. 다양한 크기와 범위의 테스트가 시스템 아키텍처와 조직의 현실에 맞게끔 조화롭게 혼합되어 있다면 좋은 테스트 스위트라고 할 수 있다.
비욘세 규칙
- 새로 합류한 직원들을 지도하다 보면 어떤 행위나 속성을 테스트해야 하냐는 질문을 자주 받는다. 간단한 대답은 '깨뜨려보고 싶은 모든 것을 테스트하라' 이다.
- 시스템이 올바로 수행하는지 확신하고 싶다면 자동 테스트를 작성하는 것만이 유일한 선택지이다.
- 여기에는 성능, 행위 정확성, 접근성, 보안처럼 의심가능한 모든 것이 다 포함된다.
- 비욘세 규칙: '네가 좋아했다면(중요하다고 생각했다면) 테스트를 준비해뒀어야지'라는 뜻이다.
- 비욘세 규칙은 코드베이스의 변경을 책임지는 인프라팀이 자주 활용한다. 가령 인프라가 수정되어 A팀의 제품이 제대로 동작하지 않은 경우, A팀의 테스트를 모두 통과했다면 문제를 수정하고 검증하는 테스트를 추가할 책임은 A팀에 있다(변경을 진행한 인프라팀이 아니다).
의도적으로 실패 상황을 만드는 테스트
- 실패는 시스템이 고려해야하는 가장 중요한 상황 중 하나다. 언젠가는 실패가 찾아온다.
- 실패 상황을 시뮬레이션하는 자동 테스트를 작성하라. 단위 테스트라면 예외나 에러를 시뮬레이션해볼 수 있고, 통합 테스트나 종단간 테스트에서라면 오류를 주입하거나 지연시간을 늘려볼 수 있다.
- 카오스 엔지니어링 같은 기법을 활용하면 실제 프로덕션 네트워크에 영향을 주는 훨씬 더 큰 중단 사태도 시뮬레이션할 수 있다. 신뢰할 수 있는 시스템이라면 부정적인 조건을 예측하고 대응 방식을 통제할 수 있어야 한다.
코드 커버리지
- 코드 커버리지는 어느 테스트가 기능 코드의 어느 라인을 실행하는지를 측정하는 수단이다. 100라인짜리 코드가 있고 테스트가 90라인을 실행했다면 코드 커버리지는 90%이다.
- 안타깝게도 코드 커버리지를 테스트 품질의 지표로 보아서는 안된다. 큰 테스트는 커버리지 인플레이션을 일으켜서 더 부정확한 결과를 만들어낸다.
- 더 큰 문제는 여느 지표와 마찬가지로 커버리지 자체가 목표가 되기 쉽다는 현실이다. 처음에는 합리적이지만, 80%를 달성하면 현실의 많은 엔지니어들은 목표 수치를 최대치로 취급한다. 곧 80%에서 더 올라가지 못하게 된다. 그 이상을 해야할 동기가 부족하기 때문이다.
- 테스트 품질을 높이는 더 나은 방안은 검사해야 할 행위에 집중하는 것이다.
- 고객이 이용하는 모든 기능이 제대로 동작한다고 확신하나요?
- 의존하는 외부 시스템이나 모듈에 파괴적인 변경이 일어났을 경우 바로 인지하고 대응할 수 있을까요?
- 작성해둔 테스트는 안정적이고 믿을만한가요?
- 코드 커버리지는 시스템이 얼마나 제대로 테스트되었느냐를 판가름하는 지표로는 적합하지 않다.
구글 규모의 테스트
- 지금까지 이야기한 가이드로 거의 모든 크기의 코드베이스에 대응할 수 있었다. 하지만 매우 거대한 코드베이스를 다루는 구글이 무엇을 더 배웠는지 살펴보자.
- 구글은 모든 코드를 모노리포로 관리한다. 20억 라인이 넘는다. 구글의 코드베이스는 매주 약 2천 5백만 라인이 변경된다.
- 코드베이스를 열어두면 모두가 함께 책임지는 공동 소유 의식이 싹튼다. 자신이 직접 수정할 수 있다는 점에서 개방적인 운영은 정말 멋지다. 실제로 구글에선 다른 이가 소유한 코드를 변경해 함께 개선하는 일이 흔하다.
- 구글은 리포지터리 브랜치를 사용하는 팀이 거의 없다. 모든 변경은 헤드에 직접 커밋되어 변경 즉시 모두가 볼 수 있다. 나아가 모든 소프트웨어는 테스트 인프라가 검증한 가장 최신 커밋까지 반영해 빌드된다.
- 제품이나 서비스를 빌드할 때 필요한 외부 모듈들도 대부분 이미 빌드된 바이너리를 가져다 쓰지 않고 소스 코드로부터 새로 빌드한다. 구글은 이런 규모의 테스트를 지속적 통합 시스템을 이용해 수행한다. 구글의 지속적 통합 시스템 중심에는 테스트 자동화 플랫폼인 TAP가 자리잡고 있다. 23장에서 자세히 다룬다.
대규모 테스트 스위트의 함정
- 코드베이스가 커가다 보면 기존 코드를 변경하는 일을 피할 수 없다. 자동 테스트가 엉망으로 작성되어 있다면 이럴 때 코드를 변경하기 어렵다. 특히 깨지기 쉬운 테스트, 즉 예상 결과를 너무 세세하게 표현하거나 광범위하고 복잡한 상용구가 덕지덕지한 테스트가 우리를 가로막는다. 이처럼 품질 낮은 테스트는 해당 테스트와 관련 없는 코드가 변경되어도 실패할 수 있다. 12~14장에서는 테스트를 강건하게 하고 품질을 높이는 전략들을 소개한다.
- 깨지기 쉬운 테스트를 만드는 주범으로 모의 객체 오용을 들 수 있다. 구글은 한 때 모의 객체 프레임워크를 오용하여 큰 난리를 치렀다. 모의 객체를 효과적으로 사용하는 방법은 13장에서 다룬다.
- 깨지기 쉬운 테스트로 인한 저항 외에도 테스트 스위트가 커지면 수행 시간이 길어진다는 점도 문제이다.
- 느려질수록 수행 빈도는 자연스럽게 줄어들 것이도, 테스트의 가치는 그만큼 작아진다. 구글의 테스트 스위트의 속도를 높이는 다양한 기법을 활용한다. 병렬 실행과 빠른 하드웨어 이용 등이다. 하지만 느린 테스트 케이스의 수가 늘어나면 이런 기법들도 힘을 쓰지 못한다.
- 테스트가 느려지는 원인은 다양하다. sleep(), setTimeout()같이 0.5초씩 기다리게 하던 것이 나중에는 몇 분까지 늘어 있을 것이다. 이 방식보다는 수 마이크로초 정도의 짧은 주기로 상태가 달라졌는지를 폴링하는 전략이 낫다. 폴링과 타임아웃을 결합하여 주어진 시간 안에 상태가 변하지 않으면 테스트를 실패하게 한다.
- 테스트 스위트가 비결정적이고 느려지면 생산성을 갉아먹는다. 테스트가 느려지자 구글에서는 테스트를 전혀 실행하지 않고 서브밋하는 식으로 문제를 회피하는 엔지니어가 생기곤 했다.
- 거대한 테스트 스위트를 잘 관리하는 비결은 바로 테스트를 존중하는 문화이다. 엔지니어들이 테스트에 관심을 갖도록 장려하라. 테스트를 견고하게 만든 엔지니어에게 보상하라.
- 적절한 성능 목표를 설정하고, 느리거나 중요하지 않은 테스트들은 리팩터링하라.
- 기본적으로 테스트도 제품 코드처럼 다뤄야 한다. 테스트를 더 견고하게 만드는 데 노력을 기울여라.
- 문화를 가꾸는 일과 더불어 린터를 개발하거나 문서자료를 보강하는 등 안좋은 테스트를 만드는 실수를 줄여주는 테스트 인프라에도 투자해야한다.
- 구글은 언어마다 표준 테스트 프레임워크와 표준 모의 객체 라이브러리가 하나씩 있다. 프레임워크와 도구의 수를 줄여 투자대비 효율을 높여라.
- 테스트 관리 비용을 낮추는 데 투자하지 않는다면 종국에는 엔지니어들이 테스트가 전혀 가치 없다고 결론내게 될 것이다.
구글의 테스트 역사
- 구글이 테스트에 대하여 이러한 결론에 도달한 과정을 알아보자.
- 구글 엔지니어들도 처음에는 자동 테스트의 가치를 받아들이지 못했다. 실제로 2005년까지도 테스트 체계가 갖춰지지 않았다. 오히려 호기심 충족에 더 가까운 업무였다. 그마저도 대부분 수동으로 실행했다.
- 하지만 2005~2006년 사이 테스트 혁명이 이루어지면서 오늘날까지도 구글의 정신에 자리 잡고 있다.
- GWS 프로젝트의 경험이 촉매 역할을 했다. 자동 테스트가 얼마나 강력한지를 분명하게 일깨워줬다.
- 자동테스트를 전사적으로 뿌리내리게 한 원동력은 세 가지이다. 오리엔테이션 수업, 테스트 인증 프로그램, 화장실에서도 테스트 이다. 이 각각은 상승효과를 일으켜 구글의 엔지니어링 문화 전체를 재편했다.
오리엔테이션 수업
- 구글 입사 오리엔테이션에 1시간짜리 '자동 테스트' 교육을 추가했다. 입사자들은 그게 구글 표준 관행인 것 처럼 교육받았고, 그 이후 1~2년 만에 오리엔테이션을 거친 엔지니어 수가 기존 엔지니어의 수를 넘어섰다. 그 결과 올바른 토대 위에서 새로 시작하는 프로젝트가 많아졌다.
테스트 인증
- 테스트 인증 프로그램을 만들고 5개 레벨로 나누어 구체적인 조건을 정의했다.
- 레벨1은 자속적 빌드 구축, 코드 커버리지 추적, 모든 테스트를 크기로 구분, 불규칙한 테스트 식별, 바로 실행할 수 있는 빠른 테스트 스위트 마련이다.
- 레벨이 높아질수록 '비결정적인 테스트 제거' 등 더 어려운 조건이 추가된다.
- 레벨5가 되려면 '모든 테스트 자동화', '모든 커밋 전에 빠른 테스트 스위트 수행', '비결정성 완전히 제거', '모든 행위를 테스트'의 조건을 달성해야한다.
- 마지막으로 사내 대시보드에서 모든 팀의 현재 레벨을 보여주어서 은근히 결쟁을 부추겼다.
화장실에서도 테스트(TotT, Testing on the Toilet)
- 화장실에 테스트 개선법을 알려주는 전단지를 붙였다. 초기에는 항의가 있었지만 많은 엔지니어들이 새로운 에피소드를 손꼽아 기다리는 성공적인 프로젝트가 되었다.
- 테스트 문화를 전파하기 위해 한페이지를 절대 넘지 않도록 제한하여 제작자가 가장 중요하고 즉시 실행 가능한 조언에 집중하도록 유도했다.
- TotT는 기술 블로그로 확장되어 더 많은 기업에 좋은 영향을 끼치고 있다.
오늘날의 테스트 문화
- 훌륭한 생각은 반드시 퍼져 나갈 것임을 굳게 믿었다. 테스트를 강제하기보다 테스트가 훌륭한 아이디어임을 입증하는 데 초점을 맞췄다.
- 스스로 테스트를 작성한다는 것은 테스트가 이롭다는 생각을 완전히 받아들였다는 뜻이다. 또한 올바른 일이라고 생각하므로 아무도 강요하지 않더라도 계속할 가능성이 크다는 의미이기도 하다.
자동 테스트의 한계
- 사람의 판단이 필요한 테스트들이 있다.
- 검색 품질이나 음성, 영상의 성능 평가, 보안 취약점 등
- 탐색적 테스팅은 검사 대상을 고장내야 할 퍼즐로 취급하고 의외의 데이터를 입력하거나 예상치 못한 절차로 조작하여 망가뜨리려 시도한다. 그리고 문제를 발견하는 즉시 자동 테스트를 추가하여 문제가 재발하지 않도록 예방한다.
마치며
- 개발자 주도 자동 테스트는 구글에서 가장 혁신적인 소프트웨어 엔지니어링 관행 중 하나이다. 이 덕분에 구글은 더 큰 팀을 구성해 더 큰 시스템을 생각보다 빠르게 구축해냈다. 점점 빨라지는 기술 변화 속도를 따라잡는 데도 도움을 주었다. 테스트를 표준 문화로 끌어올렸다.
- 이번 장의 목적은 구글이 테스트를 어떻게 생각하는지 알려드리는 것이다. 이어지는 3개 장에서는 바람직하고 안정적이고 신뢰할 수 있는 테스트를 작선하는데 도움되는 핵심 주제 몇 가지를 더 깊이 논할 것이다.
12장. 단위 테스트
핵심 정리
- 변하지 않는 테스트를 만들기 위해 노력하라(요구사항이 바뀌지 않는 한 테스트를 수정할 일이 없도록 테스트를 작성한다)
- 공개 API를 통해 테스트하라(내부 메서드를 외부로 공개하지말고, 공개용 set, get 함수를 만들어서 변경된 상태를 확인한다)
- 상호작용이 아닌, 상태를 테스트하라(메서드가 호출되었는지를 확인하지 말고, 시스템의 상태가 변화되었는지를 확인한다)
- 테스트를 완전하고 명확하게 만들라(테스트에 필요한 모든 정보를 테스트에 포함시키고, 불필요한 정보는 테스트에서 제거한다)
- 메서드가 아닌, 행위를 테스트하라(메서드의 로직을 테스트하지 말고, 원하는 행위 하나씩을 테스트하라. given/when/then)
- 행위가 부각되게끔 테스트를 구성하라(테스트 각각은 단 하나의 행위만 다뤄야한다)
- 테스트 이름은 검사하는 행위가 잘 드러나게 지어라(테스트 이름만 보고도 어떤 테스트인지 알 수 있어야한다. 테스트 이름이 should로 시작하면 좋다)
- 테스트에 로직을 넣지 말라(연산자, 반복문, 조건문이 추가되면 테스트가 복잡해진다. 버그가 생기기 쉽다. 직설적이고 서술적인 코드를 작성하라)
- 실패 메시지를 명확하게 작성하라(매개변수, 원하는 결과, 실제 결과를 에러 메시지에 표현하라)
- 테스트들이 코드를 공유할 때는 DRY보다 DAMP를 우선하라(더 이해하기 쉽고 의미있는 테스트를 위해 중복을 허용하라. DRY: Don't Repeat Yourself: 반복하지 말라, DAMP: Descriptive And Meaningful Phrase: 서술적이고 의미 있는 문구)
13장. 테스트 대역
핵심 정리
- 테스트 대역보다는 되도록 실제 구현을 사용해야 한다
- 테스트에서 실제 구현을 사용할 수 없을 때는 가짜 객체가 최선일 때가 많다
- 스텁을 과용하면 테스트가 불명확해지고 깨지기 쉬워진다
- 상호작용 테스트는 되도록 피하는 게 좋다. 상호작용 테스트는 대상 시스템의 상세 구현 방식을 노출하기 때문에 테스트를 깨지기 쉽게 만든다
14장. 더 큰 테스트
핵심 정리
- 더 큰 테스트는 단위 테스트가 다루지 못하는 문제를 책임진다
- 더 큰 테스트는 테스트 대산 시스템, 데이터, 동작, 검증으로 구성된다
- 위험을 식별해주는 테스트 전략과 그 위험을 완화해줄 더 큰 테스트까지 포함해야 좋은 설계이다
- 더 큰 테스트가 개발자 워크플로에 마찰 없이 녹아들도록 관리하려면 더 많이 노력해야한다