지속적인 통합을 이용해 위험 줄이기 (1)

등록일: 2014. 09. 25

지속적인 통합: 소프트웨어 품질을 높이고 위험을 줄이기

  • 폴 M. 듀발, 스티븐 M. 마티야스, 앤드류 글로버 지음
  • 최재훈 옮김
  • 324쪽
  • 22,000원
  • 2008년 03월 27일

품질이란 누가 보지 않을 때에도 제대로 돌아가는 걸 뜻한다

-- 헨리 포드

프로젝트를 하다 보면 일이 꼬이기 마련입니다. 지속적인 통합을 효과적으로 실천하면 무엇이 문제인지 바로바로 알 수 있게 됩니다-개발 막바지에 아는 게 아니구요, 지속적인 통합은 위험 요소가 발생했을 때 이를 알아보고 완화시킬 수 있게 도와주어 명확한 증거에 따라 프로젝트의 건강 상태를 평가하고 보고하기 쉬워집니다. 소프트웨어를 얼마나 구현했는가? 정답: 가장 최근의 빌드를 확인해보라. 테스트 적용범위가 얼마나 되나? 정답: 가장 최근의 빌드를 확인해보라. 누가 마지막으로 코드를 체크인했는가? 정답: 가장 최근의 빌드를 확인해보라.

이 장에서 우리는 지속적인 통합을 실천함으로 여러 위험 요소들, 이를테면 결함을 막바지에나 발견하는 문제, 프로젝트 가시성의 부재, 품질이 낮은 소프트웨어, 배포할 수 있는 소프트웨어를 만들지 못하는 무능력 등을 완화할 수 있다는 걸 알게 될 것 입니다.

대부분의 팀은 좋은 의도로 시작하지만 그 중 몇몇은 프로젝트상의 문제에 압도되고 맙니다. 이런 문제는 위험 요소를 관리하지 않았기 때문에 벌어집니다. 이의 앞부분에서 언급했듯이, “우리가 생각하기엔 테스트라든가 코드 검토(짝으로 검토를 하든 다른 방식으로 하든)는 좋은 실천 방법이 아닌 것 같아.”라고 말하는 개발 그룹은 거의 없습니다. 하지만 일정 때문에 압박을 받게 되면 팀은 지속적인 통합부터 빼먹기 마련입니다. 이 장에선 지속적인 통합의 여러 측면을 활용하여 어떤 소프트웨어 위험 요소를 줄일 수 있는지에 대해 초점을 맞춥니다. 지속적인 통합을 사용하면 ‘품질 안전 망’을 구축하고 소프트웨어를 보다 빨리 전달할 수 있습니다. 코드가 변경될 때마다 ‘통합 버튼’을 누름으로써 여러분은 그림 3-1에 제시된 것과 같이 위험 요소를 조기에 그리고 자주 줄여나갈 기반을 구축하게 됩니다.

그림 3.1 지속적인 통합은 소프트웨어 품질을 향상시키고 위험 요소를 줄이는 데 도움이 됩니다

특정 소프트웨어 위험 요소를 줄일 수 있다면 소프트웨어 품질을 향상시킬 수 있습니다. 이 장에서 위험 요소들을 설명함에 있어 우리는 다음과 같은 양식을 따를 겁니다.

  • 소프트웨어 위험 요소에 대한 소개와 설명
  • 우리의 경험에 따른 시나리오
  • CI의 일면을 활용하여 위험 요소를 완화시킬 해결책

어느 프로젝트에서든지 관리해야 할 위험 요소는 많습니다. 우리는 지속적인 통합을 활용해서 줄일 수 있는 핵심 위험 요소에 초점을 맞춥니다. 물론 CI가 고객에게서 요구 사항을 끌어내고, 고객의 산업을 이해하고, 자금이나 자원을 관리하는 일과 같은 사업적 도전거리를 직접 보조하지는 못합니다. 하지만 지속적인 통합을 사용하면 소프트웨어의 문제를 보다 빨리, 소프트웨어를 개발하는 도중에 발견할 수 있습니다.

지속적인 통합은 변경될 때마다 소프트웨어를 빌드함으로써, 시간을 여러분의 편으로 만들어줍니다. 지속적인 통합을 적용하면 보다 중요하고 보다 흥미로운 프로젝트 이슈에 집중할 수 있습니다. 지속적인 통합은 집합적인 실천 방법이기 때문에 이 장에 서 다루는 위험 요소들은 여러 소프트웨어 개발 실천 방법을 아우릅니다.

  • 배포 가능한 소프트웨어의 부재
  • 뒤늦은 결함 발견
  • 프로젝트 가시성의 부재
  • 저품질의 소프트웨어

“아, 일전에 이 같은 위험 요소에 대해 들은 적이 있어요. 별로 새로울 건 없네요”라고 말할지도 모르겠습니다. 하지만, 위험 요소를 안다고 해서 그것을 완화시킬 방법까지 안다고 할 수는 없습니다. 위험 요소를 파악하고 해결하여 그것들이 더 이상 프로젝트상의 쟁점이 되지 않게 하는, 보다 효율적이고 생산적인 방법이 있습니다. 다른 실천 방법들이 그렇듯, 결국엔 어떻게 효과적으로 실현하느냐가 문제입니다. 뒤이은 장에서는 통합 버튼이란 모델을 이용하여 이런 위험 요소들을 인지하고 줄이는 효과적인 방법들을 보여주겠습니다.

위험 요소: 배포 가능한 소프트웨어의 부재

한두 달에 한 번씩 별도의 컴퓨터에서 소프트웨어를 빌드했던 프로젝트가 있었습니다. 마지막으로 소프트웨어를 빌드했을 때는 납품 마감일이 코 앞이라, 팀 구성원 대부분이 저녁 늦은 시간까지 남아서 기적을 다시 이끌어내려 했습니다. 이 ‘통합 지옥’ 와중에 우리는 제대로 동작하지 않는 인터페이스들이 있다는 것과, 설정 파일을 빼먹었다는 것, 비슷한 기능을 제공하는 컴포넌트가 여러 개 있다는 것, 지난 번 빌드의 일부인 다수의 변경 사항을 병합하기가 힘들다는 것을 알게 됐습니다. 이런 문제 때문에 프로젝트상의 중요한 이정표를 빼먹게 됐던 적도 있습니다.

또 다른 프로젝트에서는 IDE를 사용하여 사람이 직접 소프트웨어 통합 빌드를 했습니다. 평균적으로 일 주일에 한 번씩 소프트웨어를 직접 통합했습니다. 형상 관리(CM) 분석가가 특정 스크립트를 사용해서 버전 관리 저장소 밖에서 소프트웨어를 빌드할 때도 있었습니다. 자동화의 부재는 빌드 수행의 부하를 증가시켰습니다. 별도의 컴퓨터가 제공하는 깨끗한 환경에서 빌드를 하지 않았기 때문에 우리는 소프트웨어를 제대로 빌드하고 있는지 자신이 없었습니다. 이 모든 것은 세 가지 효과를 일으켰습니다.

  • 소프트웨어를 빌드할 수 있을지에 대해서조차 자신감이 부족하거나 없었습니다.
  • 소프트웨어를 내부(예: 테스트 팀)나 외부(예: 고객)에 전달하기 전까지의 통합 기간이 길었고, 그 사이에 아무것도 할 수 없었습니다.
  • 테스트 가능한 빌드 결과물을 생산하고 재생산할 능력이 없었습니다.

시나리오: “내 컴퓨터에선 되는데요”

프로젝트 팀이 작동하고 배포 가능한 소프트웨어를 만들어내지 못하는 이유는 여러 가지입니다. 테스트가 실패하는 일부터 버전 관리 저장소에 엉뚱한 파일을 올려놓는 일까지 어떤 것이라도 빌드 실패에 한몫 할 수 있습니다. 그런 시나리오를 하나 살펴보죠.

존 (기술 리더): 지난 번 빌드가 테스트 서버에서 문제를 일으켰어요.

아담 (개발자): 이상하네요. 제 컴퓨터에서 빌드했을 땐 잘 됐는데요. 한번 볼께요…. 음, 잘 되는데요.

존: 아, 뭐가 문제인지 알겠어요. 새로 추가한 파일을 서브버전 저장소에 안 올렸네요.

 해결책

IDE와 빌드 프로세스가 서로 강하게 얽혀있으면 안 된다는 것은 아무리 강조해도 지나치치 않습니다. 소프트웨어 통합 전용 컴퓨터를 따로 사용하세요. 소프트웨어를 빌드할 때 필요한 모든 게 버전 관리 저장소에 있어야 합니다. 마지막으로 CI 시스템을 만드세요. CruiseControl 같은 CI 서버와 Ant, NAnt, 또는 Rake 같은 도구를 사용해서 빌드를 자동화하세요. Cruise Control은 버전 관리 저장소에 변경 사항이 들어왔는지 지켜보다가, 저장소에 변경 사항이 들어오면 프로젝트용 빌드 스크립트를 돌립니다. 빌드할 때 테스트를 돌리고, 검사를 수행하며 개발 및 테스트 환경에 소프트웨어를 배포하도록 CI 시스템의 기능을 강화할 수도 있습니다. 이렇게 하여 작동하는 소프트웨어를 언제라도 갖게 됩니다.

시나리오: 데이터베이스와 동기화하기

개발 도중에 데이터베이스를 신속하게 다시 만들어낼 수 없다면, 소프트웨어를 변경하기가 힘들 겁니다. 데이터베이스 팀과 개발 팀이 분리되어 있어서 이런 문제가 발생하곤 합니다. 서로 협력하는 데 힘쓰기보단 저마다 자기 팀이 맡은 일에 초점을 맞추기 때문이죠. 팀들이 하나가 되지 못하는 판에 제품을 통합할 방법이 있겠습니까? 이런 시나리오에선, 이를테면 데이터베이스 관리자가 데이터베이스 스크립트 상당수를 버전 관리 저장소에 커밋하지 않을 수도 있습니다. 이렇게 되면 다음과 같은 유형의 위험 요소가 발생할 수 있습니다.

  • 데이터베이스나 소스 코드를 변경하거나 리팩토링하기가 무섭다.
  • 여러 가지 데이터 집합을 사용해 데이터베이스를 생성하기가 힘들다.
  • 개발 및 테스트 환경(예: 개발, 통합, 품질 보증, 테스트)을 유지보수하기가 힘들다.

이런 위험 요소는 데이터베이스가 개발 팀을 못 따라잡거나 그 반대의 현상이 일어나게 해 개발에 악영향을 미칩니다. 소프트웨어와 데이터베이스 개발자들이 각기 다른 버전의 데이터베이스를 쓰고 있을지 모릅니다. 프로젝트 구성원들이 한 곳(버전 관리 저장소)에서 최신 데이터베이스를 구하지 못할 수도 있습니다. 다음 대화가 이 문제를 잘 묘사하고 있습니다.

로렌 (개발자): 빌드 1345를 써서 버전 1.2.1.b1 데이터베이스를 테스트하려고 하니까 문제가 많네요.

폴린 (데이터베이스 설계자): 아 이런, 빌드 1345를 쓸 땐 버전 1.2.1.b2짜리를 써야 해요. 그 전에 손봐야 할 게 몇 개 있구요.

로렌: 네 시간이나 날렸다구요.

폴린: 저한테 먼저 확인 받았어야죠.

 해결책

일부 프로젝트에 이 해결책을 적용하려면 근본적인 변화를 일으켜야 합니다. 데이터베이스가 개발과 동떨어진 개체가 되지 않게 하는 접근 방법을 대략 설명해보겠습니다.

  • 모든 데이터베이스 산출물을 버전 관리 저장소에 넣으세요. 이 산출물이란 데이터베이스 스키마와 데이터를 다시 만드는 데 필요한 모든 걸 뜻합니다. 데이터베이스 생성 스크립트, 데이터 조작 스크립트, 저장 프로시저, 트리거와 그 밖의 데이터베이스 자산을 말하는 겁니다.
  • 빌드 스크립트로 데이터베이스와 테이블을 삭제하고 다시 만들어서 데이터베이스와 데이터를 다시 빌드하세요. 그런 후에 저장 프로시저와 트리거를 다시 만들고, 마지막엔 테스트 데이터를 삽입하세요.
  • 데이터베이스를 테스트하세요(그리고 검사하세요). 컴포넌트 테스트를 사용해 데이터베이스와 데이터를 테스트하는 게 보통입니다. 경우에 따라선 데이터베이스 별로 테스트를 작성해야 할 겁니다.

5장에서 이 주제를 더 심도 깊게 다루고 시나리오와 해결책을 논의하겠습니다.

시나리오: 버튼을 안 눌렀어요

소프트웨어를 사람이 배포하면 시간과 노력을 낭비하는 겁니다. 애플리케이션 서버의 웹 관리 유틸리티를 써서 필요할 때마다 소프트웨어를 직접 배포했던 프로젝트가 있었습니다. 이 일은 하루에 한 번씩 하기로 되어 있었습니다. 하지만 팀이 다른 이슈를 다루느라 정신 없을 때가 많아서, 정작 우리가 최신 통합 빌드가 필요할 땐 병목을 일으켰습니다. 이 반복적이고 지루한 과정에 매일 10분 내지 15분을 썼습니다. 그것도 일이 잘 풀렸을 때죠. 문제는 자동화했어야 했던 일에 시간을 낭비하고 있다는 것이었습니다. 테스트 컴퓨터에 배포하는 일 말이죠. 그런데다가 관리 도구의 오른쪽 버튼을 누르지 않으면 문제가 일어나기 십상이었습니다.

사람이 배포하는 접근 방식을 취할 때 발생하는 전형적인 문제를 예로 들어보겠습니다.

레이첼 (개발자): 개발 서버에 최신 빌드가 업데이트됐나요? 존은 어디 있죠?

켈리 (개발자): 아, 존은 점심 먹으러 갔어요. 존이 업데이트를 서버에 올리기로 되어 있었는데요.

레이첼: 그래요? 존이 돌아올 때까지 기다려야겠네요.

나중에 존이 들어오고…

레이첼: 존, 최신 빌드는 어떻게 된 거예요? JSP 파일을 ‘미리 컴파일하기(Precompile)’안 한 것 같은데요. 런타임 오류가 나고 있다구요.

존 (기술 리더): 이런, 미안해요. 어제 웹 도구로 배포할 때 미리 컴파일하기 옵션을 깜박하고 잊은 것 같네요.

 해결책

우리가 참여했던 프로젝트에서는 Ant 빌드 스크립트가 애플리케이션 서버의 명령줄 옵션을 사용하게 만들어 개발 과정을 자동화했습니다. 이렇게 하여 다른 사람이 소프트웨어를 배포해줄 때까지 기다리는 병목을 줄이고 실수할 여지를 없앴습니다. 언제라도 최신 소프트웨어를 사용해 테스트할 수 있습니다. 우리는 CruiseControl CI 서버를 사용해 버전 관리 저장소에 변경 사항이 들어올 때마다 Ant 빌드 스크립트를 돌렸습니다. 더 자세히 알고 싶다면 8장을 참고하세요.

위험 요소: 뒤늦은 결함 발견

몇몇 프로젝트에서 우리는 테스트를 직접 수행했습니다. 최근에 소프트웨어를 변경한 코드가 다른 문제를 일으키진 않는지 알 도리가 없었습니다. 이를테면 결함을 하나 고치면 다른 연관된 결함이 드러나는 악순환 때문이죠. 하나를 변경하면 그것이 물밑에서 어떤 효과를 일으킬지 알지 못해서 변경할 자신이 없었습니다. 테스트들을 사람이 직접 수행했었기 때문에 개발자들이 테스트를 돌릴 거라는 보장이 없었습니다.

시나리오: 회귀 테스트

회귀 테스트 시나리오를 살펴보도록 하죠.

샐리 (기술 리더): 테스트 환경에 배포된 최신 버전에서 두 달 전 발견한 바로 그 버그를 발견했어요. 왜 그런 거죠?

카일 (개발자): 잘 모르겠네요. 최신 코드를 모두 테스트했는데요.

샐리: 시스템의 다른 부분을 테스트하는 코드까지 모두 돌려봤나요?

카일: 아뇨. 그 많은 테스트를 직접 돌려볼 시간이 없었어요. 그래서 제가 버그를 찾지 못했었나 보네요.

 해결책

이후 프로젝트에서 우리는 JUnit으로 비즈니스, 데이터, 그리고 공통 계층에 대해 단위 테스트와 컴포넌트 테스트를 작성했습니다. 기존 프로젝트의 경우에는 변경한 코드에 대해서만 결함이 있으면 단위 테스트를 작성했습니다. 우리는 Ant 빌드 스크립트가 모든 단위 테스트를 돌리고 빌드할 때마다 보고서를 만들도록 설정했습니다.

다음에 제시된 단계들은 CI 시스템을 이용해 회귀 테스트를 자동화하는 방법을 보여줍니다.

  1. 모든 소스 코드에 대해 테스트를 작성하세요(xUnit 프레임워크로 시작하면 좋습니다).
  2. 빌드 스크립트로 테스트를 돌리세요(Ant나 NAnt를 많이 사용합니다).
  3. 지속적인 통합 시스템의 일부로써 테스트를 지속적으로 돌리세요. 그래야 버전 관리 저장소에 체크인할 때마다 테스트가 실행됩니다(CruiseControl이나 이와 유사한 CI 서버를 사용하세요).

이토록 쉽게 회귀 테스트를 자동화할 수 있답니다! 6장에서는 테스트를 모든 측면에서, 빌드 과정에 없어서는 안 될 요소로 만드는 일에 관해 좀더 논의하겠습니다.

시나리오: 테스트 적용범위(Test Coverage, 테스트 커버리지)

테스트를 작성하고 돌리면 그 결과를 보는 것뿐만 아니라 코드 중 얼마나 많은 부분이 실제로 테스트되는지도 알고 싶을 겁니다. 지속적인 통합 시스템을 도입하기 전에는 프로젝트상의 단위 테스트 대부분을 직접 돌렸기 때문에, 테스트가 실행됐는지 독립적으로 검증할 방법이 없었습니다. 얼마나 많이 테스트를 했는지 관리자가 알 방법이 없을까요? 다음 대화를 고려해보세요

에블린 (관리자): 변경한 코드를 저장소에 커밋하기 전에 단위 테스트를 실행해 봤어요?

노아 (개발자): 네.

에블린: 좋아요.

에블린이 뭘 빼먹고 안 물어봤죠? 다시 한 번 해볼까요?

에블린: 새로 추가한 코드를 검사할 새 테스트를 짜거나 기존 테스트를 갱신했나요?

노아: 네

에블린: 테스트를 다 통과하던가요? 노아: 네

에블린: 충분한 양의 코드를 적절히 테스트했는지는 어떻게 판단했죠?

이번 질문을 던진 것은 좀 나았습니다. 하지만 여전히 정량적인 분석으로 보다 명확하게 설명할 수 있는 것을 불필요하게 정성적으로 분석하고 있습니다. 이제 해결책을 살펴보도록 하죠.

 해결책

일단 개발자나 팀이 소스 코드에 대응하는 테스트를 짰다고 생각한다면, 여러분은 코드 적용범위 도구를 돌려서 테스트 코드가 실지로 얼마만한 양의 소스 코드를 실행하는지 평가해볼 수 있습니다. 패키지나 클래스 별로 적용범위를 백분율로 보여주는 도구가 여럿 있습니다.

지속적인 통합은 이 같은 테스트 적용범위를 항상 최신 상태로 유지해줍니다. 이를테면, 테스트 적용범위 도구를 지속적인 통합 시스템의 빌드 스크립트에 넣어 버전 관리 저장소에 변경 사항이 들어오면 실행되게 할 수 있습니다. 7장에서 코드 적용범위에 대해 논의하겠습니다.