고품질 소프트웨어 엔지니어링을 위한 5가지 기본 빌딩 블록

일반적으로 사용자들은 불가피한 유지보수나 패치가 출시될 때까지 소프트웨어 어플리케이션을 최신 버전으로 유지하는 것을 꺼려합니다. 큰 기업들 역시 이러한 문제를 피하지 못합니다. 휴대폰에서 버그 "수리"와 같이 간단한 업데이트를 하고 주요 업데이트는 후에 배포하는데서 알 수 있습니다.  모든 사람이 초기 릴리즈와 안정된 릴리즈 사이에 존재하는 소프트웨어의 품질적 격차를 알고 있지만, 유감스럽게도 문제 해결을 위한 개선작업이 충분히 이루어지지 않고 있습니다.

 

이 기술 기사에서는 개발 그룹이 품질적 격차를 해소하는 데 도움이 되는 5가지 실행가능한 아이디어를 소개할 것입니다.

1. 테스트 커버리지

코드 커버리지 분석 사용

코드 커버리지 분석은 테스트 케이스가 실행한 어플리케이션 소스 중 테스트 대상에 해당하는 부분을 확인해 줍니다. 코드 커버리지를 분석은 테스트 활동의 완성도를 평가하는 가장 좋은 방법입니다. 코드 커버리지 평가 없이는 "아무것도 모르는 테스트"입니다.

 

그림 1 – 테스트 완성도를 평가하는 코드 커버리지 분석

물론 "100% 코드 커버리지"를 달성했다고 해서 어플리케이션이 완벽하다는 것을 증명하지는 않습니다. 하지만, 엔지니어링 고품질 소프트웨어의 주요한 구성 요소라는 것은 기억해야 합니다. 높은 수준의 안전을 요구하는 소프트웨어 개발에 있어 표준 프로세스로 코드 커버리지를 요구하고 있는 것도 이런 이유입니다.

 

 

백서를 통해 코드 커버리지에 대한 자세한 정보를 얻어보세요.

2. 유닛 테스트

유닛 테스트로 테스트 커버리지 개선

그림 2 - 유닛 테스트를 사용하여 커버리지 차이를 좁히는 100% 코드 커버리지

커버리지를 측정하면, 기존 테스트는 100% 미만의 커버리지 결과가 나올 가능성이 높습니다. 이는 테스터가 오류 케이스 또는 경계 조건이 아닌 일반 기능 테스트에 초점을 맞추었기 때문입니다.

커버리지 차이를 줄이는 확실한 방법은 추가적인 기능 테스트를 수행하는 것이지만 생산 환경에서 기능 테스트로 20~30% 어플리케이션 코드는 테스트하기 어려울 수 있습니다. 오류 처리를 트리거하기 위한 결함을 인가하기가 어렵기 때문입니다.

현장에서는 하이젠버그와 같이 치명적인 버그가 발생합니다, 이는 해당 어플리케이션과 전혀 연관이 없는 입력값의 조합에 의한 결과입니다. C 프로그래머에게는 초기화되지 않은 변수의 결과인 것으로 생각되거나 단순히 코드에서 문제가 있는 것처럼 보여 방해의 원인이 됩니다. [1]

이러한 이유로 low-level 유닛 테스트를 사용하는 것이 중요합니다. 이 테스트는 생산 환경에서는 불가능한 방식으로 오류를 인가해 오류 처리를 테스트하는 방식입니다.

3. 테스트 구조

진행하기 쉬운 테스트와 이해하기 쉬운 결과 생성

그림 3 - 테스트 피라미드 모델
그림 3 - 다른 종류의 테스트를 포함하는 피라미드 모델

이론적으로, 진행하기 쉬운 테스트와 이해하기 쉬운 테스트 결과 생성은 간단하다고 느껴질 수 있습니다. 그러나 실제로는 어려운 과제가 되기도 합니다. 통일화 되지 않은 테스트 환경이 구축되고 다른 엔지니어에 의해 유지되며 서로 다른 툴을 사용하기 때문입니다.

 

  • 유닛 테스트는 어플리케이션의 '로우레벨 빌딩 블록' 정확도를 증명하는데 사용합니다.
  • 서비스 & API 레이어 테스트는 서브 시스템 전체가 올바르게 작동하는지 증명하는데 사용합니다.
  • 휴먼-머신 인터페이스 테스트(HMI) / 기능 테스트는 최종 사용자의 관점에서 정확도를 증명하기 위해 개발되었습니다.

테스트가 이렇게 나누어지면 각 테스트 종류는 개발팀 구성원에게 공유되는 대신 각 항목에 해당하는 그룹의 엔지니어가 소유하고 유지합니다. 실제로, 대부분의 조직에서 QA 엔지니어가 개발자 수준의 테스트를 진행하거나 시스템 테스트를 하는 개발자가 되어 테스트를 진행하는 것은 불가능에 가깝습니다.

품질향상을 위해서는 개발팀 구성원 모두가 어플리케이션의 어떤 버전에서든 어떤 테스트라도 진행할 수 있어야 합니다.

이를 위한 핵심은 모든 테스트와 전제 조건, 예상하는 결과를 모두 포함하는 협업플랫폼입니다. 엔지니어는 단일 테스트 또는 "단 한 번의 클릭"으로 모든 테스트를 진행할 수 있어야 하며 실패한 테스트를 빠르게 디버그할 수 있어야 합니다.

 

추가 정보는 다음 문서에서 찾을 수 있습니다.

4. 테스트 효율성

자동, 병렬, 및 변경 기반 테스팅 시행

코드 커버리지 분석으로 테스트 완성도가 향상되고 전체 프로젝트에 배치되면 그 테스트가 단시간에 시행되는 것을 보장해야 합니다. 테스트가 여러 그룹으로 나뉜 이유 중 하나는 전체 시스템 테스트를 실행하는 데 몇 시간 또는 며칠이 걸릴 수 있기 때문입니다. 코드 한 줄만 변경했을 뿐인데, 10시간 동안 테스트를 진행하는 것은 개발자에게 부담이 될 수 있습니다. 그럼 테스트 완성도를 보장하면서 어떻게 테스트 시간을 줄일 수 있을까요?

정답은 병렬 및 변경 기반 테스팅을 이용하여 가변할 수 있는 테스트 구조를 구축하는 것입니다. 각각의 테스트는 강력하고, 작으며 빨라야 합니다. 우리는 너무도 쉽게 기존의 test suite에 새로운 테스트를 단순 결합합니다. 이는 결국 테스트를 취약하게 만들고 유지보수에 많은 시간을 소비하게 만듭니다. 테스트를 설계할 때 명심해야 할 것은 각 테스트는 다른 테스트의 출력에 의존하지 않고 자체 전제조건을 가지고 있어야 한다는 것입니다.

테스트 유지보수의 이점을 넘어 테스트를 강력하게 재설계해야 합니다.

  • 변경 기반 테스팅, 각 소프트웨어 변경으로 영향을 받은 부분만 테스트 진행
  • 병렬 테스트 실행, 수백 개의 테스트를 동시에 진행

많은 조직이 점진적(Incremental) 어플리케이션 개발 모델을  통해  소프트웨어 시스템을 개발한 반면, 대부분 테스트는 점진적 방식을 시행하지 않습니다.  지속적이며 점차적인 자동화 방식으로 수행되지 않고 주기적으로 빈번히 수행됩니다. 변경 기반 테스팅(CBT)은 코드 베이스 변경의 각각의 세트를 분석하고 그 변경으로 영향을 받은 모든 테스트의 서브 테스트를 현명하게 선택하고 전체 테스트의 진행 시간 중 일부 시간 동안 그 테스트가 완료되게 합니다. 추가로, 변경 기반 테스팅은 정확하고 지속적인 통합(CI) 개발 과정을 실행하기 위해 필요한 방법을 제공하며, CI 체크 단계에서 CBT는 빌드 상태를 검증하고 문제를 조기에 탐지하는 효율적인 방법을 제공합니다.

 

그림 4 – 코드 변경으로 영향을 받은 테스트 케이스의 회귀 테스트

속도를 더욱 향상하고 싶다면 병렬 테스팅을 고려해야 합니다. 테스트 플랫폼을 CI 서버 및 가상 테스트 기계와 통합함으로써, 전체 테스트 시간을 몇 시간에서 몇 분으로 또는 몇 분에서 몇 초로 줄일 수 있습니다.

5. 리팩토링

유지보수성 개선을 위한 코드 베이스 리팩토링

그림 5 – 코드 리팩토링 접근 방법

코드 리팩토링은 외부 인터페이스(API)의 변경 없이 어플리케이션 요소를 재구성하는 과정입니다.

리팩토링 없는 어플리케이션 코드는 몹시 복잡하며 시간이 지남에 따라 유지 보수하기가 어렵습니다. 그리고 새로운 기능 및 버그 수리 역시 기존의 기능성에 포함되어 있기 때문에 어플리케이션의 훌륭한 설계를 무너트립니다.

코드 리팩토링은 코드 가독성을 향상해주고 복잡성을 낮추어주며 유지보수 비용을 줄여줍니다. 예를 들어 코드 리팩토링이 잘 시행되는 경우, 숨겨져 있거나 잠복 또는 발견되지 않은 컴퓨터 버그의 문제를 해결하고 또 기본 로직을 간단히 하거나 불필요한 단계의 복잡성을 제거함으로써 시스템의 취약성을 해결하는데 도움을 줍니다.

모든 어플리케이션은 취약하고 깨지기 쉬운 부분이 존재합니다. 그리고 기존의 기능성을 깨는 두려움 때문에 개발자가 수정하기를 망설이게 됩니다. 이런 취약한 모듈을 우려 없이 리팩토링하는 유일한 방법은 예상되는 행동을 공식화하여 빌딩 테스트를 하는 것입니다.

결론

지난 30년간, 툴, 설계 패턴, 및 개발 패러다임의 변화가 끊임없이 이어져 왔습니다. 이들 중 많은 것들이 추가적 시간 또는 노력 없는 소프트웨어 품질 향상을 약속했습니다. 허나, 지금까지 소프트웨어 산업에서 "비용" 없이 향상된 품질을 제공하는 묘책은 없었으며 앞으로도 없을 것입니다. 소프트웨어 품질을 향상하는 합리적인 방법 중 유일한 것은 소프트웨어 테스트 효율성을 향상시키는 것입니다.

--------------------

 

참조:

[1] Hristov, Ivan. Chasing Heisenbugs from an AKKA actor integration test with awaitility. September 16, 2012. honeysoft.wordpress.com/category/heisenbug/