6 min to read
Continuous Deployment (지속적 통합) / TIL
Continuous Deployment
지속적 통합
전통적인 SW 전달 방식 : 폭포수 모델 (Waterfall)
여전히 모바일 앱이 사용하는 전달 방식으로, 출시 기한을 정해두고 소프트웨어를 완성하는 방식이다.
- 베타 버전 등을 통한 테스트를 통해 소프트웨어 안정성 개선을 위한 노력이 필요하다.
- 사용자가 항상 최신 상태로 업데이트 해야하므로, 버그 수정을 사용자에게 전달하기 매우 어렵다. (몇 SW는 지속적 전달을 위해 자동 업데이트를 도입)
- 폭포수 모델 방식은 출시 시점에 소프트웨어의 신뢰성, 안정성을 보장할 수 없고, 출시를 약속하고 뚜껑을 열어봤을 때, 산더미처럼 쌓여있는 버그를 발견할 수도 있다.
클라우드 서비스의 전달 방식 : 애자일 모델 (Agile)
서비스 전달/배포 Workflow를 구성할 수 있어야 하며, 자동화가 필수적이다.
SaaS (Software as a Service)
- 브라우저에 접속하기만 해도, 새 버전을 즉시 사용할 수 있고, 매일매일 진화하는 SW이다.
- 사용자 업데이트에 대한 걱정에서 벗어난 방식이다.
- 하루에 여러 번의 릴리즈도 가능하여 빠르게 문제를 해결할 수 있다.
- 다양한 배포 방식을 적용하거나 A/B 테스트가 가능하다.
- 빠른 배포를 보장한다.
CI/CD 파이프라인 : 지속적 배포(Continuous Deployment)
모든 코드 변경이 배포로 이어진다.
지속적 배포 = 지속적 통합 + 지속적 전달
지속적 통합 (Continuous Integration/CI)
팀 구성원이 각자의 작업을 자주 통합하는 소프트웨어 개발 방식 - Martin Fowler
지속적 통합을 통해 버그를 일찍 발견할 수 있고, 테스트가 완료된 코드에 대해 빠른 전달이 가능하다. 또한 빌드 및 테스트와 같이 사람이 해야 할 일들을 자동화할 수 있다.
- Code : 코드 저장소에 잦은 코드 Push
- Build : 코드 저장소로부터 코드를 가져와서 유닛 테스트 후 빌드, 테스트/빌드 서버에서 빌드 결과를 통해 빌드 성공/실패 확인
- Test : 코드 빌드의 결과물이 다른 컴포넌트와 잘 통합되는지 확인, 테스트 결과 확인 후 개선
지속적 통합의 원칙
- 프로젝트에서 제품을 빌드하기 위해 함께 조정해야 하는 수많은 파일이 포함되어 있기 때문에 단일 소스 레파지토리를 유지해야 한다. 이 모든 파일이 단일 레파지토리가 아닌 곳에 뿔뿔이 흩어져 있으면 추적하는 것이 힘들다.
- 빌드를 자동화해야 한다.
- 셀프 테스팅 빌드를 만들어야 한다. 빌드 프로세스에 자동화된 테스트를 포함 함으로써 버그를 더 빠르고 효율적으로 파악할 수 있다.
- 매일 메인라인에 커밋을 해야 한다. 메인 라인 커밋을 통해 각자 진행 상황을 추적하고, 충돌이 발생하면 빠르게 충돌 상황을 감지할 수 있다.
- 모든 팀원이 시스템 상태와 시스템에 적용된 변경 사항을 쉽게 확인할 수 있어야 한다.
지속적 통합에서 테스트는 중요하다. 테스트를 통해 결함과 버그를 조기에 발견할 수 있고, 이는 개발자의 생산성을 향상할 수 있다. 또한 제품의 결함과 버그를 발견하고 수정하는 것은 소프트웨어의 품질을 보증하고, 더 안정적이고 사용하기 쉽게 만든다.
지속적 전달 (Continuous Delevery/CD)
- Release : 배포 가능한 소프트웨어 패키지를 작성한다. (배포에 적합한 빌드를 선정한다.)
- Deploy : 프로비저닝을 진행하고, 서비스를 사용자에게 노출한다.
- Operate : 서비스에 생길 수 있는 현황을 파악하고 문제를 감지한다.
테스트 주도 개발(Test Driven Development, TTD)
테스트가 기능의 디자인을 주도하는 반복적인 개발 방법론.
기존의 개발 방식
요구사항 분석 → 디자인 설계 → 개발 → 테스트
테스트 주도 개발 방식
요구사항 분석 → 디자인 설계 → 테스트 → 개발
기존 개발 방식과 다르게 테스트 주도 개발은 설계 후 테스트를 한 뒤 개발을 진행한다.
TDD의 테스트는 큰 단위의 문제를 작은 단위로 나누어, 지속적 피드백을 통해 개선해 나가는 방향으로 진행된다.
TDD의 장점
- 더욱 명확한 기능과 구조를 설계할 수 있다.
- 재사용성이 고려된, 모듈화된 코드를 작성할 수 있다.
- 설계 수정 시간과 디버깅 시간의 단축 / 단위 테스트 기반의 테스트 코드를 작성하기 때문에 추후 프로그램에 문제가 발생할 경우, 각각 모듈별로 테스트를 진행하며 문제 지점을 쉽게 찾아낼 수 있다.
- 완성도 높은 설계 / 코드의 기능, 정의 등 구조적인 문제에 대해 명확하게 접근할 수 있으며, 다양한 예외 상황에 대해서도 고려하게 된다.
- 유지 보수의 용이성 / 변경 점에 따른 테스트를 진행해야 하는 상황에 대한 부담이 줄어들 수 있다.
테스트 종류
단위 테스트
// 만일 sum(x, y) 와 같이 두 숫자를 더하는 함수를 테스트하려면,
it('sum에 두 수를 인자로 입력하면, 두 수의 합이 리턴됩니다', () => {
expect(sum(1,1)).to.be.equal(2) // sum(1,1)의 리턴값은 2가 될 것이라고 기대한다
expect(sum(100, 200)).to.be.equal(300)
})
검증이 필요한 코드에 대해 테스트 케이스를 작성하는 절차 혹은 프로세스. 단위 테스트를 통해 즉각적 피드백이 나오지만, 그들이 결합하는 시점에서도 잘 작동하는 지에 대해서는 보장할 수 없기 때문에 염두에 두어야 한다.
통합 테스트
// 서버에 API 요청을 보냈을 때, 정확한 응답이 오리라고 기대하는 경우
// 아래의 코드가 백엔드 입장에서는 단순 유닛 테스트라고 볼 수도 있지만
// 서버와 클라이언트 코드가 별도로 작성되어 있고, 서버-클라이언트의 통합을 테스트해야 한다면, 이는 통합 테스트라고 할 수 있습니다.
it('서버에 POST /upper 요청에 body를 실어 보내면 응답은 대문자로 돌려줍니다', () => {
return request(API서버)
.post('/upper')
.send('"coDeStaTes"')
.set('Content-Type', 'application/json')
.then(res => {
expect(res.body).to.be.equal('CODESTATES')
})
})
모듈을 통합하는 과정에서 모듈 간 호환성 문제를 찾아내기 위해 수행되는 테스트이다. 단위 테스트에서 찾지 못하는 통합 시 발생하는 버그 등을 찾을 수 있다.
단위 테스트 및 통합 테스트 시 사용하는 도구
- mocha, chai (JavaScript)
- JUnit (Java)
E2E 테스트 (End To End Test)
전체 시스템이 제대로 동작하는지 확인하기 위한 테스트. 사용자의 입장에서 사용자가 사용하는 상황을 가정하고 시뮬레이션을 진행한다.
E2E 테스트를 통해 실제 상황에서 발생할 수 있는 에러를 사전에 발견할 수 있다.
하지만, 테스트 작성 시 들어가는 비용이 많으며, 수행 속도가 느리다. 또한 “실패했다”라는 결과만 있기 때문에 피드백의 질이 낮다.
E2E 테스트 시 사용하는 도구
- Cypress
- Nightwatch
- TestCafe
통합 테스트와 E2E 테스트의 차이
통합 테스트 | E2E 테스트 |
---|---|
앱 구성 요소들이 잘 작동하는지 확인하는 테스팅 | 사용자가 경험하는 것처럼 제품을 테스트 |
대개 전체 스택을 포함하지 않고 여러 구성 요소에 걸쳐 있다. | 범위가 더 넓고 전체 애플리케이션 기술 스택을 포함한다. |
구성 요소들이 함께 작동할 때 연결성 문제를 발견하기 위해 수행된다. | 사용자 경험을 느끼기 위해 수행된다. |
구현 비용이 적다. | 하드웨어 및 소프트웨어 측면에서 구현 비용이 더 많이 든다. |
유닛 테스팅보다 높은 수준 | 통합 테스팅보다 높은 수준 |
수행 속도가 빠르다. | 수행 속도가 느리다. |