앞 편에서 파이프라인은 확인하는 앞부분이랑 내보내는 뒷부분으로 나뉜다고 했잖아요. 이번 편은 그 앞부분, 그러니까 지속 통합 이야기예요. 영어로 줄여 CI라고도 많이 부르죠. 이름은 거창해 보여도, 핵심은 코드를 자주 합치고, 합칠 때마다 자동으로 시험해 문제를 일찍 잡는 거예요. 저는 이 지속 통합을 갖추고 나서 남의 코드랑 내 코드가 부딪혀 깨지는 사고가 확 줄었어요. 그만큼 협업이 편해졌죠.
이번 편에서는 지속 통합이 뭔지, 왜 자주 합치는 게 중요한지, 자동 시험엔 어떤 종류가 있는지, 시험을 어디까지 짜야 하는지, 그리고 사람을 괴롭히는 불안정한 시험을 어떻게 다루는지를 풀어 볼게요. 시험 이야기가 나오면 부담스러워하는 분이 많은데, 완벽하게가 아니라 쓸모 있게라는 마음으로 접근하면 훨씬 가벼워져요.
지속 통합이 대체 뭐예요?
지속 통합은 말 그대로 코드를 계속 합친다는 뜻이에요. 여럿이 각자 작업하다 보면 코드가 갈라지는데, 이걸 자주 하나로 모아 서로 부딪히는 데가 없는지 확인하는 거죠. 그리고 합칠 때마다 자동으로 시험을 돌려 뭔가 깨지지 않았는지 검사해요. 그러니까 지속 통합은 자주 합치기랑 합칠 때마다 자동 검사, 이 두 가지가 짝을 이룬 거예요.
왜 이게 필요하냐면, 코드를 오래 갈라 두면 나중에 합칠 때 지옥이거든요. 각자 몇 주씩 따로 작업한 걸 한꺼번에 합치면, 서로 고친 데가 겹쳐 충돌투성이가 되고 누구 게 맞는지 가리기도 어려워요. 반대로 매일 조금씩 합치면, 부딪힘도 작고 금방 풀리죠. 저는 이걸 작은 아픔을 자주 겪어 큰 아픔을 피한다고 표현해요.
여기서 자동 검사가 붙는 게 핵심이에요. 합치는 것만으론 부족하고, 합친 결과가 여전히 멀쩡히 도는지를 확인해야 하니까요. 사람이 매번 손으로 다 확인할 순 없으니, 시험을 미리 짜 두고 자동으로 돌려 대신 확인하게 하는 거죠. 코드를 올릴 때마다 이 시험이 돌아 괜찮다는 초록불이 켜져야 합쳐지게 만들어요.
저는 지속 통합을 협업의 안전벨트라고 불러요. 내가 고친 게 남의 기능을 망가뜨렸는지, 남이 고친 게 내 코드를 깼는지를 합치는 순간 바로 알려 주니까요. 이게 없으면 서로 모르는 새 망가진 채로 쌓이다, 한참 뒤에 대체 언제부터 깨졌지 하며 뒤늦게 발견해요. 일찍 알면 고치기 쉽고, 늦게 알면 고치기 어렵죠.
재밌는 건, 지속 통합이 거창한 장비가 아니라 습관에 가깝다는 거예요. 아무리 좋은 자동 시험을 갖춰도, 사람들이 코드를 오래 갈라 붙들고 있으면 지속 통합이 아니거든요. 반대로 도구가 소박해도 날마다 조금씩 합치는 팀은 이미 지속 통합을 하고 있는 거예요. 저는 그래서 후배들한테 도구부터 갖추려 하지 말고, 자주 합치는 버릇부터 들이라고 말해요. 자동 시험은 그 습관을 안전하게 받쳐 주는 장치일 뿐, 습관 자체가 먼저예요.
왜 자주 합치는 게 그렇게 중요해요?
자주 합치면 문제가 작을 때 드러나요. 오늘 합쳤는데 시험이 빨개지면, 원인은 오늘 내가 고친 것 안에 있을 가능성이 커요. 어제까진 멀쩡했으니까요. 이렇게 범위가 좁으면 원인을 금방 짚어 고치죠. 반대로 몇 주 만에 합쳐 깨지면, 그 몇 주 치 변경 전부가 용의자라 원인 찾기가 막막해져요. 저는 이 범위를 좁힌다는 게 자주 합치기의 가장 큰 이득이라고 봐요.
자주 합치면 충돌도 작아져요. 같은 부분을 여럿이 고쳤을 때 생기는 충돌은, 갈라진 시간이 길수록 커지고 복잡해지거든요. 매일 합치면 하루 치 차이만 맞추면 되니 금방 끝나요. 저는 예전에 한 달간 따로 작업한 걸 합치다 충돌 푸는 데만 이틀을 쓴 적이 있어요. 그 뒤로 날마다 조금씩 합치는 습관을 들였죠.
또 자주 합치면 팀 전체가 지금 상태를 공유해요. 다들 최신 코드를 자주 받아 가니, 내가 만든 게 남한테도 금방 닿고 남이 만든 것도 내가 금방 받죠. 서로 딴 세상 코드를 붙들고 있다 나중에 놀라는 일이 없어요. 저는 이 같은 그림을 보고 일한다는 감각이, 팀이 손발을 맞추는 데 정말 중요하다고 느껴요.
다만 자주 합치려면 합치는 게 겁나지 않아야 해요. 그래서 자동 시험이 짝을 이루는 거예요. 시험이 합쳐도 안전한지를 확인해 주니, 마음 놓고 자주 합칠 수 있죠. 시험 없이 자주 합치기만 하면 깨진 걸 모른 채 쌓일 뿐이에요. 그래서 저는 이 둘을 한 몸으로 다뤄요. 자주 합치되, 합칠 때마다 반드시 자동으로 확인하는 거죠.
자동 시험은 어떤 종류가 있어요?
자동 시험은 흔히 세 층으로 나눠요. 가장 아래는 작은 조각 하나하나를 따로 시험하는 거예요. 함수 하나에 값을 넣어 기대한 답이 나오는지 확인하는 식이죠. 예를 들어 expect(add(2, 3)).toBe(5)처럼요. 빠르고 많이 짤 수 있어, 시험의 가장 큰 밑바닥을 이뤄요. 저는 계산이나 규칙이 얽힌 부분엔 이 작은 시험을 넉넉히 깔아 둬요.
가운데 층은 여러 조각이 함께 도는지를 보는 시험이에요. 화면이 서버한테 데이터를 청하고 받아 그리는 흐름 전체가 잘 이어지는지 확인하는 거죠. 조각 하나하나는 멀쩡한데 이어 붙일 때 어긋나는 문제를 잡아요. 작은 시험보다 느리고 손이 더 가지만, 실제에 가까운 확인을 해 줘요.
가장 위층은 사용자가 하듯 처음부터 끝까지 훑는 시험이에요. 진짜 브라우저를 띄워 버튼을 누르고 입력하고 사람처럼 써 보는 거죠. 가장 실제 같지만 가장 느리고 잘 깨지기도 해요. 그래서 저는 이 위층 시험은 정말 중요한 길, 이를테면 로그인이나 결제 같은 데만 몇 개 둬요. 다 이걸로 덮으려 하면 너무 느리고 불안정해지거든요.
이 세 층은 아래가 넓고 위가 좁은 모양이 건강해요. 빠르고 안정적인 작은 시험을 잔뜩, 느리고 깨지기 쉬운 위층 시험은 조금만요. 저는 이 균형이 무너져 위층 시험만 잔뜩인 프로젝트를 본 적이 있는데, 시험이 느려터지고 자주 깨져 다들 무시하게 됐더라고요. 층마다 제 역할과 알맞은 양이 있다는 걸 기억하면 좋아요.
어느 층부터 손대야 할지 막막하면, 저는 가운데 층부터 권하는 편이에요. 작은 시험은 너무 잘게 쪼개 정작 중요한 흐름을 놓치기 쉽고, 위층 시험은 너무 무거워 처음부터 부담이거든요. 화면이 데이터를 청해 받아 그리는 실제 흐름 하나를 시험으로 묶어 두면, 적은 노력으로 넓은 안심을 얻어요. 저는 새 기능을 만들 때 이 대표적인 흐름 하나를 먼저 시험으로 걸어 두고, 세부는 필요에 따라 작은 시험으로 메워 가요.
시험을 어디까지 짜야 해요?
시험을 얼마나 촘촘히 짰는지를 재는 숫자가 있어요. 코드의 몇 퍼센트가 시험에 닿았는지를 보는 건데, 이걸 덮개율이라 불러요. 이 숫자가 높으면 안심이 되지만, 저는 숫자 자체를 목표로 삼는 건 경계해요. 100을 채우려고 의미 없는 시험을 억지로 짜면, 숫자는 좋아 보여도 진짜 문제는 못 잡거든요. 시험은 양이 아니라 무엇을 지키느냐가 중요해요.
그래서 저는 깨지면 크게 아픈 곳부터 시험을 깔아요. 돈이 오가는 계산, 로그인 같은 인증, 자주 고쳐 실수가 잦은 부분이요. 반대로 거의 안 바뀌고 깨져도 티가 금방 나는 단순한 화면엔 시험을 덜 둬요. 중요도랑 위험에 따라 시험을 두껍게도 얇게도 까는 거죠. 모든 코드를 똑같이 덮으려 하면 힘만 빠져요.
시험은 또 고칠 때 든든한 뒷배가 돼요. 시험이 잘 깔려 있으면, 코드를 크게 뜯어고쳐도 시험이 초록불이면 안심이거든요. 뭔가 망가졌으면 시험이 빨개져 알려 줄 테니까요. 저는 시험을 미래의 나를 위한 안전망이라 생각해요. 지금 조금 귀찮아도, 나중에 겁 없이 고칠 자유를 주니까요.
다만 시험을 너무 코드에 딱 붙게 짜면 안 돼요. 코드 속 세세한 방식까지 시험이 붙들고 있으면, 방식만 살짝 바꿔도 동작은 그대로인데 시험이 깨져요. 그럼 시험이 발목만 잡는 짐이 되죠. 저는 시험을 짤 때 안이 어떻게 도는지가 아니라 밖에서 뭘 해 주는지를 확인하게 짜요. 그래야 안을 마음껏 고쳐도 시험이 안 깨져요.
불안정한 시험은 어떻게 다뤄요?
시험을 굴리다 보면 됐다 안 됐다 하는 얄미운 시험을 만나요. 코드는 안 바꿨는데 어떤 날은 초록, 어떤 날은 빨강인 거죠. 이런 불안정한 시험은 지속 통합의 가장 큰 적이에요. 왜냐면 사람들이 또 걔 또 그러네 하고 빨간불을 무시하게 만들거든요. 그러다 진짜 문제일 때도 넘겨 버리면, 시험이 있으나 마나가 돼요.
불안정한 시험은 대개 시간이나 순서에 기대는 데서 나와요. 화면이 다 그려지기 전에 확인하려 들거나, 바깥 서비스 응답이 매번 조금씩 다른 걸 안 감안하거나요. 저는 이런 시험을 발견하면, 기다릴 걸 제대로 기다리게 고치거나, 바깥에 기대는 부분을 흉내로 대신하게 바꿔요. 진짜 바깥 서비스 대신 정해진 답을 주는 가짜를 두면, 응답이 흔들려 깨지는 일이 사라지거든요.
당장 못 고치는 불안정한 시험은 잠깐 떼어 두되 잊지 않게 표시해 둬요. 빨간불을 무시하게 두느니, 문제 있는 시험만 따로 빼 나머지 초록불은 믿을 수 있게 하는 거죠. 대신 떼어 둔 걸 방치하면 안 되니, 목록으로 남겨 언젠가 고치게요. 저는 이 떼어 둔 시험 목록을 빚처럼 여기고, 틈날 때 하나씩 갚아요.
무엇보다 저는 빨간불의 권위를 지키려 애써요. 시험이 빨개지면 진짜 문제다라는 믿음이 팀에 있어야, 시험이 힘을 가져요. 불안정한 시험을 방치해 이 믿음이 무너지면, 아무리 시험을 많이 짜도 다들 안 보게 되죠. 그래서 저는 시험 개수보다 시험을 믿을 수 있는가를 더 챙겨요. 믿을 수 있는 시험 열 개가, 못 믿을 시험 백 개보다 나아요.
지속 통합을 잘 굴리는 습관은요?
첫째는 시험을 빠르게 유지하는 거예요. 시험이 오래 걸리면 사람들이 결과를 안 기다리고 넘어가거든요. 저는 빠른 작은 시험은 매번 돌리고, 느린 위층 시험은 덜 자주 돌리게 나눠, 개발자가 금방 답을 받게 해요. 확인이 빨라야 자주 합치는 습관도 유지되니까요. 시험이 여럿이면 서로 관계없는 것끼리 동시에 돌려 시간을 줄이는 것도 좋은 방법이에요. 확인이 몇 초 만에 끝나면, 개발자가 흐름을 안 끊고 계속 일할 수 있거든요.
둘째는 깨지면 그 자리에서 고치는 문화예요. 중심 가지가 빨개졌는데 나중에 하지 하고 미루면, 그 위에 쌓이는 다른 작업까지 다 흔들려요. 저는 깨진 중심 가지 고치기가 새 기능보다 먼저라는 규칙을 좋아해요. 다 같이 쓰는 길이 막혔으면, 그걸 뚫는 게 최우선이거든요. 금방 못 고칠 것 같으면, 차라리 문제의 변경을 잠깐 되돌려 길부터 트고 천천히 원인을 보는 것도 방법이에요.
셋째는 시험을 코드랑 함께 짜는 거예요. 기능을 다 만들고 나중에 몰아서 시험을 짜려 하면, 귀찮아서 자꾸 미루다 결국 안 짜게 돼요. 저는 기능이랑 시험을 같이 올려, 시험 없는 코드가 슬그머니 들어오는 걸 막아요. 새 코드엔 그걸 지키는 시험이 따라오게 하는 거죠.
넷째는 버그를 잡을 때마다 시험을 하나 남기는 거예요. 문제가 터져 고쳤으면, 그 문제를 재현하는 시험을 짜 두는 습관이에요. 그러면 나중에 같은 실수가 다시 스며들어도, 그 시험이 빨간불로 막아 줘요. 저는 이렇게 쌓인 시험들을 과거의 아픔이 남긴 방패라고 불러요. 한 번 아팠던 곳을 두 번은 안 아프게 해 주니까요. 이 습관만 꾸준히 지켜도, 시험이 우리 서비스의 약한 곳을 알아서 촘촘히 덮어 가요.
정리하면, 지속 통합은 자주 합치고 합칠 때마다 자동으로 시험해 문제를 일찍 잡는 거예요. 시험은 작은 것부터 위층까지 알맞은 균형으로 깔되, 숫자가 아니라 무엇을 지키느냐를 보고, 무엇보다 믿을 수 있게 유지해야 해요. 다음 편에서는 이렇게 확인을 통과한 코드를 실제로 내보내는 뒷부분, 지속 배포 이야기로 넘어가 볼게요.