현재 소프트웨어는 매우 복잡하며, 오류 발생 시 사회·경제적 손실이 막대하다. 다음 세 가지 실제 사례는 소프트웨어 오류가 얼마나 심각한 결과를 초래하는지 보여준다.
테스트 비용이 매우 크다.
Microsoft 사례에서 언급된 내용에 따르면,
즉, 품질 확보는 개발보다 더 많은 자원을 요구할 수 있다.
이 문제는 단순해 보이지만 고려해야 할 사항이 많다.
소프트웨어는 지속적으로 변하므로 테스트 또한 지속적으로 수행되어야 한다.
int에서 float으로 바꾸면 오류 처리가 필요하다.코드 변경 시 기존 기능이 깨지지 않는지 변경의 안전성을 보장하기 위해서 반복적으로 검사하는 regression testing이 중요하다.
기존 기능이 깨지지 않았는지 테스트하는 활동인 regression testing은 서로 다른 코드 두 개의 결과를 비교해서 이미 발견된 버그가 재발하지 않도록 방지하고 품질을 유지할 수 있다.
예를 들어 두 개의 스레드 각각 세 개의 연산을 수행한다면 가능한 스케줄은 이지만 결과는 11 가지로 수렴한다. 이때 어떠한 곳에서 문제가 발생하는지 찾는 것은 매우 어렵다.
Concurrency로 인한 bug는 재현성이 낮고, 타이밍에 따라 드러나므로 테스트로 발견하기 가장 어려운 유형 중 하나이다.
Verification은 명세/설계/코드가 올바른지 확인하는 것을 목적으로 한다. Validation은 사용자 요구를 충족하는 것을 목적으로 한다.
예를 들어 계산기 앱이 사칙연산을 정확히 수행하는지 확인하는 것은 verification, 계산기의 기능이 실제 사용자 니즈를 충족하는지 확인하는 것은 validation이다.
올바르게 만들었는가 vs 올바른 것을 만들었는가
소프트웨어 시스템은 구조, 규모, 사용 목적이 다양한다. 게임은 사용성, 반응성이 중요하고 항공 소프트웨어는 안전성과 신뢰성이 요구된다. 따라서 어떤 분석 기법을 사용할지는 시스템의 종류, 규모, 품질 목표, 자원 등에 따라 달라진다.
소프트웨어 분석은 소프트웨어 산출물 (artifact)을 체계적으로 분석하여 그 속성을 파악하는 것을 뜻한다. 여기서 artifact의 범위는 매우 넓다.
다양한 분석/테스트 기법이 존재하지만, 어떤 기술도 단독으로는 충분하지 않다. 또한 시스템 특성, 품질 목적, 보안/안전 요구사항, 예산 등에 따라 기술 조합이 달라진다. 따라서 test strategy를 설계하는 것이 소프트웨어 품질 보증의 핵심이다.
(전통적 소프트웨어 테스팅 즉, 기능적 정확성을 중심으로 한 테스트의 목표와 한계)
Testing은 통제된 환경에서 테스트 데이터를 사용하여 실제 코드를 실행하는 활동을 칭한다. 즉, 테스트는 컴파일만 하거나 코드를 눈으로 보는 것이 아니라 프로그램을 실행한 결과를 관찰하는 활동이라는 점이 핵심이다.
Testing의 주요 목적은 다음과 같다.
또한 testing은 결함을 발견하고 품질을 평가하고, 명세와 문서를 명확히하는 것도 목적으로 한다.
하지만 아무리 많은 테스트를 수행해도 실행되지 않는 경로에 숨어 있는 버그는 여전히 존재할 수 없다. 따라서 테스트는 버그 "없음"을 절대 보장할 수 없다.
| 용어 | 정의 | 예시 |
|---|---|---|
| Error | 사람이 만든 실수 | time=0인 경우를 고려하지 않음 |
| Fault (Defect, Bug) | 코드에 존재하는 문제 | divide-by-zero 예외 처리가 없음 |
| Failure | Fault가 실행되어 실제로 잘못된 결과 발생 | time=0 실행 시 예외 발생 |
Error가 fault로 fault가 failure로 이어질 수 있지만 fault가 실행되지 않으면 failure는 발생하지 않는다.
| Level | 설명 |
|---|---|
| Unit Testing | 개별 모듈 (함수/클래스)을 독립적으로 테스트 |
| Integration Testing | 모듈 간 상호작용 테스트 |
| System Testing | 전체 시스템이 하나의 제품으로서 정상 동작하는지 화인 |
테스트를 먼저 작성하는 개발 방법을 Test-Driven Development라고 한다. 이때 실패하는 테스트 없이는 코드를 작성하면 안 된다. 이러한 agile 기법의 기대 효과는 다음과 같다.
코드가 커밋될 때마다 자동으로 빌드하고 테스트를 실행해서 결과를 기록하면 빠른 피드백을 통해 회귀 발생을 빠르게 감지할 수 있고 협업 개발 시 품질 유지에 도움이 된다.
프로그램이 정답을 출력했는지 자동으로 판단하기 어렵다는 근본적인 문제가 있다.
실제 시스템에서 Input generator로 이상적 정답인 golden oracle을 만들기는 어렵다. 결론적으로 테스트에서 오라클 비용은 매우 크며, 자동화의 가장 큰 장애 요소 중 하나이다.
기능적 정확성만으로 실제 소프트웨어 품질을 충분히 설명할 수 없다. 테스트는 기능과는 다른 영역 (사용성, 보안, 성능)으르 포함해야 하지만, 정답이 명확하지 않고 자동화가 쉽지 않다.
사용성 테스트가 어려운 이유는 아래와 같다.
즉 사용성은 기능처럼 정답이 있는 테스트가 아니며, 사람의 행동과 경험이 관여하기 때문에 자동화가 매우 어렵다.
또한 GUI/Web 사용성을 테스팅하는 것 또한 상당히 어려운 문제이다.
수동 테스트에서는 다음과 같은 고려사항이 있다.
즉, 사용성 테스트는 잦동화가 어렵고 비용이 크며, 정량 평가가 어렵다는 특징을 가진다.
Usability 평가에서 가장 실용적인 접근 중 하나로 A/B Testing이 있다. A와 B 두 변형을 그룹에 나누고 클릭 수 같이 실제 사용자 행동을 지표로 삼아 비교하는 방법이다. 이는 광고, GUI 배치, UX 디자인 결정에 자주 사용된다.
A/B Testing은 표본을 기반으로 하는 통계적인 비교이기 때문에 명확한 정답이 없는 사용성 평가에서 실질적인 도구가 된다.
보안 테스트는 기능 테스트보다 훨씬 복잡하며, 다음의 근본적 문제들이 있다.
프로그램의 입력 도메인에서 무작위 입력을 생성해서 테스팅하는 방식이다. 단순하지만 의외로 강력하며, 대규모 시스템 테스트에 활용된다.
예를 들어 23,000개의 무작위 입력에서 실패가 발생하지 않았다면 이 프로그램의 실패율은 최소 90%의 신뢰도로 1/10,000 이하로 추정할 수 있다. 즉, random testing은 기능 정확성보다 신뢰성을 확률적을 ㅗ보증할 수 있다는 장점이 있다.
잘못된 입력이나 예상하지 못한 입력, 랜덤 변형 입력을 지속적으로 주입하여 보안 취약점이나 심각한 오류를 찾아내는 기법이다. 서비스가 중단되는 원인을 찾거나 강한 오류를 발생시키는 문제를 발견할 수 있다.
Fuzzing으로 발견할 수 있는 버그 유형은
Fuzzing은 다음과 같은 흐름으로 진행된다.
(AFL, libFuzzer 같은 현대적 fuzzer가 이 방식을 사용한다.)
Fuzzing은 단순 무작위가 아니라 새로운 코드 경로를 여는 입력을 학습하며 진화한다.
성능 테스트는 기능이 정확히 돌아가는지보다, 시스템이 얼마나 빠르고 안정적으로 동작하는지를 검증하는 영역이다.
핵심 컴포넌트들의 실행 시간을 측정해서 이전 버전과 비교한다. 성능 저하가 regression으로 발생했는지 감지할 수 있다. 또한 bottleneck을 빠르게 찾아낼 수 있다.
CPU 사용량, 메모리 사용량을 파악하고 어떤 함수가 전체 실행 시간을 잡아먹는지 식별할 수 있는 기법이다. 이를 통해 어디를 최적화해야 하는지 알 수 있다.
코드가 완성되기 전에 모델링과 시뮬레이션을 통해 성능을 예측할 수 있다.
예를 들어 queuing 모델에서 서버 수, 요청 빈도, 처리 속도 등을 기반으로 병목을 예측할 수 있다. 이처럼 성능 테스트는 구현 이후뿐 아니라 설계 단계에서도 가능하다.
정상 범위를 넘어서는 극단적인 부하를 주면서 강건성을 측정하는 기법이다.
소프트웨어의 오류 처리 능력을 파악하고, 복구 가능성을 예상할 수 있어서 결국 서비스의 지속성을 보장할 수 있는지 판단할 수 있다.
메모리 누수 같은 문제들은 짧은 테스트에서는 드러나지 않을 수 있다. Soak testing은 오랜 시간 동안 일정한 부하를 주어 시스템을 테스트하는 기법이다.
메모리 누수를 포함하여 리소스가 고갈되는 문제처럼 오랜 시간 동안 실행했을 때 발생하는 문제를 탐지할 수 있다.
넷플릭스에서는 무작위로 서버 인스턴스를 종료시키거나 네트워크 지연, 장애를 강제로 유발하여 시스템이 얼마나 탄력적인지 평가한다. 이 방법은 전통적 소프트웨어 테스트가 아니라, 대규모 분산 시스템의 안정성과 회복력을 검증하는 기법이다.