[번역] Continous Integration - Martin Fowler

세니·2024년 3월 9일
post-thumbnail

글을 파파고 + 구글 번역과 함께 의역한 글입니다.
처음으로 번역한 글이라 매우 어색할지도 모르며..^^

원주소: https://martinfowler.com/articles/continuousIntegration.html


Continuous Integration 지속적 통합

지속적 통합은 팀의 각 인원들이 적어도 매일 그들의 변경사항을 코드베이스에 머지하는 소프트웨어 개발 방식입니다.

이러한 각 통합은 가능한 한 빨리 통합 오류를 발견하기 위해 자동화된 빌드 (테스트 포함)를 통해 검증됩니다.

팀은 이 접근 방식을 통해 delivery 지연의 위험을 줄이고, 통합을 하기 위한 노력을 줄이고, 새로운 기능으로 신속한 개선을 위해 건강한 코드베이스를 발전시키는게 가능하다는 걸 알게 되었습니다.


나는 대규모 소프트웨어 프로젝트를 처음 목격한 일 중 하나를 생생하게 기억합니다. 나는 영국의 한 대형 전자회사에서 여름 인턴십을 하고 있었습니다. QA 그룹의 일원인 나의 매니저는 나에게 한 현장을 안내해 주었고 우리는 좁은 방에서 일하는 사람들로 가득한 거대하고 우울하며 창문도 없는 창고에 들어갔습니다. 나는 이 프로그래머들이 몇 년 동안 이 소프트웨어에 대한 코드를 작성해 왔으며 프로그래밍을 완료하는 동안 별도의 유닛이 이제 함께 통합되고 몇 달 동안 통합되어 왔다고 들었습니다. 내 가이드는 통합을 완료하는 데 시간이 얼마나 걸릴지 아무도 모른다고 말했습니다. 이를 통해 저는 소프트웨어 프로젝트의 일반적인 이야기를 배웠습니다. 여러 개발자의 작업을 통합하는 것은 오래 걸리고 예측할 수 없는 프로세스입니다.

수년 동안 이렇게 긴 통합에 갇힌 팀에 대해 들어본 적이 없습니다. 하지만 이러한 통합이 고통 없는 과정이라는 의미는 아닙니다. 개발자는 며칠 동안 새로운 기능에 대해 작업하면서 정기적으로 common main branch의 변경 사항을 자신의 feature branch로 pull 받았을 것 입니다. 그녀가 변경 사항을 push할 준비가 되기 전에 상호 작용하는 일부 코드를 변경하는 큰 변화가 main에 발생합니다. 그녀는 자신의 기능을 마무리하는 것에서 자신의 작업을 이러한 변화와 통합하는 방법을 찾는 것으로 전환해야 합니다. 이는 동료에게는 더 좋지만 그녀에게는 그다지 효과적이지 않습니다. 변경의 복잡성은 애플리케이션을 실행할 때만 표시되어 익숙하지 않은 코드를 디버그 하도록 강요하는 교묘한 결함이 아니라 소스 코드를 병합하는 데 있기를 바랍니다.

적어도 그 시나리오에서, 그녀는 pull request를 제출하기 전에 그 사실을 알게 됩니다. 누군가 변경 사항을 검토할 때까지 기다리는 동안 pull request가 너무 복잡할 수 있습니다. Review하는 데 시간이 걸리므로 다음 기능으로 전 할 수 있습니다. 해당 기간 동안 통합이 어려워지면 매우 당황스러울 수 있으며 review 프로세스가 더 오래 걸릴 수 있습니다. 통합 테스트는 pull request가 병합된 후에만 실행되는 경우가 많기 때문에 이것이 이야기의 끝이 아닐 수도 있습니다.

시간이 지나고, 이 팀은 핵심 코드를 크게 변경하면 이런 종류의 문제가 발생한다는 사실을 알게 되어 해당 작업을 중단할 수 있습니다. 그러나 이는 정기적인 리팩토링을 방지함으로써 결국 코드베이스 전체에 걸쳐 무질서한 내용이 커지는 것을 허용하게 됩니다. 형편없는 코드 기반을 접한 사람들은 어떻게 그런 상태가 되었는지 궁금해 하며, 종종 그 답은 사람들이 그 형편없는 것을 제거하는 것을 방해할 정도로 많은 마찰이 있는 통합 프로세스에 있습니다.

하지만 꼭 그럴 필요는 없습니다. Thoughtworks의 동료들과 전 세계의 많은 사람들이 수행한 대부분의 프로젝트는 통합을 non-event로 취급합니다. 개별 개발자의 작업은 공유 프로젝트 상태에서 불과 몇 시간 거리에 있으며 몇 분 안에 해당 상태로 다시 통합될 수 있습니다. 통합 오류는 신속하게 발견되어 신속하게 수정할 수 있습니다.

이러한 대비는 비싸고 복잡한 도구의 결과가 아닙니다. 핵심은 팀의 모든 구성원이 통제된 소스 코드 저장소에 대해 적어도 매일 자주 통합하는 간단한 관행에 있습니다. 이러한 방식을 "지속적 통합"(또는 Trunk-Based Development 이라고도 함)이라고 합니다.

이번 글에서는 지속적 통합(Continuous Integration)이 무엇인지, 그리고 이를 잘 수행하는 방법에 대해 설명합니다. 제가 이 글을 쓴 이유는 두 가지입니다. 첫째로, 이 분야에는 항상 새로운 사람들이 들어오고 있으며, 나는 그들이 우울한 창고를 피할 수 있는 방법을 보여주고 싶습니다. 그러나 두 번째로 지속적인 통합은 오해가 많은 개념이기 때문에 이 주제에 대한 명확성이 필요합니다. 지속적인 통합을 수행하고 있다고 말하는 사람들이 많지만 워크플로우를 설명하고 나면 중요한 부분이 누락되어 있다는 것이 분명해집니다. 지속적 통합에 대한 명확한 이해는 의사소통에 도움이 되므로 업무 방식을 설명할 때 무엇을 기대해야 할지 알 수 있습니다. 또한 사람들이 자신의 경험을 개선하기 위해 할 수 있는 일이 더 있다는 것을 깨닫는 데도 도움이 됩니다.

나는 원래 이 기사를 2001년에 썼고 2006년에 업데이트했습니다. 그 이후로 소프트웨어 개발 팀의 일반적인 성과에 많은 변화가 있었습니다. 1980년대에 보았던 수개월 간의 통합은 먼 기억이며, 버전 제어, 빌드 스크립트와 같은 기술이 일반화되었습니다. 나는 지속적인 통합의 가치를 확인하기 위한 20년의 경험을 바탕으로 당시 개발 팀보다 더 잘 다루기 위해 2023년에 이 기사를 다시 작성했습니다.


지속적 통합으로 기능 구축

지속적인 통합이 무엇인지, 어떻게 작동하는지 설명하는 가장 쉬운 방법은 작은 기능 개발과 함께 작동하는 방식에 대한 간단한 예를 보여주는 것입니다. 저는 현재 마법 물약의 주요 제조업체와 같이 일하고 있고, 물약의 효과가 얼마나 오래 지속될지 계산하기 위해 제품 품질 시스템을 확장하고 있습니다. 우리 시스템에는 이미 12개의 물약이 지원되고 있으며 비행 물약에 대한 논리를 확장해야 합니다. (우리는 너무 일찍 마모 되면 고객 유지에 심각한 영향을 미친다는 것을 알게 되었습니다.) 비행 물약은 처리해야 할 몇 가지 새로운 요소를 소개하며, 그 중 하나는 2차 혼합 중 달의 위상입니다.

내 로컬 개발 환경에서 최신 제품 소스의 복사본을 가져오는 것으로 시작했습니다. git pull을 이용해 중앙 리포지터리에서 현재 메인 라인으로 check out을 했습니다.

소스가 내 환경에 있으면 명령을 실행하여 제품을 빌드 합니다. 이 명령은 내 환경이 올바르게 설정되었는지 확인하고, 소스를 실행 가능한 제품으로 컴파일하고, 제품을 시작하고, 이에 대해 포괄적인 테스트 제품군을 실행합니다. 새 기능을 추가하는 방법을 결정하기 위해 코드를 살펴보는 동안 이 작업은 몇 분 밖에 걸리지 않습니다. 이 빌드는 거의 실패하지 않지만 만약 실패할 경우 변경 하기 전에 알고 싶기 때문에 만일을 대비해 수행합니다. 실패한 빌드 위에 변경 사항을 적용하면 실패의 원인이 바로 내 변경 사항이라고 생각하여 혼란스러워질 것입니다.

이제 작업 복사본을 가지고 실패한 빌드를 처리하기 위해 필요한 모든 작업을 수행합니다. 이는 제품 코드 변경과 일부 자동화된 테스트 추가 또는 변경으로 구성됩니다. 그 시간 동안 자동화된 빌드와 테스트를 자주 실행합니다. 한 시간 정도 후에 논리를 통합하고 테스트를 업데이트했습니다.

이제 변경 사항을 중앙 리포지터리에 다시 통합할 준비가 되었습니다. 이를 위한 첫 번째 단계는 다시 pull 받는것 입니다. 왜냐하면 제가 작업하는 동안 동료들이 변경 사항을 메인 라인에 push 했을 가능성이 있기 때문입니다. 실제로 그러한 변경 사항이 몇 가지 있는데, 이를 내 작업 복사본에 pull 받고, 그 위에 변경 사항을 결합하고 빌드를 다시 실행합니다. 일반적으로 불필요한 것처럼 느껴지지만 이번에는 테스트가 실패합니다. 테스트를 통해 무엇이 잘못 되었는지에 대한 단서를 얻을 수 있지만, 무엇이 변경되었는지 확인하기 위해 가져온 커밋을 살펴보는 것이 더 유용하다는 것을 알았습니다. 누군가 함수를 조정하여 해당 논리 중 일부를 다시 요청한 듯 합니다. 그들은 메인라인 코드를 수정하고 변경 사항에 대해 다시 요청했지만, 아직 볼 수 없었습니다. 동일한 조정을 수행하고 이번에는 통과한 빌드를 다시 실행합니다.

몇 분 동안 정리하고 있었기 때문에 다시 pull를 받고 새로운 커밋이 생겼습니다. 그러나 이 빌드에서는 잘 동작하므로 내 변경 사항을 중앙 리포지터리에 git push 할 수 있습니다.

그러나 push했다고 끝난게 아닙니다. 메인라인으로 푸시하면 Continuous Integration Service가 내 커밋을 확인하고 변경된 코드를 CI 에이전트로 체크아웃한 후 거기에 빌드합니다. 내 환경에서는 빌드가 괜찮았기 때문에 CI 서비스에서는 실패할 것이라고 기대하지 않지만, "내 컴퓨터에서 작동한다"는 말이 프로그래머 집단에서 잘 알려진 이유가 있습니다. CI 서비스 빌드가 실패하는 원인이 되는 사항이 누락되는 경우는 거의 없지만, 그것이 전혀 발생하지 않는 것과는 다릅니다.

통합 시스템의 빌드는 오래 걸리지 않지만 열정적인 개발자가 비행 시간 계산의 다음 단계에 대해 생각하기 시작할 만큼 충분히 깁니다. 하지만 저는 늙은이이기 때문에 몇 분 동안 다리를 쭉 뻗고 이메일을 읽습니다. 곧 CI 서비스로부터 모든 것이 잘되었다는 알림을 받게 되므로 다음 변경사항을 위한 프로세스를 다시 시작합니다.


지속적인 통합의 실천

위의 이야기는 일반 프로그래머가 작업하는 것과 같은 느낌을 줄 수 있는 지속적인 통합의 예입니다. 하지만 다른 일과 마찬가지로 일상 업무에서도 이 작업을 수행할 때 정리해야 할 사항이 꽤 많습니다. 이제 우리가 해야 할 주요 사례를 살펴보겠습니다.

모든 것을 버전 관리 메인라인에 넣습니다.

요즘 거의 모든 소프트웨어 팀은 소스 코드를 버전 제어 시스템에 보관하므로 모든 개발자는 제품의 현재 상태뿐만 아니라 제품에 적용된 모든 변경 사항을 쉽게 찾을 수 있습니다. 버전 제어 도구를 사용하면 시스템을 개발 중 어느 시점으로든 롤백할 수 있습니다. 이는 Diff 디버깅을 사용하여 버그를 찾아 시스템의 기록을 이해하는 데 매우 도움이 될 수 있습니다. 이 글을 쓰는 동안 주요 버전 제어 시스템은 git 입니다 .

그러나 버전 제어가 일반적이지만 일부 팀에서는 버전 제어를 최대한 활용하지 못합니다. 전체 버전 제어에 대한 테스트는 매우 최소한으로 구성된 환경(예: 기본 운영 체제만 설치된 랩탑)을 사용할 수 있어야 하고 저장소를 복제한 후 제품을 쉽게 빌드하고 실행할 수 있어야 한다는 것입니다. 이는 저장소가 제품 소스 코드, 테스트, 데이터베이스 스키마, 테스트 데이터, 구성 파일, IDE 구성, 설치 스크립트, 써드파티 라이브러리 및 소프트웨어 구축에 필요한 모든 도구를 안정적으로 반환해야 함을 의미합니다.

내가 저장소가 이러한 요소를 모두 반환 해야 한다고 말한 것을 눈치챘을 것입니다 . 이는 요소를 저장하는 것과는 다릅니다. 컴파일러를 저장소에 저장할 필요는 없지만 올바른 컴파일러를 얻을 수 있어야 합니다. 작년 제품 소스를 확인해 보면 지금 사용하고 있는 버전이 아닌 작년에 사용하던 컴파일러로 빌드할 수 있어야 할 수도 있습니다. 저장소는 불변 asset 저장소에 대한 링크를 저장하여 이를 수행할 수 있습니다. 일단 asset이 ID와 함께 저장되면 항상 해당 자산을 정확히 다시 가져오게 된다는 점에서 불변입니다. asset 저장소를 신뢰하고 항상 "최신 버전"이 아닌 특정 버전을 참조한다면 라이브러리 코드를 사용하여 이 작업을 수행할 수도 있습니다.

비디오와 같이 너무 큰 것과 유사한 asset 저장 스키마를 사용할 수 있습니다. 저장소를 복제한다는 것은 필요하지 않더라도 모든 것을 가져오는 것을 의미하는 경우가 많습니다. asset 저장소에 대한 참조를 사용하여 빌드 스크립트는 특정 빌드에 필요한 것만 다운로드하도록 선택할 수 있습니다.

일반적으로 우리는 빌드하는 데 필요한 모든 것을 소스 제어에 저장해야 하지만 실제로 빌드하는 것은 아무것도 저장하지 않습니다. 어떤 사람들은 빌드 제품을 소스 제어에 유지하지만 나는 그것이 이상하다고 (Code Smell) 생각합니다. 이는 더 깊은 문제, 일반적으로 빌드를 안정적으로 다시 생성할 수 없음을 나타내는 것입니다. 빌드 제품을 캐시하는 것이 유용할 수 있지만 항상 일회용으로 처리해야 하며, 일반적으로 사용하지 말아야 할 때 사람들이 의존하지 않도록 즉시 제거하는 것이 좋습니다.

이 원칙의 두 번째 요소는 특정 작업에 대한 코드를 쉽게 찾을 수 있어야 한다는 것입니다. 그 중 일부는 저장소와 더 넓은 기업 내에서 명확한 이름과 URL 체계입니다. 이는 또한 버전 제어 시스템 내에서 사용할 branch를 파악하는 데 시간을 소비할 필요가 없음을 의미합니다. 지속적인 통합은 명확한 메인라인 (제품의 현재 상태 역할을 하는 단일 공유 분기)을 갖는 데 의존합니다. 이는 프로덕션에 배포될 다음 버전입니다.

git을 사용하는 팀은 주로 메인라인 브랜치에 "main"이라는 이름을 사용하지만 때때로 "trunk" 또는 "master"라는 이전 기본값을 볼 수도 있습니다. 메인라인은 중앙 저장소의 branch이므로 메인라인에 커밋을 추가하려면 main먼저 로컬 복사본에 커밋한 다음 해당 커밋을 중앙 서버에 푸시해야 합니다. tracking branch(예: origin/main)는 내 로컬 컴퓨터에 있는 메인라인의 복사본입니다. 그러나 지속적인 통합 환경에서는 매일 메인 라인에 푸시되는 커밋이 많기 때문에 최신 정보가 아닐 수 있습니다.

가능한 한 텍스트 파일을 사용하여 제품과 환경을 정의해야 합니다. 내가 이렇게 말하는 이유는 버전 관리 시스템이 텍스트가 아닌 파일을 저장하고 추적할 수 있지만 일반적으로 버전 간 차이를 쉽게 확인할 수 있는 기능을 제공하지 않기 때문입니다. 이로 인해 어떤 변경이 이루어졌는지 이해하기가 훨씬 더 어려워집니다. 미래에는 의미 있는 diff를 생성할 수 있는 기능을 갖춘 더 많은 저장 형식을 보게 될 가능성이 있지만 현재 명확한 diff는 거의 전적으로 텍스트 형식으로 되어 있습니다. 거기에서도 이해 가능한 차이점을 생성하는 텍스트 형식을 사용해야 합니다.


빌드 자동화

소스 코드를 실행 중인 시스템으로 전환하는 것은 컴파일, 파일 이동, 데이터베이스에 스키마 로드 등을 포함하는 복잡한 프로세스가 될 수 있습니다. 그러나 소프트웨어 개발 분야에 있는 대부분의 작업과 마찬가지로 자동화될 수 있으므로 결과적으로 자동화되어야 합니다. 사람들에게 이상한 명령을 입력하라고 요청하거나 대화 상자를 클릭하는 것은 시간 낭비이자 실수입니다.

대부분의 현대 프로그래밍 환경에는 빌드 자동화를 위한 도구가 포함되어 있으며 이러한 도구는 오랫동안 사용되어 왔습니다. 나는 최초의 Unix 도구 중 하나인 make 를 통해 처음으로 이러한 기능을 접했습니다.

빌드에 대한 모든 지침은 저장소에 저장되어야 하며 실제로 이는 텍스트 표현을 사용해야 함을 의미합니다. 그렇게 하면 쉽게 검사하여 작동 방식을 확인할 수 있고 결정적으로 변경 시 차이점을 확인할 수 있습니다. 따라서 지속적인 통합을 사용하는 팀은 빌드를 수행하거나 환경을 구성하기 위해 UI에서 클릭해야 하는 도구를 피합니다.

일반 프로그래밍 언어를 사용하여 빌드를 자동화하는 것이 가능합니다. 실제로 간단한 빌드는 종종 쉘 스크립트로 작성됩니다. 그러나 빌드가 더욱 복잡해짐에 따라 빌드 자동화를 염두에 두고 설계된 도구를 사용하는 것이 더 좋습니다. 부분적으로는 이러한 도구에 일반적인 빌드 작업을 위한 기능이 내장되어 있기 때문입니다. 그러나 주된 이유는 논리를 구성하는 특정 방식인 빌드 도구,  Dependency Network 라고 부르는 대체 계산 모델에서 가장 잘 작동하기 때문입니다 . Dependency Network는 논리를 종속성 그래프로 구성된 작업으로 구성합니다.

사소하고 단순한 종속성 네트워크에서는 "테스트" 작업이 "컴파일" 작업에 종속되어 있다고 말할 수 있습니다. 테스트 작업을 호출하면 컴파일 작업을 실행해야 하는지 확인하고, 그렇다면 먼저 호출합니다. 컴파일 작업 자체에 종속성이 있는 경우 네트워크는 이를 먼저 호출해야 하는지 확인하고 종속성 체인을 따라 거꾸로 호출해야 합니다. 작업에 오랜 시간이 걸리고 필요하지 않으면 낭비되는 경우가 많기 때문에 종속성 네트워크는 빌드 스크립트에 유용합니다. 마지막으로 테스트를 실행한 이후 아무도 소스 파일을 변경하지 않았다면 잠재적으로 긴 컴파일 시간을 절약할 수 있습니다.

작업을 실행해야 하는지 확인하는 가장 일반적이고 간단한 방법은 파일 수정 시간을 살펴보는 것입니다. 컴파일에 대한 입력 파일 중 하나라도 출력보다 나중에 수정된 경우 해당 작업이 호출되면 컴파일을 실행해야 한다는 것을 알 수 있습니다.

일반적인 실수는 자동화된 빌드에 모든 것을 포함하지 않는 것입니다. 빌드에는 저장소에서 데이터베이스 스키마를 가져오고 실행 환경에서 실행하는 것이 포함되어야 합니다. 이전의 경험 법칙을 자세히 설명하겠습니다. 누구나 깨끗한 시스템을 가져올 수 있어야 하고, 저장소에서 소스를 확인하고, 단일 명령을 실행하고, 자신의 환경에서 시스템을 실행할 수 있어야 합니다.

간단한 프로그램은 빌드하는 데 한두 줄의 스크립트 파일만 필요할 수 있지만 복잡한 시스템에는 빌드에 필요한 시간을 최소화하도록 세밀하게 조정된 큰 종속성 그래프가 있는 경우가 많습니다. 예를 들어, 이 웹사이트에는 천 개가 넘는 웹페이지가 있습니다. 내 빌드 시스템은 이 페이지의 소스를 변경하면 이 페이지 하나만 빌드하면 된다는 것을 알고 있습니다. 하지만 publication 도구 체인의 핵심 파일을 변경하면 모두 다시 빌드해야 합니다. 어느 쪽이든 편집기에서 동일한 명령을 호출하면 빌드 시스템이 수행할 작업의 양을 파악합니다.

필요한 것이 무엇인지에 따라 다양한 종류의 것들이 필요할 수 있습니다. 테스트 코드가 있거나 없거나, 또는 다양한 테스트 세트를 사용하여 시스템을 구축할 수 있습니다. 일부 구성 요소는 독립적으로 구축될 수 있습니다. 빌드 스크립트를 사용하면 다양한 경우에 대한 대체 타겟을 빌드할 수 있습니다.


빌드 자체 테스트 수행

전통적으로 빌드는 프로그램을 실행하는 데 필요한 컴파일, 링크 및 모든 추가 작업을 의미했습니다. 프로그램이 실행될 수 있지만 이것이 올바른 작업을 수행한다는 의미는 아닙니다. 현대의 정적으로 유형이 지정된 언어는 많은 버그를 잡을 수 있지만 훨씬 더 많은 버그가 그 그물을 통과합니다. 지속적인 통합이 요구하는 만큼 자주 통합하려는 경우는 중요한 문제입니다. 버그가 제품에 유입되면 빠르게 변화하는 코드 기반에서 버그 수정을 수행해야 하는 어려운 작업에 직면하게 됩니다. 수동 테스트는 변경 빈도에 대처하기에는 너무 느립니다.

이런 상황에 직면했을 때 우리는 애초에 제품에 버그가 들어가지 않도록 해야 합니다. 이를 수행하는 주요 기술은 가능한 한 많은 버그를 제거하기 위해 각 통합 전에 실행되는 포괄적인 테스트 모음입니다. 물론 테스트가 완벽하지는 않지만 유용할 만큼 많은 버그를 잡을 수 있습니다. 제가 사용했던 초기 컴퓨터는 부팅 시 눈에 보이는 메모리 자체 테스트를 수행했는데, 이로 인해 이를 자체 테스트 코드 라고 부르게 되었습니다 .

자체 테스트 코드 작성은 프로그래머의 작업 흐름에 영향을 미칩니다. 모든 프로그래밍 작업은 프로그램 기능 수정과 테스트 제품군을 보강하는 것을 모두 결합합니다. 프로그래머의 작업은 새로운 기능이 작동할 때만 끝나는 것이 아니라 이를 증명하기 위한 자동화된 테스트가 있을 때도 완료됩니다.

이 기사의 첫 번째 버전 이후 20년 동안 저는 프로그래밍 환경에서 프로그래머가 이러한 테스트 제품군 구축하기 위한 도구를 제공해야 할 필요성을 점점 더 많이 수용하는 것을 보았습니다. 이에 대한 가장 큰 추진력은 원래 Kent Beck과 Erich Gamma가 작성한 JUnit으로, 1990년대 후반 Java 커뮤니티에 뚜렷한 영향을 미쳤습니다. Xunit 프레임워크 라고 불리는 다른 언어에 대한 유사한 테스트 프레임워크에 영감을 주었습니다 . 이는 프로그래머가 제품 코드와 함께 테스트를 쉽게 구축할 수 있도록 하는 가볍고 프로그래머 친화적인 메커니즘을 강조했습니다. 이러한 도구에는 테스트가 통과하면 녹색이지만 실패하면 빨간색으로 바뀌는 일종의 그래픽 진행 표시줄이 있는 경우가 많습니다. 이는 "녹색 빌드" 또는 "빨간색 막대"와 같은 문구로 이어집니다.

이러한 테스트 제품군의 테스트는 녹색이면 제품에 심각한 버그가 없다는 것을 확신해야 한다는 것입니다. 나는 줄을 주석으로 처리하거나 조건문을 뒤집는 등 제품 코드를 간단하게 수정할 수 있지만 테스트를 변경할 수는 없는 장난꾸러기 임프를 상상하고 싶습니다. 사운드 테스트에서는 테스트가 빨간색으로 바뀌지 않고는 임프가 손상을 입히는 것을 절대 허용하지 않습니다. 테스트 실패는 빌드 실패로 충분합니다. 99.9% 녹색은 여전히 빨간색입니다.

자체 테스트 코드는 지속적인 통합에 매우 중요하므로 필수 전제 조건입니다. 지속적인 통합을 구현하는 데 있어 가장 큰 장벽은 종종 테스트 기술이 부족하다는 것입니다.

자체 테스트 코드와 지속적 통합이 서로 긴밀하게 연결되어 있다는 것은 놀라운 일이 아닙니다. 지속적인 통합은 본래 익스트림 프로그래밍 의 일부로 개발되었으며 테스트는 항상 익스트림 프로그래밍의 핵심 사례였습니다. 테스트 주도 개발 (TDD) 의 형태로 수행되는 경우가 많습니다 . 이는 직전에 작성한 테스트를 수정하지 않는 한 새 코드를 작성하지 말라고 지시하는 방식입니다. TDD는 통합 전에 완료되는 한 프로덕션 코드 후에 테스트를 작성할 수 있으므로 지속적 통합에 필수적인 것은 아닙니다. 하지만 대부분의 경우 TDD가 자체 테스트 코드를 작성하는 가장 좋은 방법이라는 것을 알았습니다.

테스트는 코드 베이스의 상태를 자동으로 확인하는 역할을 하며, 테스트는 코드 자동 확인의 핵심 요소이지만 많은 프로그래밍 환경에서는 추가 확인 도구를 제공합니다. Linters 잘못된 프로그래밍 관행을 감지하고 코드가 팀이 선호하는 형식 지정 스타일을 따르도록 보장하며, 취약성 스캐너는 보안 약점을 찾아낼 수 있습니다. 팀은 이러한 도구를 평가하여 검증 프로세스에 포함시켜야 합니다.

물론 테스트만으로 모든 것을 찾을 수는 없습니다. 흔히 말했듯이, 테스트는 버그가 없음을 증명하지 않습니다. 그러나 완벽함이 자체 테스트 빌드에 대한 투자금을 회수하는 유일한 지점은 아닙니다. 자주 실행되는 불완전한 테스트는 전혀 작성되지 않은 완벽한 테스트보다 훨씬 낫습니다.


모두가 매일 메인라인에 커밋을 푸시합니다.

통합은 주로 의사소통에 관한 것입니다. 통합을 통해 개발자는 자신이 변경한 내용을 다른 개발자에게 알릴 수 있습니다. 빈번한 의사소통을 통해 사람들은 변화가 진행됨에 따라 신속하게 알 수 있습니다.

메인라인에 commit하는 개발자의 전제 조건 중 하나는 코드를 올바르게 빌드할 수 있다는 것입니다. 물론 여기에 빌드 테스트 통과도 포함됩니다. 모든 커밋 주기와 마찬가지로 개발자는 먼저 작업 복사본을 메인라인과 일치하도록 업데이트하고 메인라인과의 충돌을 해결한 다음 로컬 컴퓨터에서 빌드합니다. 빌드가 통과되면 자유롭게 메인라인으로 푸시할 수 있습니다.

모든 사람들이 메인라인으로 자주 푸시하면 개발자는 두 개발자 사이에 충돌이 있는지 빨리 알아낼 수 있습니다. 문제를 빠르게 해결하는 열쇠는 문제를 빠르게 찾는 것입니다. 개발자가 몇 시간마다 커밋하면 충돌이 발생한 후 몇 시간 내에 충돌이 감지될 수 있으며, 그 시점에서는 많은 일이 발생하지 않고 해결하기 쉽습니다. 몇 주 동안 감지되지 않은 충돌은 해결하기가 매우 어려울 수 있습니다.

코드베이스의 충돌은 다양한 형태로 발생합니다. 가장 쉽게 찾고 해결하는 것은 두 명의 개발자가 동일한 코드 조각을 서로 다른 방식으로 수정할 때 발생하는 "merge conflict 병합 충돌"이라고 불리는 텍스트 충돌입니다. 버전 관리 도구는 두 번째 개발자가 업데이트된 메인라인을 작업 복사본으로 가져오면 이를 쉽게 감지합니다. 더 어려운 문제는 의미 충돌 입니다 . 동료가 함수 이름을 변경하고 내가 새로 추가한 코드에서 해당 함수를 호출하면 버전 제어 시스템이 도움을 줄 수 없습니다. 정적으로 유형이 지정된 언어에서는 컴파일 오류가 발생하는데 이는 감지하기 매우 쉽지만 동적 언어에서는 그러한 도움을 받을 수 없습니다. 그리고 동료가 내가 호출하는 함수의 본문을 변경하여 함수의 내용을 미묘하게 변경하는 경우 정적으로 유형이 지정된 컴파일도 도움이 되지 않습니다. 이것이 바로 자체 테스트 코드를 갖는 것이 중요한 이유입니다.

테스트 실패는 변경 사항 간에 충돌이 있음을 경고하지만 여전히 충돌이 무엇인지, 해결 방법을 파악해야 합니다. 커밋 사이에 변경 사항이 적용되는 시간은 몇 시간에 불과하므로 문제가 숨어 있을 수 있는 곳이 너무 많습니다. 또한 변경된 사항이 많지 않으므로 Diff 디버깅을 사용하여 버그를 찾는 데 도움을 받을 수 있습니다.

내 일반적인 경험 법칙은 모든 개발자가 매일 메인라인에 전념해야 한다는 것입니다. 실제로 지속적 통합 경험이 있는 사람들은 그보다 더 자주 통합합니다. 통합을 더 자주 할수록 충돌 오류를 찾아야 하는 위치가 줄어들고 충돌을 더 빨리 해결할 수 있습니다.

빈번한 커밋은 개발자가 작업을 각각 몇 시간씩 작은 단위로 나누도록 장려합니다. 이를 통해 진행 상황을 추적하고 파악할 수 있습니다. 사람들은 처음에 단 몇 시간 만에 의미 있는 일을 할 수 없다고 생각하는 경우가 많습니다. 그러나 우리는 멘토링과 연습이 학습에 도움이 된다는 것을 알게 되었습니다.


메인라인으로 푸시할 때마다 빌드가 트리거되어야 합니다.

팀의 모든 사람이 적어도 매일 통합한다면 이는 메인라인이 건강한 상태를 유지한다는 의미입니다. 그러나 실제로는 여전히 문제가 발생합니다. 이는 규율의 소홀함, 푸시 전 업데이트 및 빌드 무시로 인해 발생할 수 있으며, 개발자 작업 공간 간에 환경적 차이가 있을 수도 있습니다.

따라서 우리는 모든 커밋이 참조 환경에서 검증되었는지 확인해야 합니다. 이를 수행하는 일반적인 방법은 메인라인을 모니터링하는 Continuous Integration Service (CI 서비스)를 사용하는 것입니다. ****(CI 서비스의 예로는 Jenkins, GitHub Actions, Circle CI 등과 같은 도구가 있습니다.) 메인라인이 커밋을 받을 때마다 CI 서비스는 메인라인의 헤드를 통합 환경으로 체크아웃하고 전체 빌드를 수행합니다. 이 통합 빌드가 녹색인 경우에만 개발자는 통합이 완료된 것으로 간주할 수 있습니다. 푸시할 때마다 빌드가 이루어지도록 함으로써 오류가 발생하는 경우 최신 푸시에 결함이 있다는 것을 알고 수정해야 할 부분을 좁힙니다.

여기서 강조하고 싶은 점은 CI 서비스를 사용할 때 버전 관리 시스템의 참조 인스턴스에 대한 주요 브랜치인 메인라인에서만 사용한다는 것입니다. CI 서비스를 사용하여 여러 브랜치에서 모니터링하고 빌드하는 것이 일반적이지만 통합의 전체 요점은 모든 커밋이 단일 브랜치에 공존하도록 하는 것입니다 . CI 서비스를 사용하여 다양한 분기에 대해 자동화된 빌드를 수행하는 것이 유용할 수 있지만 이는 연속 통합과 동일하지 않으며 연속 통합을 사용하는 팀은 제품의 단일 분기를 모니터링하는 데 CI 서비스만 필요합니다.

요즘 거의 모든 팀이 CI 서비스를 사용하고 있지만 CI 서비스 없이도 지속적 통합을 수행하는 것은 완벽하게 가능합니다 . 팀 구성원은 메인라인 헤드를 통합 시스템으로 수동으로 체크아웃하고 빌드를 수행하여 통합을 확인할 수 있습니다. 그러나 자동화가 자유롭게 가능하다면 수동 프로세스는 별 의미가 없습니다.

(Thoughtworks의 동료들이 지속적인 통합을 위한 많은 오픈 소스 도구, 특히 최초의 CI 서비스인 Cruise Control에 기여했다는 점을 언급하는 것이 적절합니다.)


손상된 빌드를 즉시 수정

지속적 통합은 메인라인이 정상적인 상태로 유지되는 경우에만 작동할 수 있습니다. 통합 빌드가 실패하면 즉시 수정해야 합니다. Kent Beck이 말했듯이 "빌드를 수정하는 것보다 우선순위가 더 높은 작업은 없습니다." 이는 빌드를 수정하기 위해 팀의 모든 사람이 하던 일을 중단해야 한다는 의미는 아니며, 일반적으로 작업을 다시 시작하려면 몇 사람만 필요합니다. 이는 빌드 수정을 긴급하고 우선순위가 높은 작업으로 의식적으로 우선순위를 지정하는 것을 의미합니다.

일반적으로 빌드를 수정하는 가장 좋은 방법은 메인라인에서 최신 커밋을 되돌려 시스템을 마지막정상적인 빌드로 되돌리는 것입니다. 문제의 원인이 즉시 명백하다면 새 커밋으로 직접 수정할 수 있지만, 그렇지 않은 경우 메인라인을 되돌리면 일부 사람들은 별도의 개발 환경에서 문제를 파악할 수 있어 나머지 팀이 계속해서 메인 라인을 작업할 수 있습니다.

일부 팀은 Pending Head (사전 테스트, 지연 또는 게이트 커밋이라고도 함) 를 사용하여 메인라인이 중단될 위험을 모두 제거하는 것을 선호합니다 . CI 서비스는 통합을 위해 메인 라인으로 push한 커밋이 바로 올라가지 않도록 설정해야 합니다. 대신 빌드가 완료될 때까지 다른 브랜치에 배치되고 녹색 빌드 후에 메인라인으로 마이그레이션됩니다. 이 기술은 메인라인 중단에 대한 위험을 방지하지만 효과적인 팀은 빨간색 메인라인을 거의 볼 수 없으며 몇 번 발생하는 경우 그 가시성은 사람들이 이를 피하는 방법을 배우도록 권장합니다.


빌드를 빠르게 유지

지속적 통합의 핵심은 신속한 피드백을 제공하는 것입니다. 오랜 시간이 걸리는 빌드보다 지속적인 통합의 피를 빨아들이는 것은 없습니다. 여기서 나는 긴 체격으로 간주되는 것에 대해 어떤 변덕스러운 노인의 즐거움을 인정해야 합니다. 대부분의 동료들은 한 시간이 걸리는 빌드를 완전히 불합리하다고 생각합니다. 저는 팀들이 그렇게 빨리 만들 수 있기를 꿈꿨던 것을 기억합니다. 그리고 가끔 우리는 여전히 그 속도에 맞춰 빌드하는 것이 매우 어려운 경우에 직면합니다.

그러나 대부분의 프로젝트에서는 10분 빌드라는 XP 지침이 완벽하게 적합합니다. 우리의 현대 프로젝트 대부분은 이를 달성합니다. 이를 실현하기 위해 집중적인 노력을 기울일 가치가 있습니다. 왜냐하면 빌드 시간을 깎아 만든 1분은 각 개발자가 커밋할 때마다 1분씩 절약되기 때문입니다. 지속적인 통합에는 빈번한 커밋이 필요하므로 이로 인해 시간이 많이 걸립니다.

1시간의 빌드 시간을 목표로 하고 있다면 더 빠른 빌드를 달성하는 것이 어려운 일처럼 보일 수 있습니다. 새로운 프로젝트를 진행하면서 일을 빠르게 유지하는 방법을 생각하는 것은 어려울 수도 있습니다. 적어도 엔터프라이즈 애플리케이션의 경우 일반적인 병목 현상은 테스트, 특히 데이터베이스와 같은 외부 서비스와 관련된 테스트라는 것을 알았습니다.

가장 중요한 것은 배포 파이프라인 설정 작업을 시작하는 것입니다 . 배포 파이프라인 ( 빌드 파이프라인 또는 단계적 빌드 라고도 함 ) 의 기본 개념은 실제로 여러 빌드가 순차적으로 수행된다는 것입니다. 메인라인에 대한 커밋은 첫 번째 빌드, 즉 커밋 빌드라고 부르는 것을 트리거합니다. 커밋 빌드는 누군가 커밋을 메인라인에 푸시할 때 필요한 빌드입니다. 커밋 빌드는 신속하게 수행되어야 하는 작업이므로 버그 감지 기능을 저하시키는 여러 가지 지름길을 사용하게 됩니다. 비결은 좋은 커밋 빌드가 다른 사람들이 작업할 수 있을 만큼 안정적이도록 버그 찾기 요구 사항과 속도의 균형을 맞추는 것입니다.

일단 커밋 빌드가 잘 되면 다른 사람들도 안심하고 코드 작업을 할 수 있습니다. 하지만 우리가 시작할 수 있는 더 느린 테스트들이 있습니다. 추가적인 기계들은 더 오랜 시간이 걸리는 추가 테스트 루틴을 빌드에서 실행할 수 있습니다.

이에 대한 간단한 예는 2단계 배포 파이프라인입니다. 첫 번째 단계에서는 가짜 in-memory 데이터베이스 또는 외부 서비스용 스텁과 같이 Test Doubles 로 대체된 느린 서비스를 사용하여 보다 지역화된 단위 테스트인 컴파일 및 테스트를 실행합니다 . 이러한 테스트는 10분 지침 내에서 매우 빠르게 실행될 수 있습니다. 그러나 대규모 상호 작용과 관련된 버그, 특히 실제 데이터베이스와 관련된 버그는 발견되지 않습니다. 두 번째 단계 빌드에서는 실제 데이터베이스에 도달하고 더 많은 엔드투엔드 동작을 포함하는 다양한 테스트 모음을 실행합니다. 이 제품군을 실행하는 데 몇 시간이 걸릴 수 있습니다.

이 시나리오에서 사람들은 첫 번째 단계를 커밋 빌드로 사용하고 이를 기본 CI 주기로 사용합니다. 이차 빌드가 실패하면 '모두 중지'하는 품질이 동일하지 않을 수 있지만 팀은 커밋 빌드를 계속 실행하면서 이러한 버그를 최대한 빨리 수정하는 것을 목표로 합니다. 이차적 빌드는 훨씬 느릴 수 있으므로 커밋할 때마다 실행되지 않을 수 있습니다. 이 경우 가능한 한 자주 실행되어 커밋 단계에서 마지막으로 좋은 빌드를 선택합니다.

이차 빌드에서 버그를 발견하면 커밋 빌드가 다른 테스트를 통해 수행할 수 있다는 신호입니다. 가능한 한 우리는 이후 단계의 실패가 버그를 잡을 수 있는 커밋 빌드의 새로운 테스트로 이어지도록 하여 버그가 커밋 빌드에서 수정된 상태로 유지되도록 하려고 합니다. 이렇게 하면 커밋 테스트가 통과될때마다 강화됩니다. 버그가 나오는 빠르게 실행되는 테스트를 빌드할 방법이 없는 경우가 있으므로 두번째 빌드에서 해당 조건에 대해서만 테스트하기로 결정할 수도 있습니다. 다행히도 대부분의 경우 커밋 빌드에 적절한 테스트를 추가할 수 있습니다.

속도를 높이는 또 다른 방법은 병렬성과 다중 시스템을 사용하는 것입니다. 특히 클라우드 환경을 사용하면 팀이 빌드를 위해 소규모 서버를 쉽게 가동할 수 있습니다.  테스트를 제공하면 합리적이고 독립적으로 실행할 수 있으며, 잘 작성된 테스트를 통해 이러한 방법으로 빠르게 빌드 시간을 가져갈 수 있습니다. 이러한 병렬 클라우드 빌드는 개발자의 사전 통합 빌드에도 가치가 있을 수 있습니다.

더 광범위한 빌드 프로세스를 고려하는 동안 자동화의 또 다른 범주인 종속성과의 상호 작용을 언급할 가치가 있습니다. 대부분의 소프트웨어는 다양한 조직에서 생산된 광범위한 종속 소프트웨어를 사용합니다. 이러한 종속성이 변경되면 제품이 손상될 수 있습니다. 따라서 팀은 기본적으로 다른 팀 구성원인 것처럼 새 버전의 종속성을 자동으로 확인하고 이를 빌드에 통합해야 합니다. 이 작업은 종속성 변경 속도에 따라 자주, 일반적으로 적어도 매일 수행되어야 합니다. 계약 테스트를 실행할 때도 비슷한 접근 방식을 사용해야 합니다 . 이러한 종속성 상호 작용이 빨간색으로 변하는 경우 일반 빌드 실패와 동일한 "라인 중지" 효과는 없지만 조사하고 수정하려면 팀의 즉각적인 조치가 필요합니다.


진행 중인 작업 감추기

Continuous Integration(연속 통합)은 앞으로 조금 진전이 있고 빌드가 정상적인 상태가 되면 바로 통합하는 것을 의미합니다. 사용자가 볼 수 있는 기능이 완전히 구성되고 릴리스 준비가 되기 전에 통합하는 것을 제안하는 경우가 많습니다. 따라서 실제 릴리즈에 있는 미완성 기능의 일부인 코드인 잠재 코드를 처리하는 방법을 고려해야 합니다.

어떤 사람들은 잠재 코드에 대해 걱정합니다. 왜냐하면 그것이 생산이 아닌 품질의 코드를 출시된 실행 파일에 넣는 것이기 때문입니다. 지속적 통합을 수행하는 팀은 메인 라인으로 전송되는 모든 코드가 생산 품질이며 코드를 확인하는 테스트를 거칩니다. 잠재 코드는 생산에서 실행되지 않을 수 있지만 그렇다고 해서 테스트에서 실행되는 것을 막을 수는 없습니다.

우리는 키스톤 인터페이스를 사용하여 새로운 기능에 대한 경로를 제공하는 인터페이스가 코드 베이스에 마지막으로 추가되는 것임을 보장함으로써 코드가 프로덕션에서 실행되는 것을 방지할 수 있습니다. 테스트는 해당 최종 인터페이스 이외의 모든 수준에서 코드를 확인할 수 있습니다. 잘 설계된 시스템에서는 이러한 인터페이스 요소가 최소화되어야 하므로 짧은 프로그래밍 에피소드로 추가하기가 간단해야 합니다.

다크 런칭(Dark Launching)을 사용하여 생산의 일부 변화를 사용자에게 보여주기 전에 테스트할 수 있습니다. 이 기법은 성능에 미치는 영향을 평가하는 데 유용합니다,

키스톤은 잠재 코드의 대부분의 경우를 다루지만, 그렇지 않은 경우에는 특징 플래그를 사용합니다. 특징 플래그는 잠재 코드를 실행하려고 할 때마다 확인되며, 아마도 환경별 구성 파일에 환경의 일부로 설정됩니다. 이렇게 하면 잠재 코드가 테스트를 위해 활성화되지만 운영에서는 비활성화될 수 있습니다. 특징 플래그는 연속 통합을 가능하게 할 뿐만 아니라 A/B 테스트 및 카나리아 릴리스의 런타임 전환을 더 쉽게 해줍니다. 그런 다음 기능이 완전히 릴리스되면 이 로직을 즉시 제거하여 플래그가 코드 기반을 흐리게 만들지 않도록 합니다.

Branch By Abstraction은 잠재 코드를 관리하는 또 다른 기술로, 코드 베이스 내의 대규모 인프라 구 변화에 특히 유용합니다. 본질적으로 이는 변경되는 모듈에 대한 내부 인터페이스를 생성합니다. 그런 다음 인터페이스는 오래된 논리와 새로운 논리 사이를 라우팅할 수 있으며, 시간이 지남에 따라 실행 경로를 점진적으로 대체합니다. 우리는 지속성 플랫폼을 변경하는 것과 같은 일반적인 요소를 전환하기 위해 이러한 작업을 수행하는 것을 보았습니다.

새로운 기능을 도입할 때는 항상 문제가 발생했을 때 롤백할 수 있도록 해야 합니다. 병렬 변경(일명 확장 계약)은 변경 사항을 되돌릴 수 있는 단계로 구분합니다. 예를 들어 데이터베이스 필드의 이름을 변경하는 경우 먼저 새 이름으로 새 필드를 만든 다음 이전 필드와 새 필드에 모두 기록한 다음 기존 필드 모두에 데이터를 복사한 다음 새 필드에서 읽은 다음 이전 필드를 제거합니다. 이러한 단계를 모두 되돌릴 수는 있지만 한 번에 변경하는 경우에는 불가능합니다. 지속적 통합을 사용하는 팀은 이러한 방식으로 변경 사항을 세분화하고 쉽게 되돌릴 수 있도록 합니다.


프로덕션 환경 복제본 테스트

테스트의 요점은 통제된 조건에서 시스템이 프로덕션 환경에서 겪게 될 모든 문제를 제거하는 것입니다. 여기서 중요한 부분은 프로덕션 시스템이 실행되는 환경입니다. 다른 환경에서 테스트하는 경우 모든 차이로 인해 테스트 중에 발생한 일이 프로덕션 환경에서는 발생하지 않을 위험이 발생합니다.

결과적으로 우리는 테스트 환경을 프로덕션 환경과 최대한 정확하게 구하도록 설정하려고 합니다. 동일 버전 데이터베이스 소프트웨어, 운영 체제를 사용하십시오. 프로덕션 환경에 있는 모든 적절한 라이브러리를 시스템에서 실제로 사용하지 않더라도 테스트 환경에 배치합니다. 동일한 IP 주소와 포트를 사용하고 동일한 하드웨어에서 실행하세요.

가상 환경을 사용하면 이전보다 훨씬 쉽게 이 작업을 수행할 수 있습니다. 우리는 컨테이너에서 프로덕션 소프트웨어를 실행하고 개발자의 작업 공간에서도 테스트를 위해 정확하게 동일한 컨테이너를 안정적으로 구축합니다. 이를 수행하는 데 드는 노력과 비용은 가치가 있으며, 환경이 일치하지 않아 발생하는 결함은 단일 버그를 추적하는 것에 비해 비용이 일반적으로 적게 듭니다.

일부 소프트웨어는 다양한 운영 체제 및 플랫폼 버전과 같은 여러 환경에서 실행되도록 설계되었습니다. 배포 파이프라인은 이러한 모든 환경에서 병렬로 테스트하도록 준비해야 합니다.

주의할 점은 프로덕션 환경이 개발 환경만큼 좋지 않을 때입니다. 스마트폰처럼 통신이 좋지 않은 Wi-Fi에 연결된 시스템에서 프로덕션 소프트웨어가 실행되나요? 그런 다음 테스트 환경이 좋지 않은 네트워크 연결과 같은지 확인하십시오.


모든 사람은 어떤 일이 발생하는지 볼 수 있습니다

지속적인 통합은 모두 소통에 관한 것이므로 모든 사람이 시스템의 상태와 변경 사항을 쉽게 확인할 수 있도록 하고 싶습니다.

커뮤니케이션을 하는 데 가장 중요한 것 중 하나는 메인 라인 빌드의 상태입니다. CI 서비스에는 모든 사람이 실행 중인 빌드의 상태를 볼 수 있는 대시보드가 있습니다. 종종 다른 도구와 연결하여 빌드 정보를 슬랙과 같은 내부 소셜 미디어 도구로 알려줍니다. IDE에는 이러한 메커니즘이 포함된 경우가 많기 때문에 개발자는 대부분의 작업에 사용 중인 도구 안에 있는 동안 경고를 받을 수 있습니다. 많은 팀에서 빌드 실패에 대한 알림만 보내지만 성공에 대한 메시지도 보낼 가치가 있다고 생각합니다. 그렇게 하면 사람들이 정기적인 신호에 익숙해지고 빌드 길이에 대한 감각을 얻을 수 있습니다. CI 서버에서만 제공되는 경우에도 매일 "잘 완료"되는 것이 좋다는 사실은 말할 것도 없습니다.

하나의 물리적 공간을 공유하는 팀은 종종 빌드를 위해 일종의 상시 물리적 디스플레이를 가지고 있습니다. 일반적으로 이것은 단순화된 대시보드를 보여주는 큰 화면의 형태를 취합니다. 이것은 종종 메인라인 커밋 빌드의 빨간색/녹색 색상을 사용하여 특히 모든 사람에게 고장 난 빌드를 알리는 데 유용합니다.

제가 오히려 좋아했던 오래된 물리적 디스플레이 중 하나는 빨간색과 초록색 lava 램프를 사용하는 것이었습니다. lava 램프의 특징 중 하나는 한동안 켜지면 거품이 나기 시작한다는 것입니다. 빨간 램프가 켜지면 팀이 거품이 나기 전에 빌드를 수정해야 한다는 아이디어였습니다. 빌드 상태에 대한 물리적 디스플레이는 종종 장난스러워서 팀 작업 공간에 약간의 기발한 개성을 더했습니다. 저는 춤추는 토끼에 대한 좋은 기억이 있습니다.

이러한 디스플레이는 빌드의 현재 상태뿐만 아니라 최근 히스토리에 대한 유용한 정보를 보여줄 수 있으며, 이는 프로젝트 건전성을 나타내는 지표가 될 수 있습니다. 2000년대 초 저는 안정적인 빌드를 만들 수 없었던 과거가 있는 팀과 함께 일했습니다. 우리는 매일 작은 정사각형으로 1년 전체를 표시하는 달력을 벽에 붙였습니다. QA 그룹은 커밋 테스트를 통과한 안정적인 빌드를 하나 받으면 해당 날짜에 녹색 스티커를 붙이거나 그렇지 않으면 빨간색 정사각형을 붙였습니다. 시간이 지남에 따라 달력은 녹색 정사각형이 사라질 때까지 꾸준히 개선된 빌드 프로세스 상태를 보여주었습니다.


배포 자동화

지속적 통합을 수행하려면 커밋 테스트를 실행하기 위한 환경과 배포 파이프라인의 추가 부분을 실행하기 위한 여러 환경이 필요합니다. 하루에 여러 번 이러한 환경 간에 실행 파일을 이동하므로 이 작업을 자동으로 수행하려고 합니다. 따라서 애플리케이션을 모든 환경에 쉽게 배포할 수 있는 스크립트를 보유하는 것이 중요합니다.

가상화, 컨테이너화, 서버리스를 위한 최신 도구를 사용하면 더 나아갈 수 있습니다. 제품을 배포하기 위한 스크립트뿐만 아니라 필요한 환경을 처음부터 구축하기 위한 스크립트도 있습니다. 이렇게 하면 뼈대로 제공되는 기본 환경에서 시작하여 제품 실행에 필요한 환경을 생성하고 제품을 설치하고 실행할 수 있습니다. 이 모든 과정이 완전히 자동으로 이루어집니다. 진행 중인 작업을 숨기기 위해 기능 플래그를 사용하는 환경은 모든 기능 플래그가 켜진 상태로 설정될 수 있으므로 이러한 기능은 모든 내재적 상호 작용으로 테스트될 수 있습니다.

이에 따른 자연스러운 결과는 동일한 스크립트를 사용하여 비슷한 방식으로 쉽게 프로덕션 환경에 배포할 수 있다는 것입니다. 많은 팀이 이러한 자동화를 사용하여 하루에 여러 번 새 코드를 프로덕션에 배포하지만 설사 빈도 수를 줄이더라도 자동 배포는 프로세스 속도를 높이고 오류를 줄이는 데 도움이 됩니다. 또한 테스트 환경에 배포하는 데 사용하는 것과 동일한 기능을 사용하므로 저렴한 비용이 들기도 합니다.

프로덕션에 자동으로 배포하는 경우 편리하다고 생각되는 추가 기능 중 하나는 자동 롤백입니다. 안 좋은 일도 가끔 생기고, 빌드 상태가 안좋게 된다면 빨리 마지막으로 좋았던 상태로 돌아갈 수 있어서 좋습니다. 자동으로 되돌릴 수 있으면 배포의 압박이 많이 줄어들어 사람들이 더 자주 배포하도록 장려하여 사용자에게 새로운 기능을 신속하게 제공할 수 있습니다. Blue Green Deployment 를 사용하면 배포된 버전 간에 트래픽을 이동하여 새 버전을 빠르게 활성화하고 필요한 경우 동일하게 빠르게 롤백할 수 있습니다.

자동화된 배포를 사용하면 Canary Release를 더 쉽게 설정할 수 있으며 전체 사용자에게 릴리스하기 전에 문제를 해결하기 위해 사용자의 하위 집합에 제품의 새 버전을 배포할 수 있습니다.

모바일 애플리케이션은 테스트 환경으로의 배포를 자동화하는 것이 필수적인 좋은 예입니다. 이 경우, 새로운 버전이 앱 스토어의 보호자들에 의해 검토되기 전에 장치에 설치되어 탐색될 수 있습니다. 실제로 모든 장치 바인딩 소프트웨어에는 테스트 장치에 새 버전을 쉽게 적용할 수 있는 방법이 필요합니다.

이와 같은 소프트웨어를 배포할 때 버전 정보가 표시되는지 확인하세요. 화면에는 버전 제어와 다시 연결되는 빌드 ID가 포함되어야 하고, 로그를 통해 실행 중인 소프트웨어 버전을 쉽게 확인할 수 있어야 하며, 버전 정보를 제공하는 API 엔드포인트가 있어야 합니다.


통합 스타일

지금까지 통합에 접근하는 한 가지 방법을 설명했지만, 그것이 보편적이지 않다면 다른 방법도 있을 것입니다. 다른 분류와 마찬가지로 내가 제공하는 모든 분류에는 경계가 모호하지만, 사전 릴리스 통합, 기능 분기 및 지속적 통합의 세 가지 스타일의 처리 통합을 생각하는 것이 유용하다고 생각합니다.

가장 오래된 것은 80년대에 해당 웨어하우스에서 본 것입니다 - 사전 출시 통합. 이는 통합을 소프트웨어 프로젝트의 한 단계인 폭포수 프로세스 의 자연스러운 개념입니다 . 이러한 프로젝트 작업은 unit으로 나누어져 개인 또는 소규모 팀이 수행할 수 있습니다. 각 장치는 소프트웨어의 일부이며 다른 장치와의 상호 작용은 최소화됩니다. 이러한 Unit은 자체적으로 구축되고 테스트됩니다("단위 테스트"라는 용어의 본 사용법). 그런 다음 장치가 준비되면 이를 최종 제품에 통합합니다. 이 통합은 한 번 발생하고 이어서 통합 테스트를 거쳐 릴리스됩니다. 따라서 작업을 생각해보면 모든 사람이 기능에 대해 병렬로 작업하는 단계와 통합을 위한 단일 노력 흐름의 두 단계를 볼 수 있습니다.

이 스타일의 통합 빈도는 일반적으로 소프트웨어의 주요 버전인 릴리스 빈도와 연결되며 일반적으로 몇 달 또는 몇 년 단위로 측정됩니다. 이 팀은 긴급 버그 수정을 위해 다른 프로세스를 사용할 것 이기에 정규 통합 일정과 별도로 릴리스될 수 있습니다.

요즘 통합에 대한 가장 인기 있는 접근 방식 중 하나는 Feature Branches를 사용하는 것입니다 . 이 스타일에서 기능이 이전 접근 방식의 단위와 마찬가지로 개인이나 소규모 팀에 할당됩니다. 그러나 통합하기 전에 모든 단위가 완료될 때까지 기다리는 대신 개발자는 완료되는 즉시 해당 기능을 메인라인에 통합합니다. 일부 팀은 각 기능 통합 후 프로덕션으로 출시하고 다른 팀은 릴리스를 위해 몇 가지 기능을 일괄 처리하는 것을 선호합니다.

기능 브랜치를 사용하는 팀은 일반적으로 모든 사람이 정기적으로 메인라인에서 작업을 수행할 것으로 기대하지만 이는세미통합입니다. Rebecca와 제가 별도의 기능을 작업하는 경우 매일 메인라인에서 작업을 수행할 수 있지만, 우리 중 한 명이 기능을 완료하고 통합하여 메인라인으로 푸시할 때까지 서로의 변경 사항을 볼 수 없습니다. 그런 다음 다른 사람은 다음 번 풀에서 해당 코드를 확인하여 작업 복사본에 통합합니다. 따라서 각 기능이 메인라인에 푸시된 후 다른 모든 개발자는 이 최신 메인라인 푸시를 자체 기능 분기와 결합하는 통합 작업을 수행합니다.

각 개발자가 메인라인의 변경 사항을 자신의 로컬 브랜치에 결합하기 때문에 이것은 단지 세미통합입니다. 개발자가 변경 사항을 푸시하여 또 다른 세미 통합이 발생할 때까지 전체 통합이 발생할 수 없습니다. Rebecca와 제가 둘 다 메인라인에서 동일한 변경 사항을 가져오더라도 우리는 해당 변경 사항만 통합했으며 서로의 브랜치는 통합하지 않았습니다.

지속적인 통합을 통해 매일 우리 모두는 변경 사항을 메인라인으로 푸시하고 다른 모든 사람의 변경 사항을 우리 작업으로 끌어오고 있습니다. 이로 인해 더 많은 통합 작업이 이루어지지만 각 작업의 크기는 훨씬 작습니다. 며칠을 결합하는 것보다 코드 기반에서 몇 시간의 작업을 결합하는 것이 훨씬 쉽습니다.


지속적 통합의 장점

세 가지 통합 스타일의 상대적 장점을 논의할 때 대부분의 논의는 통합 빈도 에 관한 것입니다 . 출시 전 통합과 기능 분기는 모두 서로 다른 빈도로 작동할 수 있으며 통합 스타일을 변경하지 않고도 통합 빈도를 변경할 수 있습니다. 사전 출시 통합을 사용하는 경우 월간 릴리스와 연간 릴리스 사이에는 큰 차이가 있습니다. 기능 분기는 일반적으로 더 높은 빈도로 작동합니다. 여러 단위를 함께 일괄 처리할 때까지 기다리는 것이 아니라 각 기능이 개별적으로 메인라인에 푸시될 때 통합이 발생하기 때문입니다. 팀이 기능 분기를 수행하고 모든 기능이 구축하는 데 하루의 작업 미만인 경우 이는 사실상 지속적인 통합과 동일합니다. 그러나 지속적 통합은 빈도가 높은 스타일로 정의된다는 점에서 다릅니다 . 지속적인 통합은 통합 빈도를 자체 목표로 설정하고 기능 완료 또는 릴리스 빈도에 바인딩하지 않는 점을 만듭니다.

따라서 대부분의 팀이 스타일을 바꾸지 않고 빈도를 높임으로써 아래에서 설명할 요소를 유용하게 개선할 수 있습니다. 기능의 크기를 2개월에서 2주로 줄이면 상당한 이점이 있습니다. 지속적 통합은 고주파 통합을 기준선으로 설정하고 지속 가능한 습관과 관행을 설정할 수 있다는 장점이 있습니다.


delivery 지연의 위험 감소

복잡한 통합을 수행하는 데 걸리는 시간을 추정하는 것은 매우 어렵습니다. 때로는 git에서 병합하는 것이 어려울 수 있지만 모든 것이 잘 작동합니다. 다른 경우에는 빠른 병합이 가능하지만 미묘한 통합 버그를 찾아서 수정하는 데 며칠이 걸립니다. 통합 사이의 시간이 길어질수록, 통합할 코드가 많아지고 시간도 더 오래 걸립니다. 하지만 더 나쁜 것은 예측 불가능성이 증가한다는 것입니다.

이 모든 것이 출시 전 통합을 악몽의 특별한 형태로 만듭니다. 통합은 출시 전 마지막 단계 중 하나이기 때문에 이미 시간이 촉박하고 부담이 큽니다. 예측하기 어려운 단계가 하루 늦게 있다는 것은 완화하기 매우 어려운 위험이 있다는 것을 의미합니다. 그렇기 때문에 제 80년대의 기억력은 강하고, 프로젝트가 통합 버그를 수정할 때마다 두 개가 더 발생하는 통합 지옥에 갇힌 것은 이번이 처음이 아닙니다.

통합 빈도를 높이는 모든 단계는 이러한 위험을 낮춥니다. 수행해야 할 통합이 적을수록 새 릴리스가 준비되기까지 알 수 없는 시간이 줄어듭니다. 기능 분기는 이러한 통합 작업을 개별 기능 스트림에 푸시하여 기능이 준비되자마자 스트림이 메인라인으로 푸시될 수 있도록 도와줍니다.

하지만 이 지점만 남겨두는 것은 중요합니다. 다른 사람이 메인라인으로 푸시하면 기능이 완료되기 전에 몇 가지 통합 작업을 도입합니다. 분기가 분리되어 있기 때문에 한 분기에서 작업하는 개발자는 다른 기능이 푸시할 수 있는 내용과 이를 통합하는 데 필요한 작업량에 대해 잘 파악하기 힘듭니다. 우선 순위가 높은 기능이 통합 지연에 직면할 위험이 있지만 우선 순위가 낮은 기능의 푸시를 방지하여 이를 관리할 수 있습니다.

지속적인 통합은 전달 위험을 효과적으로 제거합니다. 통합은 너무 작아서 일반적으로 설명 없이 진행됩니다. 어색한 통합은 해결하는 데 몇 분 이상 걸리는 통합입니다. 가장 최악의 경우 충돌로 인해 작업이 처음부터 다시 시작되지만 여전히 하루도 안 걸리는 작업이므로 이해 관계자 위원회에 문제가 될 소지가 없습니다. 또한 저희는 소프트웨어를 개발하면서 정기적으로 통합을 수행하고 있기 때문에 문제를 해결할 시간이 더 많고 문제 해결 방법을 연습할 수 있습니다.

팀이 정기적으로 프로덕션 환경으로 출시하지 않더라도 모든 사람이 제품 상태를 정확히 확인할 수 있기 때문에 지속적인 통합이 중요합니다. 출시 전에 수행해야 하는 숨겨진 통합 노력은 없으며 통합에 대한 모든 노력은 이미 시작되었습니다.


통합에 낭비되는 시간 줄이기

통합에 소요된 시간이 통합 규모와 얼마나 일치하는지 측정하는 진지한 연구는 본 적이 없지만, 제가 경험한 일화에 따르면 관계가 선형적이지 않다는 것이 강력하게 시사됩니다. 통합할 코드가 2배 더 많으면 통합을 수행하는 데 4배의 시간이 더 걸릴 가능성이 높습니다. 이는 세 개의 노드를 완전히 연결하려면 세 개의 선이 필요하지만 그 중 네 개를 연결하려면 여섯 개의 선이 필요한 것과 비슷합니다. 통합은 연결에 관한 것이므로 비선형적인 증가가 이루어지며 이는 동료들의 경험에 반영됩니다.

기능 분기를 사용하는 조직에서는 이러한 손실된 시간의 대부분을 개인이 느낍니다. 메인라인에 대한 큰 변화를 리베이스하는 데 몇 시간을 소비하는 것은 실망스럽습니다. 완료된 풀 요청에 대한 코드 검토를 기다리는 데 며칠이 걸렸는데, 대기 기간 동안 또 다른 주요 변경 사항이 발생하여 더욱 실망스럽습니다. 2주 전에 완료된 기능 통합 테스트에서 발견된 문제를 디버깅하기 위해 새로운 기능에 대한 작업을 제쳐두어야 하는 것은 생산성이 저하됩니다.

지속적인 통합을 수행할 때 통합은 일반적으로 이벤트가 아닙니다. 메인라인을 풀다운하고 빌드를 실행한 후 푸시합니다. 내가 새로운 마음으로 작성한 적은 양의 코드가 충돌이 있으면 쉽게 확인할 수 있습니다. 워크플로우는 규칙적이기 때문에 연습하고 가능한 자동화하도록 인센티브를 제공합니다.

이러한 많은 비선형 효과와 마찬가지로 통합은 사람들이 잘못된 교훈을 배우는 함정이 되기 쉽습니다. 어려운 통합은 너무 충격적이어서 팀이 통합을 덜 자주 수행해야 한다고 결정할 수 있으며, 이는 향후 문제를 더욱 악화시킬 뿐입니다.

여기서 일어나는 일은 팀 구성원들 사이에 훨씬 더 긴밀한 협력이 이루어지고 있다는 것입니다. 두 개발자가 상충되는 결정을 내릴 경우 우리는 통합할 때 이를 알아냅니다. 따라서 통합 사이의 시간이 짧을수록 충돌을 감지하는 데 걸리는 시간이 줄어들고 충돌이 너무 커지기 전에 처리할 수 있습니다. 빈도수가 많은 통합으로 우리의 소스 제어 시스템은 다른 방법으로는 말할 수 없는 내용을 전달할 수 있는 소통 채널이 됩니다.


버그 감소

버그 - 자신을 파괴하고 일정과 평판을 엉망으로 만드는 불쾌한 것들입니다. 배포된 소프트웨어의 버그는 사용자를 화나게 만듭니다. 정기적인 개발 중에 발생하는 버그는 방해가 되어 나머지 소프트웨어가 올바르게 작동하도록 하는 것을 더 어렵게 만듭니다.

지속적 통합은 버그를 제거하지는 않지만 버그를 찾아 제거하기가 훨씬 더 쉬워집니다. 이는 빈도수가 많은 통합으로 인한 것이 아니라 자체 테스트 코드의 필수 도입으로 인한 것입니다. 지속적인 통합은 자체 테스트 코드 없이는 작동하지 않습니다. 왜냐하면 적절한 테스트 없이는 정상적 메인라인을 유지할 수 없기 때문입니다. 따라서 지속적인 통합은 정기적인 테스트 방식을 도입합니다. 테스트가 부적절할 경우 팀에서는 신속하게 이를 파악하고 조치를 취할 수 있습니다. semantic 충돌로 인해 버그가 나타나면 통합할 코드의 양이 적기 때문에 쉽게 발견할 수 있습니다. 빈번한 통합은 Diff 디버깅 과도 잘 작동하므로 몇 주 후에 발견된 버그라도 작은 변경으로 범위를 좁힐 수 있습니다.

버그도 누적됩니다. 버그가 많을수록 각 버그를 제거하는 것이 더 어려워집니다. 이것은 부분적으로 우리가 버그 상호작용을 받기 때문인데, 여기서 실패는 여러 결함의 결과로 나타나 각 결함을 찾기 어렵게 만듭니다. 또한 사람들은 버그가 많을 때 그것을 찾고 제거할 에너지가 적습니다. 따라서 Continuous Integration에 의해 강화된 자체 테스트 코드는 결함으로 인한 문제를 줄이는 데 또 다른 기하급수적인 효과를 갖습니다.

이는 많은 사람들이 직관에 반하는 또 다른 현상으로 이어집니다. 변경 사항을 얼마나 자주 도입한다는 것이 버그를 도입한다는 것으로 의미하는지 보고, 사람들은 높은 신뢰성을 가진 소프트웨어를 보유하려면 출시 속도를 늦춰야 한다고 결론을 내렸습니다. 이는 Nicole Forsgren이 이끄는 DORA 연구 프로그램 에 의해 확실하게 반박 되었습니다. 그들은 엘리트 팀이 이러한 변경을 수행했을 때 더 빠르고 더 자주 프로덕션에 배포했으며 실패 발생률이 극적으로 낮다는 것을 발견했습니다. 또한 연구에서는 애플리케이션의 코드 저장소에 3개 이하의 현재 사용중인 분기가 있고, 적어도 하루에 한 번 분기를 메인라인에 병합하고, 코드 동결이나 통합 단계가 없을 때 팀의 성과 수준이 더 높은 것으로 나타났습니다.


지속적인 생산성을 위한 리팩토링

대부분의 팀은 시간이 지남에 따라 코드베이스가 악화된다는 것을 발견합니다. 초기에 결정한 사항은 당시에는 좋았지만 6개월의 작업 후에는 더 이상 최적이 아닙니다. 그러나 팀이 배운 내용을 통합하기 위해 코드를 변경한다는 것은 기존 코드에 깊은 변경 사항을 도입하는 것을 의미하며, 이로 인해 시간이 많이 걸리고 위험이 가득한 어려운 병합이 발생합니다. 모든 사람들은 누군가 미래를 위한 좋은 변화를 만들었지만 며칠 동안 다른 사람의 작업이 중단되었던 때를 모두가 기억합니다. 그러한 경험을 고려할 때, 비록 모든 사람이 구축하기에는 이제 어색하지만, 아무도 기존 코드의 구조를 재작업하고 싶어하지 않으므로 새로운 기능의 제공이 느려집니다.

리팩토링은 이러한 붕괴 과정을 약화시키고 실제로 되돌리는데 필수적 기술입니다. 정기적으로 리팩터링하는 팀은 코드의 작은 동작 보존 변환을 사용하여 코드 베이스의 구조를 개선하는 체계적인 기술을 보유하고 있습니다. 이러한 변환 특성은 버그가 발생할 가능성을 크게 줄여주며, 특히 자체 테스트 코드 기반이 지원되는 경우 빠르게 수행할 수 있습니다. 기회가 있을 때마다 리팩토링을 적용하면 팀은 기존 코드베이스의 구조를 개선하여 새로운 기능을 더 쉽고 빠르게 추가할 수 있습니다.

그러나 이 행복한 이야기는 통합 문제로 인해 수포로 돌아갈 수 있습니다. 2주간의 리팩토링 세션은 코드를 크게 향상시킬 수 있지만 다른 모든 사람들이 지난 2주 동안 이전 구조를 사용하여 작업했기 때문에 병합 시간이 길어집니다. 이로 인해 리팩토링 비용이 엄청나게 높아집니다. 빈번한 통합은 리팩토링을 수행하는 사람과 다른 모든 사람이 정기적으로 작업을 동기화하도록 보장하여 이러한 딜레마를 해결합니다. 지속적 통합을 사용할 때 누군가가 내가 사용하고 있는 핵심 라이브러리를 방해하는 변경을 하는 경우 이러한 변경 사항에 맞게 프로그래밍하는 데 몇 시간만 조정하면 됩니다. 그들이 내 변화의 방향과 충돌하는 일을 한다면 나는 즉시 알 수 있으므로 그들과 대화하여 더 나은 방향을 모색할 수 있는 기회를 가질 수 있습니다.

지금까지 이 기사에서 나는 빈도수가 많은 통합의 장점에 대해 몇 가지 반직관적인 개념을 제기했습니다. 즉, 통합을 더 자주할수록 통합에 소요되는 시간이 줄어들고, 빈번한 통합으로 인해 버그가 줄어든다는 것입니다. 소프트웨어 개발에서 아마도 가장 중요한 반직관적인 개념은 다음과 같습니다. 코드 기반을 건전하게 유지하기 위해 많은 노력을 기울이는 팀은 기능을 더 빠르고 저렴하게 제공한다는 것입니다 . 테스트 작성 및 리팩토링에 투자한 시간은 제공 속도에서 인상적인 수익을 제공하며 지속적인 통합은 팀 환경에서 해당 작업을 수행하는 핵심 부분입니다.


프로덕션으로의 릴리즈는 사업적 결정입니다.

우리가 이해관계자에게 새로 만들어진 기능을 시연하고 있다고 상상해 보세요. 그녀는 이렇게 질문합니다. “이것은 정말 멋지고 비즈니스에 큰 영향을 미칠 것입니다. 이것을 실시간으로 만들려면 얼마나 걸리나요?” 해당 기능이 통합되지 않은 지점에 표시되는 경우, 특히 생산 과정에서 자동화가 부족한 경우 대답은 몇 주 또는 몇 달이 될 수 있습니다. 지속적인 통합은 Release-Ready Mainline 유지할 수 있습니다 . 이는 최신 버전의 제품을 프로덕션에 출시하기로 한 결정이 순전히 비즈니스 결정임을 의미합니다. 이해관계자가 최신 버전을 실제로 보여주기 원한다면 자동화된 파이프라인을 실행하는 데 몇 분밖에 걸리지 않습니다. 이를 통해 소프트웨어 고객은 기능 출시 시기를 보다 효과적으로 제어할 수 있으며 개발 팀과 보다 긴밀하게 협력할 수 있습니다.

지속적인 통합과 출시 준비가 완료된 메인라인은 빈번한 배포에 대한 가장 큰 장벽 중 하나를 제거합니다. 빈번한 배포는 사용자가 새로운 기능을 더 빠르게 얻고, 해당 기능에 대해 더 빠른 피드백을 제공하며, 일반적으로 개발 주기에서 더 협업적으 될 수 있다는 점에서 가치가 있습니다. 이는 고객과 개발 사이의 장벽을 허무는 데 도움이 됩니다. 저는 이 장벽이 성공적인 소프트웨어 개발의 가장 큰 장벽이라고 생각합니다.


지속적인 통합을 사용하면 안되는 경우

이 모든 이점들은 상당히 매력적으로 들립니다. 하지만 저처럼 경험이 많거나(혹은 냉소적인) 사람들은 이점들만 나열된 목록을 항상 의심합니다. 대부분의 것들은 비용이 들고, 아키텍처와 프로세스에 대한 결정은 보통 상충 관계의 문제입니다.

하지만 저는 지속적 통합(Continuous Integration)은 헌신적이고 숙련된 팀이 사용하기에 단점이 거의 없는 드문 경우 중 하나라고 인정합니다. 때때로 일어나는 통합에 의한 비용이 너무 크기 때문에, 거의 모든 팀이 통합 빈도를 높임으로써 이득을 볼 수 있습니다. 이점이 더 이상 쌓이지 않는 한계는 있지만, 그 한계는 며칠이 아닌 몇 시간 단위로 이루어지며, 이것이 바로 지속적 통합의 영역입니다. 자체 테스트 코드, 지속적 통합, 리팩토링 간의 상호 작용은 특히 강력합니다. 우리는 Thoughtworks에서 이 접근 방을 수십 년 동안 사용해왔으며, 우리의 유일한 질문은 이를 더 효과적으로 어떻게 실행할 것인가입니다 - 핵심 접근법은 이미 입증되었습니다.

하지만 지속적 통합 모두에게 적합하다는 의미는 아닙니다. "헌신적이고 숙련된 팀이 사용하기에 단점이 거의 없다"고 말했다는 점을 주목할 필요가 있습니다. 이 두 형용사는 지속적 통합이 좋은 선택이 아닐 수 있는 상황들을 지시합니다.

"헌신적"이라 함은, 풀타임으로 제품에 작업하는 팀을 의미합니다. 이에 대한 좋은 반례로는 한 두 명의 관리자와 많은 기여자가 있는 고전적 오픈 소스 프로젝트가 있습니다. 이런 상황에서는 관리자조차도 프로젝트에 주당 몇 시간만 투자하며, 기여자들은 잘 알지 못하고, 기여자들이 언제 기여하거나 어떤 기준을 따라야 하는지에 대해 잘 파악하지 못합니다. 이러한 환경이 피처 브랜치 워크플로우와 풀 리퀘스트로 이어졌습니다. 이런 맥락에서 지속 통합은 실현 가능하지 않지만, 통합 빈도를 높이려는 노력은 여전히 가치가 있을 수 있습니다.

지속적 통합은 주로 풀타임으로 제품에 작업하는 팀에 더 적합합니다. 이는 일반적으로 상용 소프트웨어에서의 경우와 같습니다. 하지만 고전적인 오픈 소스와 풀타임 모델 사이에는 많은 중간 지점이 있습니다. 우리는 팀의 규칙에 맞는 통합 정책을 사용하는 것에 대해 판단을 내려야 합니다.

두 번째 형용사는 필요한 관행을 따르는 팀의 숙련도를 살핍니다. 강력한 테스트 집합 없이 지속적 통합을 시도하는 팀은 버그를 걸러내는 메커니즘이 없기 때문에 온갖 문제에 부딪힐 것입니다. 자동화하지 않으면 통합에 너무 오랜 시간이 걸려 개발 흐름을 방해할 것입니다. 메인라인에 푸시할 때 빌드가 성공적이었는지를 보장하는 데에 집중하지 않는다면, 메인라인은 항상 고장 날 것이며, 모두의 작업에 방해가 될 것입니다.

지속적 통합을 도입하려는 모든 사람은 이러한 기술을 염두에 두어야 합니다. 자체 테스트 코드 없이 지속적 통합을 시행하는 것은 작동하지 않을 것이며, 잘 수행될 때 지속적 통합이 어떤 것인지에 대해 잘못된 인상을 줄 것입니다.

그렇다고 해서 기술적 요구사항이 특별히 어려운 것은 아닙니다. 이 과정을 팀에서 작동시키기 위해 자유로운 개발자가 필요한 것은 아닙니다. (실제로 자유로운 개발자들은 종종 장애물이 됩니다. 왜냐하면 그런 식으로 자신을 생각하는 사람들은 보통 매우 규칙성이 없기 때문입니다.) 이 기술적 관행을 배우는 것은 그리 어렵지 않으며, 보통 문제는 좋은 스승를 찾고, 규칙을 형성하는 습관을 만드는 것입니다. 팀이 흐름을 익히면, 보통 편안하고, 매끄럽고, 빠르게 느껴집니다.


지속적 통합 소개

지속적 통합과 같은 방식을 도입하는 방법을 설명할 때 어려운 점 중 하나는 어디에서 시작하는지에 따라 크게 달라진다는 것입니다. 이 글을 쓰면서 나는 당신이 어떤 코드를 작업하고 있는지, 당신의 팀이 어떤 기술과 습관을 보유하고 있는지, 더 넓은 조직적 맥락은 말할 것도 없습니다. 여러분이 자신의 길을 찾는 데 도움이 되길 바라며 나 같은 사람이 할 수 있는 일은 몇 가지 일반적인 이정표를 지적하는것 뿐입니다.

새로운 관행을 도입할 때 왜 이것을 해야 하는지 명확히 아는 것이 중요합니다. 위의 이점 목록에는 가장 일반적인 이유가 포함되어 있지만 상황에 따라 중요성이 달라집니다. 일부 이점은 다른 이점보다 평가하기가 더 어렵습니다. 통합에서 낭비를 줄이는 것은 실망스러운 문제를 해결하며, 계속 작업을 진행하면서 쉽게 느낄 수 있습니다. 시스템의 복잡함을 줄이고 전반적인 생산성을 향상 시키기 위해 리팩토링을 수행하는 것은 확인하기가 더 까다롭습니다. 효과를 보기까지는 시간이 걸리고, 반례를 감지하기도 어렵습니다. 그러나 이것이 아마도 지속적인 통합의 가장 가치 있는 이점일 것입니다.

위의 사례 목록은 지속적인 통합 작업을 수행하기 위해 팀이 배워야 하는 기술을 나타냅니다. 이들 중 일부는 높은 통합 빈도에 가까워지기 전에도 가치를 가져올 수 있습니다. 자체 테스트 코드는 커밋이 자주 발생하지 않는 경우에도 시스템에 안정성을 추가합니다.

한 가지 목표는 통합 빈도를 두 배로 늘리는 것입니다. 기능 분기가 일반적으로 10일 동안 실행되는 경우 이를 5일로 줄이는 방법을 알아보세요. 여기에는 더 나은 빌드 및 테스트 자동화와 대규모 작업을 더 작고 독립적으로 통합된 작업으로 분할할 수 있는 방법에 대한 창의적인 사고가 포함될 수 있습니다. 사전 통합 검토를 사용하는 경우 테스트 적용 범위를 확인하고 더 작은 커밋을 장려하기 위해 해당 검토에 명시적인 단계를 포함할 수 있습니다.

새로운 프로젝트를 시작한다면 처음부터 지속적 통합부터 시작하면 됩니다. 우리는 빌드 시간을 주시하고 10분 규칙보다 느리게 진행되기 시작하자마자 조치를 취해야 합니다. 신속하게 행동함으로써 코드 기반이 너무 커져서 큰 어려움을 겪기 전에 필요한 구조 조정을 수행할 것입니다.

무엇보다도 우리는 도움을 받아야 합니다. 우리를 도와주려면 이전에 지속적인 통합을 수행한 사람을 찾아야 합니다. 다른 새로운 기술과 마찬가지로 최종 결과가 어떻게 보일지 모르면 도입하기가 어렵습니다. 이러한 지원을 받으려면 비용이 들 수 있지만, 그렇지 않으면 시간과 생산성 손실을 감수하게 될 것입니다. (Disclaimer / Advert - 예, Thoughtworks에서는 이 분야에 대한 컨설팅을 수행합니다. 결국 우리는 앞으로 저지르게 될 대부분의 실수를 저질렀습니다.)


공통 질문들

지속적인 통합은 어디에서 왔습니까?

지속적인 통합(Continuous Integration)은 1990년대 익스트림 프로그래밍의 일부로 Kent Beck에 의해 개발되었습니다. 그 당시에는 출시 전 통합이 일반적이었고 출시 빈도는 종종 수년 단위로 측정되었습니다. 릴리스 주기가 빨라지면서 반복 개발이 일반적으로 추진되었습니다. 그러나 릴리스 사이에 몇 주 동안 생각한 팀은 거의 없었습니다. Kent는 관행을 정의하고, 그가 작업한 프로젝트를 통해 이를 개발했으며, 그것이 의존하는 다른 주요 관행과 상호 작용하는 방식을 확립했습니다.

Microsoft는 매일 빌드(보통 밤새)를 수행하는 것으로 알려져 있었지만 지속적인 통합의 중요한 요소인 결함 수정에 대한 테스트 방식이나 초점은 없었습니다.

어떤 사람들은 Grady Booch가 이 용어를 만들었다고 생각하지만 그는 자신의 객체 지향 디자인 책에서 한 문장의 즉석 설명으로만 이 문구를 사용했습니다. 그는 그것을 정의된 관행으로 취급하지 않았으며 실제로 색인에 나타나지 않았습니다.

지속적인 통합과 트렁크 기반 개발의 차이점은 무엇입니까?

CI 서비스가 인기가 많아지면서, 많은 사람들이 CI 서비스를 사용하여 기능 분기에서 정기적인 빌드를 실행했습니다. 위에서 설명한 것처럼 이는 전혀 지속적 통합이 아니지만 많은 사람들이 상당히 다른 작업을 수행하면서 지속적 통합을 수행하고 있다고 말하게 되었고 이로 인해 많은 혼란이 발생했습니다.

일부 사람들은 트렁크 기반 개발이라는 새로운 용어를 만들어 이러한 Semantic Diffusion으로 다루기로 결정했습니다. 일반적으로 나는 이것을 지속적인 통합과 동의어로 보고 "기능 브랜치에서 Jenkins를 실행하는 것"과 혼동을 겪는 경향이 없다는 것을 인정합니다. 나는 둘 사이의 구별을 공식화하려는 일부 사람들의 글을 읽었지만 이러한 구별은 일관되지도 매력적이지도 않습니다.

나는 트렁크 기반 개발이라는 용어를 사용하지 않습니다. 부분적으로는 새로운 이름을 만드는 것이 semantic diffustion에 대응하는 좋은 방법이라고 생각하지 않기 때문입니다. 그러나 대부분 이 기술의 이름을 바꾸면 특히 Kent Beck의 작업이 무례하게 지워지기 때문입니다. 처음에는 지속적인 통합을 옹호하고 개발했습니다.

이 용어를 피했음에도 불구하고 트렁크 기반 개발이라는 이름으로 작성된 지속적인 통합에 대한 좋은 정보가 많이 있습니다. 특히 Paul Hammant는 자신의 웹사이트 에 훌륭한 자료를 많이 작성했습니다 .

기능 분기에서 CI 서비스를 실행할 수 있습니까?

간단한 대답은 "그렇습니다. 그러나 지속적인 통합을 수행하고 있지는 않습니다 "입니다. 여기서 핵심 원칙은 "모든 사람이 매일 메인라인에 전념한다"는 것입니다. 기능 분기에서 자동화된 빌드를 수행하는 것이 유용하지만 이는 단지 세미통합 일 뿐입니다 .

그러나 이러한 방식으로 데몬 빌드를 사용하는 것이 지속적인 통합에 관한 것이라는 것은 일반적인 혼란입니다. 이러한 도구를 지속적인 통합 서비스(Continuous Integration Services)라고 부르기 때문에 혼란이 옵니다. 더 나은 용어는 “지속적인 빌드 서비스(Continuous Build Services)”와 같은 것입니다. CI 서비스를 사용하는 것은 지속적인 통합을 수행하는 데 유용한 도움이 되지만 실제 도구를 혼동해서는 안 됩니다.

지속적인 통합과 지속적인 전달의 차이점은 무엇입니까?

지속적 통합에 대한 초기 설명은 팀 개발 환경의 메인라인과 개발자 통합 주기에 중점을 두었습니다. 이러한 설명에서는 통합 메인라인에서 프로덕션 릴리스로의 여정에 대해 많이 언급하지 않았습니다. 그렇다고 해서 그들이 사람들의 마음 속에 없었다는 뜻은 아닙니다. "배포 자동화" 및 "프로덕션 환경 복제에서 테스트"와 같은 사례는 프로덕션 경로에 대한 인식을 명확하게 나타냅니다.

어떤 상황에서는 메인라인 통합 이후에 별다른 것이 없었습니다. Kent가 90년대 후반 스위스에서 작업 중이던 시스템을 나에게 보여줬던 기억이 납니다. 그곳에서 그들은 매일 자동으로 프로덕션에 배포했습니다. 그러나 이것은 프로덕션 배포를 위한 복잡한 단계가 없는 Smalltalk 시스템이었습니다. 2000년대 초반 Thoughtworks에서는 제작 과정이 훨씬 더 복잡한 상황이 자주 발생했습니다. 이로 인해 해당 경로를 다루는 지속적인 통합 이상의 활동이 있다는 개념이 생겼습니다. 이 활동은 지속적인 전달(Continuous Delivery)로 알려졌습니다.

Continuous Delivery의 목표는 제품이 항상 최신 빌드를 릴리스할 수 있는 상태에 있어야 한다는 것입니다. 이는 본질적으로 프로덕션 릴리스가 비즈니스 결정임을 보장합니다.

요즘 많은 사람들에게 지속적인 통합은 개발 팀 환경의 메인라인에 코드를 통합하는 것이며 지속적인 전달은 프로덕션 릴리스로 향하는 나머지 배포 파이프라인입니다. 어떤 사람들은 Continuous Delivery를 지속적인 통합을 포괄하는 것으로 간주하고, 다른 사람들은 이를 종종 CI/CD라는 이름으로 밀접하게 연결된 파트너로 간주합니다. 다른 사람들은 Continuous Delivery가 단지 Continuous Integration의 동의어일 뿐이라고 주장합니다.

지속적 배포는 이 모든 것에 어떻게 적합합니까?

지속적인 통합을 통해 모든 사람이 적어도 매일 자신의 코드를 버전 관리의 메인라인에 통합할 수 있습니다. 그런 다음 Continuous Delivery는 누구나 원할 때마다 제품을 출시할 수 있도록 하는 데 필요한 모든 단계를 수행합니다. 지속적인 배포는 배포 파이프라인의 모든 자동화된 테스트를 통과할 때마다 제품이 자동으로 프로덕션으로 릴리스된다는 것을 의미합니다.

지속적인 배포를 사용하면 지속적인 통합의 일부로 메인라인에 푸시된 모든 커밋이 배포 파이프라인의 모든 검증이 녹색인 경우 자동으로 프로덕션에 배포됩니다. 지속적인 전달은 이것이 가능함을 보장합니다(따라서 지속적인 배포의 전제 조건입니다).

Pull requests와 코드 검토는 어떻게 하나요?

Github의 Artifact인 Pull Requests 는 이제 소프트웨어 프로젝트에서 널리 사용됩니다. 기본적으로 이는 메인라인으로의 푸시에 일부 프로세스를 추가하는 방법을 제공합니다. 일반적으로 사전 통합 코드 검토와 관련하여 메인라인으로 푸시되기 전에 다른 개발자의 승인이 필요합니다. 이는 주로 오픈 소스 프로젝트의 기능 분기 맥락에서 개발되었으며, 이를 통해 프로젝트 관리자는 기여가 프로젝트의 스타일과 향후 의도에 적절하게 맞는지 검토할 수 있습니다.

사전 통합 코드 검토는 일반적으로 통합 프로세스에 상당한 마찰을 추가하기 때문에 지속적인 통합에 문제가 될 수 있습니다. 몇 분 내에 완료할 수 있는 자동화된 프로세스 대신 코드 검토를 수행할 사람을 찾고, 스케줄을 잡고, 검토가 승인되기 전에 피드백을 기다려야 합니다. 일부 조직에서는 몇 분 안에 업무 흐름을 시작할 수 있지만 이는 몇 시간 또는 며칠이 걸리기 쉬우므로 지속적인 통합이 작동하는 타이밍이 깨집니다.

지속적인 통합을 수행하는 사람들은 코드 검토가 작업 흐름에 어떻게 부합하는지 재구성하여 이 문제를 처리합니다. Pair Programming은 코드가 작성되는 동안 지속적인 실시간 코드 검토를 생성하여 검토를 위한 훨씬 빠른 피드백 루프를 생성하므로 널리 사용됩니다. Ship / Show / Ask 프로세스는 팀이 통합 빈도에 방해가 되지 않으므로 통합 후 검토가 더 나은 경우가 많다는 것을 인식하고 필요한 경우에만 차단 코드 검토를 사용하도록 권장합니다. 많은 팀에서는 개선 코드 검토가 건전한 코드 기반을 유지하는 데 중요한 힘이지만 지속적인 통합이 리팩토링에 친화적인 환경을 생성할 때 가장 잘 작동한다는 것을 알고 있습니다.

사전 통합 검토는 관련이 별로 없는 개발자가 즉흥적으로 기여한 오픈 소스 컨텍스트에서 성장했다는 점을 기억해야 합니다. 해당 환경에서 효과적인 관행은 긴밀하게 연결된 직원으로 구성된 정규 팀을 위해 재평가되어야 합니다.

데이터베이스를 어떻게 처리합니까?

데이터베이스는 통합 빈도를 높이면서 특정한 챌린지를 제공합니다. 버전 제어 소스에 테스트 데이터에 대한 데이터베이스 스키마 정의와 로드 스크립트를 쉽게 포함할 수 있습니다. 그러나 이는 프로덕션 데이터베이스와 같이 버전 제어의 외부 데이터에는 도움이 되지 않습니다. 데이터베이스 스키마를 변경하는 경우 기존 데이터를 처리하는 방법을 알아야 합니다.

기존의 출시 전 통합에서는 데이터 마이그레이션이 상당한 과제이며, 마이그레이션을 수행하기 위해 특수 팀을 구성하는 경우가 많습니다. 처음에는 빈도가 높은 통합을 시도하면 감당할 수 없을 만큼 많은 양의 데이터 마이그레이션 작업이 발생하게 됩니다.

그러나 실제로는 관점을 바꾸면 이 문제가 해결됩니다. 우리는 Continuous Integration을 사용한 초기 프로젝트의 Thoughtworks에서 이 문제에 직면했고, 내 동료 Pramod Sadalage가 개발한 Evolutionary Database Design 접근 방식으로 전환하여 문제를 해결했습니다. 이 방법론의 핵심은 데이터베이스 스키마와 데이터를 모두 변경하는 일련의 마이그레이션 스크립트를 통해 데이터베이스 스키마와 데이터를 정의하는 것입니다. 각 마이그레이션은 규모가 작으므로 추론하고 테스트하기가 쉽습니다. 마이그레이션은 자연스럽게 구성되므로 수백 번의 마이그레이션을 순차적으로 실행하여 중요한 스키마 변경을 수행하고 진행하면서 데이터를 마이그레이션할 수 있습니다. 이러한 마이그레이션을 애플리케이션의 데이터 액세스 코드와 동기화된 버전 제어에 저장할 수 있으므로 올바른 스키마와 올바르게 구조화된 데이터를 사용하여 모든 버전의 소프트웨어를 구축할 수 있습니다. 이러한 마이그레이션은 테스트 데이터와 프로덕션 데이터베이스에서 실행할 수 있습니다.


마무리하면서

대부분의 소프트웨어 개발은 기존 코드를 변경하는 것입니다. 코드 베이스에 새로운 기능을 추가하는 데 드는 비용과 응답 시간은 해당 코드 베이스의 상태에 따라 크게 달라집니다. 형편없는 코드 기반은 수정하기가 더 어렵고 비용도 더 많이 듭니다. 불필요한 작업을 최소한으로 유지하려면 팀은 정기적으로 코드를 리팩터링하고, 변화하는 요구 사항을 반영하도록 구조를 변경하고, 팀이 제품 작업을 통해 배운 교훈을 통합할 수 있어야 합니다.

지속적인 통합은 이러한 종류의 진화적인 디자인 생태계의 핵심 구성 요소이기 때문에 건강한 제품에 필수적입니다. 자체 테스트 코드와 함께 지원되며 리팩토링의 기반이 됩니다. 익스트림 프로그래밍에서 탄생한 기술적 관행을 통해 팀은 변화하는 요구 사항과 기술 기회를 활용하여 정기적인 제품 개선을 제공할 수 있습니다.

profile
세니는 무엇을 하고 있을까

0개의 댓글