Share via


단위 테스트가 남아 있는 시프트 테스트

테스트는 코드가 예상대로 수행되도록 하는 데 도움이 되지만, 테스트를 빌드하는 데 시간과 노력은 기능 개발과 같은 다른 작업에서 벗어나는 데 시간이 걸립니다. 이 비용을 사용하여 테스트에서 최대 값을 추출하는 것이 중요합니다. 이 문서에서는 단위 테스트의 가치와 왼쪽 이동 테스트 전략에 초점을 맞춘 DevOps 테스트 원칙에 대해 설명합니다.

대부분의 테스트를 작성하는 데 사용되는 전용 테스터와 많은 제품 개발자는 단위 테스트를 작성하는 방법을 배우지 않았습니다. 테스트를 작성하는 것은 너무 어렵거나 너무 많은 작업처럼 보일 수 있습니다. 단위 테스트 전략이 작동하는지 여부, 잘못 작성된 단위 테스트가 있는 나쁜 경험 또는 단위 테스트가 기능 테스트를 대체할 까봐 두려워할 수 있습니다.

Graphic that describes arguments about adopting unit testing.

DevOps 테스트 전략을 구현하려면 실용적이고 모멘텀 구축에 집중합니다. 클린 리팩터링할 수 있는 새 코드 또는 기존 코드에 대한 단위 테스트를 고집할 수 있지만 레거시 코드베이스에서 일부 종속성을 허용하는 것이 합리적일 수 있습니다. 제품 코드의 상당 부분이 SQL을 사용하는 경우 단위 테스트가 해당 계층을 모의 하는 대신 SQL 리소스 공급자에 종속되도록 허용하는 것이 단기적인 진행 방식일 수 있습니다.

DevOps 조직이 성숙함에 따라 리더십이 프로세스를 개선하는 것이 더 쉬워집니다. 변화에 대한 저항이 있을 수 있지만 Agile 조직은 명확하게 배당금을 지급하는 변화를 소중히 여깁니다. 기능 개발을 통해 새로운 가치를 창출하는 데 더 많은 시간을 투자할 수 있기 때문에 더 적은 실패로 더 빠른 테스트 실행에 대한 비전을 쉽게 판매할 수 있어야 합니다.

DevOps 테스트 분류

테스트 분류 정의는 DevOps 테스트 프로세스의 중요한 측면입니다. DevOps 테스트 분류는 개별 테스트를 종속성 및 실행하는 데 걸리는 시간으로 분류합니다. 개발자는 다양한 시나리오에서 사용할 올바른 유형의 테스트와 프로세스의 여러 부분에 필요한 테스트를 이해해야 합니다. 대부분의 조직은 다음 네 가지 수준에서 테스트를 분류합니다.

  • L0L1 테스트는 단위 테스트이거나 테스트 중인 어셈블리의 코드에 의존하는 테스트이며 다른 테스트는 없습니다. L0은 빠른 메모리 내 단위 테스트의 광범위한 클래스입니다.
  • L2어셈블리와 SQL 또는 파일 시스템과 같은 다른 종속성이 필요할 수 있는 기능 테스트입니다.
  • L3 기능 테스트는 테스트 가능한 서비스 배포에 대해 실행됩니다. 이 테스트 범주에는 서비스 배포가 필요하지만 주요 서비스 종속성에 스텁을 사용할 수 있습니다.
  • L4 테스트는 프로덕션에 대해 실행되는 제한된 통합 테스트 클래스입니다. L4 테스트에는 전체 제품 배포가 필요합니다.

모든 테스트가 항상 실행되는 것이 이상적이지만 불가능합니다. Teams는 DevOps 프로세스에서 각 테스트를 실행할 위치를 선택하고, Shift-left 또는 shift-right 전략을 사용하여 프로세스의 이전 또는 나중에 다른 테스트 유형을 이동할 수 있습니다.

예를 들어 개발자가 커밋하기 전에 항상 L2 테스트를 실행하고, L3 테스트 실행이 실패하면 끌어오기 요청이 자동으로 실패하고, L4 테스트가 실패할 경우 배포가 차단될 수 있습니다. 특정 규칙은 조직마다 다를 수 있지만 조직 내의 모든 팀에 대한 기대치를 적용하면 모든 사람이 동일한 품질 비전 목표를 향해 이동합니다.

단위 테스트 지침

L0 및 L1 단위 테스트에 대한 엄격한 지침을 설정합니다. 이러한 테스트는 매우 빠르고 신뢰할 수 있어야 합니다. 예를 들어 어셈블리의 L0 테스트당 평균 실행 시간은 60밀리초 미만이어야 합니다. 어셈블리의 L1 테스트당 평균 실행 시간은 400밀리초 미만이어야 합니다. 이 수준의 테스트는 2초를 초과하지 않아야 합니다.

한 Microsoft 팀은 6분 이내에 60,000개가 넘는 단위 테스트를 병렬로 실행합니다. 그들의 목표는 이 시간을 1분 미만으로 줄이는 것입니다. 팀은 다음 차트와 같은 도구를 사용하여 단위 테스트 실행 시간을 추적하고 허용된 시간을 초과하는 테스트에 대한 버그를 파일로 추적합니다.

Chart that shows continuous focus on test execution time.

기능 테스트 지침

기능 테스트는 독립적이어야 합니다. L2 테스트의 주요 개념은 격리입니다. 올바르게 격리된 테스트는 실행 중인 환경을 완벽하게 제어할 수 있으므로 모든 시퀀스에서 안정적으로 실행할 수 있습니다. 테스트 시작 시 상태를 알고 있어야 합니다. 한 테스트가 데이터를 만들어 데이터베이스에 남겨 두면 다른 데이터베이스 상태에 의존하는 다른 테스트의 실행이 손상될 수 있습니다.

사용자 ID가 필요한 레거시 테스트는 ID를 가져오기 위해 외부 인증 공급자를 호출했을 수 있습니다. 이 연습에서는 몇 가지 과제를 소개합니다. 외부 종속성이 불안정하거나 일시적으로 사용할 수 없게 되어 테스트가 중단될 수 있습니다. 또한 이 방법은 테스트가 권한과 같은 ID의 상태를 변경할 수 있으므로 테스트 격리 원칙을 위반하여 다른 테스트에 대한 예기치 않은 기본 상태를 발생시킬 수 있습니다. 테스트 프레임워크 내에서 ID 지원에 투자하여 이러한 문제를 방지하는 것이 좋습니다.

DevOps 테스트 원칙

테스트 포트폴리오를 최신 DevOps 프로세스로 전환하려면 품질 비전을 명확하게 설명합니다. Teams는 DevOps 테스트 전략을 정의하고 구현할 때 다음 테스트 원칙을 준수해야 합니다.

Diagram that shows an example of a quality vision and lists test principles.

이전 테스트를 위해 왼쪽으로 이동

테스트를 실행하는 데 시간이 오래 걸릴 수 있습니다. 프로젝트의 규모가 커짐에 따라 테스트 번호와 형식이 크게 증가합니다. 테스트 도구 모음을 완료하는 데 몇 시간 또는 며칠이 걸리면 마지막 순간에 실행될 때까지 더 멀리 밀어낼 수 있습니다. 테스트의 코드 품질 이점은 코드가 커밋된 후 얼마 지나지 않아 실현되지 않습니다.

장기 실행 테스트는 조사하는 데 시간이 많이 걸리는 오류를 생성할 수도 있습니다. 팀은 특히 스프린트 초반에 실패에 대한 허용 범위를 구축할 수 있습니다. 이러한 허용 오차는 코드베이스 품질에 대한 인사이트를 사용하여 테스트의 가치를 훼손합니다. 또한 장기 실행, 막판 테스트는 코드를 배송할 수 있도록 알 수 없는 양의 기술 부채를 지불해야 하기 때문에 스프린트 종료 기대에 예측 불가능성을 추가합니다.

테스트를 왼쪽으로 이동하는 목표는 파이프라인의 앞부분에서 테스트 작업을 수행하여 품질 업스트림 이동하는 것입니다. 테스트 및 프로세스 개선의 조합을 통해 왼쪽으로 이동하면 테스트를 실행하는 데 걸리는 시간과 주기의 뒷부분에서 오류의 영향을 모두 줄일 수 있습니다. 왼쪽으로 이동하면 변경 내용이 기본 분기에 병합되기 전에 대부분의 테스트가 완료됩니다.

Diagram that shows the move to shift-left testing.

코드 품질을 개선하기 위해 특정 테스트 책임을 맡기는 것 외에도 팀은 DevOps 주기에서 다른 테스트 측면을 오른쪽 또는 나중에 이동하여 최종 제품을 개선할 수 있습니다. 자세한 내용은 프로덕션에서 테스트할 수 있는 Shift 권한을 참조하세요.

가능한 가장 낮은 수준에서 테스트 작성

더 많은 단위 테스트를 작성합니다. 외부 종속성이 가장 적은 테스트를 선호하고 빌드의 일부로 대부분의 테스트를 실행하는 데 집중합니다. 어셈블리 및 관련 테스트가 삭제되는 즉시 어셈블리에 대한 단위 테스트를 실행할 수 있는 병렬 빌드 시스템을 고려합니다. 이 수준에서 서비스의 모든 측면을 테스트하는 것은 불가능하지만, 더 무거운 기능 테스트와 동일한 결과를 생성할 수 있는 경우 더 가벼운 단위 테스트를 사용하는 것이 원칙입니다.

테스트 안정성 목표

신뢰할 수 없는 테스트는 기본 위해 조직적으로 비용이 많이 듭니다. 이러한 테스트는 자신 있게 변경하기 어렵게 함으로써 엔지니어링 효율성 목표에 대해 직접 작동합니다. 개발자는 어디서나 변경할 수 있어야 하며, 아무 것도 손상되지 않는다는 확신을 빠르게 얻을 수 있어야 합니다. 안정성을 위해 높은 막대를 유지 관리합니다. 신뢰할 수 없는 경향이 있으므로 UI 테스트 사용을 권장하지 않습니다.

어디서나 실행할 수 있는 기능 테스트 작성

테스트는 테스트를 사용하도록 특별히 설계된 특수 통합 지점을 사용할 수 있습니다. 이 연습의 한 가지 이유는 제품 자체의 테스트 용이성이 부족하기 때문입니다. 아쉽게도 이와 같은 테스트는 종종 내부 지식에 의존하며 기능 테스트 관점에서 중요하지 않은 구현 세부 정보를 사용합니다. 이러한 테스트는 일반적으로 프로덕션 배포를 제외하는 테스트를 실행하는 데 필요한 비밀 및 구성이 있는 환경으로 제한됩니다. 기능 테스트는 제품의 공용 API만 사용해야 합니다.

테스트 용 제품 디자인

성숙한 DevOps 프로세스의 조직은 클라우드 주기에서 품질 제품을 제공하는 것이 무엇을 의미하는지 완전히 파악합니다. 기능 테스트보다 단위 테스트를 위해 균형을 강력하게 전환하려면 팀에서 테스트 가능성을 지원하는 디자인 및 구현을 선택해야 합니다. 서로 다른 코딩 스타일이 있는 것처럼 테스트 가능성을 위해 잘 디자인되고 잘 구현된 코드를 구성하는 것에 대한 다양한 아이디어가 있습니다. 원칙은 테스트 용 설계가 디자인 및 코드 품질에 대한 논의의 주요 부분이 되어야 한다는 것입니다.

테스트 코드를 제품 코드로 처리

테스트 코드가 제품 코드임을 명시적으로 명시하면 테스트 코드의 품질이 제품 코드만큼 배송에 중요합니다. 팀은 제품 코드를 처리하는 것과 동일한 방식으로 테스트 코드를 처리하고 테스트 및 테스트 프레임워크의 디자인 및 구현에 동일한 수준의 주의를 기울여야 합니다. 이러한 노력은 구성 및 인프라를 코드로 관리하는 것과 비슷합니다. 완료하려면 코드 검토에서 테스트 코드를 고려하고 제품 코드와 동일한 품질 표시줄에 보관해야 합니다.

공유 테스트 인프라 사용

테스트 인프라를 사용하여 신뢰할 수 있는 품질 신호를 생성하기 위한 막대를 낮춥니다. 테스트를 전체 팀의 공유 서비스로 봅니다. 제품 코드와 함께 단위 테스트 코드를 저장하고 제품으로 빌드합니다. 빌드 프로세스의 일부로 실행되는 테스트도 Azure DevOps와 같은 개발 도구에서 실행해야 합니다. 로컬 개발부터 프로덕션까지 모든 환경에서 테스트를 실행할 수 있는 경우 제품 코드와 동일한 안정성을 갖습니다.

테스트를 담당하는 코드 소유자 만들기

테스트 코드는 리포지토리의 제품 코드 옆에 있어야 합니다. 구성 요소 경계에서 코드를 테스트하려면 구성 요소 코드를 작성하는 사람에게 테스트에 대한 책임을 푸시합니다. 구성 요소를 테스트하기 위해 다른 사용자를 사용하지 마세요.

사례 연구: 단위 테스트를 사용하여 왼쪽으로 이동

Microsoft 팀은 레거시 테스트 제품군을 최신 DevOps 단위 테스트 및 왼쪽 시프트 프로세스로 바꾸기로 결정했습니다. 팀은 다음 그래프와 같이 삼주 스프린트에서 진행률을 추적했습니다. 그래프는 126주 동안 또는 약 2년 반 동안 42개의 스프린트를 나타내는 스프린트 78-120을 다룹니다.

팀은 스프린트 78에서 27K 레거시 테스트에서 시작하여 S120에서 0개의 레거시 테스트에 도달했습니다. L0 및 L1 단위 테스트 집합은 대부분의 이전 기능 테스트를 대체했습니다. 새 L2 테스트는 일부 테스트를 대체했으며, 많은 이전 테스트가 삭제되었습니다.

Diagram that shows a sample test portfolio balance over time.

완료하는 데 2년이 넘게 걸리는 소프트웨어 경험에서는 프로세스 자체에서 많은 것을 배울 수 있습니다. 전반적으로 2년 동안 테스트 시스템을 완전히 다시 실행하려는 노력은 막대한 투자였습니다. 모든 기능 팀이 동시에 작업을 수행하지는 않았습니다. 조직 전체의 많은 팀이 모든 스프린트에 시간을 투자했으며, 일부 스프린트에서는 팀이 한 일의 대부분이었습니다. 교대 근무 비용을 측정하기는 어렵지만 팀의 품질 및 성과 목표에 대해 협상할 수 없는 요구 사항이었습니다.

시작하기

처음에 팀은 TRA 테스트라는 이전 기능 테스트를 단독으로 떠났습니다. 팀은 개발자가 단위 테스트 작성, 특히 새로운 기능에 대한 아이디어를 구매하기를 원했습니다. L0 및 L1 테스트를 최대한 쉽게 작성할 수 있도록 하는 데 초점을 맞춥니다. 팀은 먼저 해당 기능을 개발하고 모멘텀을 구축해야 했습니다.

이전 그래프는 팀이 단위 테스트 작성의 이점을 보았기 때문에 단위 테스트 수가 일찍 증가하기 시작하는 것을 보여 줍니다. 단위 테스트는 기본 달성하기 쉽고, 실행하는 속도가 빠르며, 실패가 적습니다. 끌어오기 요청 흐름에서 모든 단위 테스트 실행에 대한 지원을 쉽게 얻을 수 있었습니다.

팀은 스프린트 101까지 새로운 L2 테스트를 작성하는 데 집중하지 않았습니다. 그 동안 TRA 테스트 수는 스프린트 78에서 스프린트 101로 27,000에서 14,000으로 감소했습니다. 새로운 단위 테스트는 일부 TRA 테스트를 대체했지만, 그 유용성에 대한 팀 분석에 따라 많은 테스트가 단순히 삭제되었습니다.

TRA 테스트는 원본 트리에서 더 많은 테스트가 발견되어 그래프에 추가되었기 때문에 스프린트 110에서 2100에서 3800으로 뛰어올랐습니다. 테스트가 항상 실행 중이었지만 제대로 추적되지 않은 것으로 밝혀졌습니다. 이것은 위기가 아니었지만 필요에 따라 정직하고 재평가하는 것이 중요했습니다.

더 빨라지게

팀이 매우 빠르고 신뢰할 수 있는 CI(연속 통합) 신호를 갖게 되면 제품 품질에 대한 신뢰할 수 있는 지표가 되었습니다. 다음 스크린샷은 작동 중인 끌어오기 요청 및 CI 파이프라인과 다양한 단계를 거치는 데 걸리는 시간을 보여 줍니다.

Diagram that shows the pull request and rolling CI pipeline in action.

끌어오기 요청에서 병합으로 이동하는 데 약 30분이 걸리며, 여기에는 60,000개의 단위 테스트 실행이 포함됩니다. 코드 병합에서 CI 빌드까지 약 22분이 걸립니다. CI의 첫 번째 품질 신호인 SelfTest는 약 1시간 후에 제공됩니다. 그런 다음, 제품의 대부분은 제안 된 변경으로 테스트됩니다. Merge에서 SelfHost까지 2시간 이내에 전체 제품이 테스트되고 변경 내용이 프로덕션으로 전환될 준비가 된 것입니다.

메트릭 사용

팀은 다음 예제와 같이 점수를 카드 추적합니다. 높은 수준에서 점수카드 상태 또는 부채 및 속도라는 두 가지 유형의 메트릭을 추적합니다.

Diagram that shows a metrics scorecard for tracking test performance.

라이브 사이트 상태 메트릭의 경우 팀은 검색할 시간, 완화할 시간 및 팀에서 수행하는 복구 항목 수를 추적합니다. 복구 항목은 유사한 인시던트가 되풀이되지 않도록 팀이 라이브 사이트 회고에서 식별하는 작업입니다. 점수카드 또한 팀이 적절한 기간 내에 수리 항목을 닫는지 여부를 추적합니다.

엔지니어링 상태 메트릭의 경우 팀은 개발자당 활성 버그를 추적합니다. 팀에 개발자당 5개 이상의 버그가 있는 경우 팀은 새 기능 개발 전에 해당 버그를 수정하는 데 우선 순위를 지정해야 합니다. 또한 팀은 보안과 같은 특수 범주에서 노후화된 버그를 추적합니다.

엔지니어링 속도 메트릭은 CI/CD(연속 통합 및 지속적인 업데이트) 파이프라인의 여러 부분에서 속도를 측정합니다. 전체 목표는 아이디어에서 시작하여 코드를 프로덕션으로 가져오고 고객으로부터 데이터를 다시 받는 DevOps 파이프라인의 속도를 높이는 것입니다.

다음 단계