임베디드 소프트웨어는 다양한 분야에서 폭넓게 활용되고, 이는 실생활과 매우 밀접한 관계를 가진다. IT와 관련된 제품들은 오류를 발생하면 심각한 결과로 이어질 가능성이 있다.
특히나 의료기기나 자동차와 같이 문제가 발생하면 생명에 직결되는 경우에는 심각한 상황을 연출하게 된다.
Therac-25는 1976년 캐나다 AECL이 개발한 방사능 치료 기기로, 1982년에 하드웨어 제어 방식에서 소프트웨어 제어 방식으로 시스템을 변경했다.

1985년에 첫 의료 사고가 발생한 이후 19개월이라는 긴 시간 동안 6건의 사고가 추가로 발생했으며, 이로 인해 3명이 사망하고 3명이 심각한 장애를 입었다. 이후 2000년 파나마 시티에서는 유사한 원인으로 28명이 방사능에 노출되어 23명이 사망하는 참사가 발생했다.
사고 원인에 대해서 알아보기 위해서는 기기에 대한 속성을 파악할 필요가 있다. 해당 기기는 치료실과 제어실이 분리되어 운영중이었으며 기기는 두 가지 동작 모드를 가졌는데, X-ray 모드는 텅스텐 스크린 막을 통과시켜 얕은 종양을 치료하는 약한 방사능 모드이고, Electron 모드는 피부 깊숙한 종양 치료에 쓰이는 모드이다.
사고 시나리오를 살펴보면 오퍼레이터가 실수로 X-ray 모드를 설정했다가 이를 인지하고 급하게 Electron 모드로 변경하여 치료를 시작했을 때, 기기 내에 X-ray 모드 수준의 강력한 방사능이 세팅된 상태로 환자에게 그대로 노출되어 버리는 심각한 결함이 있었습니다
사고 이후에 조사에 따르면, 단 한 명의 프로그래머가 제어 소프트웨어를 작성했으며 개발 완료 후 어떠한 소프트웨어 검증 절차도 거치지 않았다고한다. 그리고 최초 사고 후 여러차례 검증하였으나 원인을 찾지 못했고 이를 통해 임베디드 소프트웨어의 오류는 재현이 어렵다는 것을 확인할 수 있다. 결론적으로 원인을 찾지 못하고 19개월간 시스템을 그대로 사용하게 됩니다.
이 사고를 통해 얻을 수 있는 주안점은, 임베디드 소프트웨어는 엄격한 기능 안전 표준(Functional Safety Standards)을 바탕으로 철저한 신뢰성 및 안전성 검증을 반드시 거쳐야 한다는 것**이다.
소프트웨어 테스팅은 시스템에 대해 다음과 같은 가장 중요한 5가지 질문을 던지고 답을 구하는 과정이다.

위의 질문들이 커질수록 전체 시간에서 구현과 개발자 테스트가 차지하는 비중은 줄어들게 되지만, 역설적으로 전체 프로젝트 오류 중 소스 코드 구현 시 발생하는 오류의 비중이 가장 높기에 체계적인 테스트를 진행해야한다.
소스 코드 검증 기법은 저번 포스트에서 언급한대로 총 두가지의 방향으로 나뉜다.

우선 1) 정적 분석(Static Analysis)는 코드를 실행하지 않고 인스펙션을 통해 오류를 사전에 예방하는 기법이다. 다음으로 2) 동적 분석(Dynamic Analysis)로 불리는 실제 소프트웨어 소스 코드를 실행하여 오류를 발견하는 기법이 존재한다.
Dynamic Testing에는 아래의 4가지 개념이 존재한다.
![]() | ![]() |
|---|
테스팅은 프로그램에 오류나 결함이 '존재함'을 밝혀낼 수는 있지만, 결함이 전혀 없음을 증명할 수는 없다.
수많은 테스팅 기법이 존재하지만, 단일 방법 하나만으로 평균 75% 이상의 오류 감지율을 보이는 것은 없고, 오류 감지율을 극대화하려면 V-모델과 같은 생명주기에 맞추어 반드시 다양한 검증 기법을 병행해야 한다.
프로그램 테스트는 프로그램이 의도대로 작동하는지 확인하고 사용하기 전에 결함을 발견하기위해 진행된다. 데이터를 만들어서 프로그램을 실행하며, 실행 결과를 검토해 프로그램의 문제를 파악한다. 이 때, 테스트를 통해서는 존재의 여부를 알 수 있지만 오류가 없다는 것을 보장하지는 않는다는 것을 유의해야한다.
Program을 테스팅을 할 때 우리는 두가지 용어를 주의깊게 살필 필요가 있다.
1) Verification(검증)은 "우리가 제품을 올바르게 만들고 있는가?" 에 대한 질문으로 소프트웨어가 설계 명세서를 정확히 준수하는지를 확인하는 것을 말하고 2) Validation(확인)은 "우리가 올바른 제품을 만들고 있는가?" 에 대한 질문으로 소프트웨어가 사용자가 실제로 요구하는 바를 잘 수행하는지를 확인하는 역할을 맡게된다.
그리고 Program testing에는 목적이 두가지 존재하는데,
첫째로 Validation testing(확인 테스팅)은 개발자와 고객에게 소프트웨어가 요구사항을 충족한다는 것을 증명하기 위해 사용하며 정상적인 시스템 사용 환경을 반영한 테스트 케이스를 사용한다.
둘째로 Defect testing(결함 테스팅)은 시스템이 부정확하거나 명세를 위반하는 상황을 의도적으로 찾아내기 위해 사용하며 사용자가 평소에 사용하지 않는 모호하거나 극단적인 테스트 케이스를 고의로 주입하여 결함을 노출시킵니다
그 중 하나인 Input-Output 모델은 다음과 같은 형태로 동작한다.

Inspections(인스펙션)은 정적 분석이라고도 불리며, 시스템의 정적인 결과물(Code, Doc 등)을 분석해 문제를 발견하는 정적인 검증 기법이다. 근본적으로 코드 실행을 하지 않고 그 전 단계에서 오류를 사전에 예방하고자하는 목적으로 사용한다.
Testing(테스팅)은 동적 분석이라고도 하며, 테스트 데이터를 주입해 시스템을 작동시키고 동작을 관찰하는 동적 검증 기법이다. 인스펙션과 달리 코드를 실제로 실행시키고 이를 통해 오류를 발견하는 것에 초점을 두고 있다.

인스펙션은 3가지의 장점을 가진다.
첫째로 오류 간섭(Masking) 회피이다. '오류 간섭'이란 테스팅 과정에서 하나의 오류가 다른 오류를 숨기는 현상이 발생하는 것을 말한다. 테스팅이 가지는 치명적인 단점이 인스펙션에서는 독립적으로 결함을 찾을 수 있어 이를 회피할 수 있다.
둘째로 비용의 효율성이다. 시스템이 불완전할 때 테스팅을 하려면 특수한 Test Harnesses를 개발하여 사용 가능 부분을 실행해야하므로 추가 비용이 발생한다. 인스펙션은 문서 기반이므로 미완성인 코드에 대해 검사를 진행해도 비용을 추가로 지불할 필요가 없다.
마지막으로 광범위한 품질 속성 평가이다. 인스펙션은 버그를 찾는 것 뿐아니라 프로그램 전반의 품질에 대한 속성들을 종합적으로 평가하기에 용이하다.
시스템의 Performance나 사용성(Usability) 같은 시스템 작동 시 발생하는 비기능적 특징은 인스펙션으로 확인할 수 없고 오직 동적 시스템 실행(Testing)을 통해서만 점검할 수 있다. 고로 어느 한가지 기법만을 이용하는 것이 아닌 두 기법을 병행하여 V&V(Verification & Validation) 프로세스 전반에 적용해야한다.
소프트웨어 테스팅은 시간 흐름 및 목적에 따라 크게 1) Development Testing, 2) Release Testing, 3) User Testing 의 3단계로 나뉜다.
시스템을 개발하는 내부 팀이 중심이 되어 버그와 결함을 발견하기 위해 직접 수행하는 과정으로, 범위에 따라 3단계로 세분화된다.
개별 기능, 메서드, 객체 클래스 등을 완벽히 격리된 상태에서 테스트하는 '결함 테스트' 이다.

Unit Testing의 전략으로는 '방어벽(Barricade) 모듈' 을 두는 것이 있다. 신뢰할 수 없는 오염된 외부 입력 데이터를 정상 입력과 비정상 입력으로 걸러내고 정제하여 안전 구역(Clean Area) 내의 내부 클래스들은 검증된 정상 입력만을 처리하도록 설계하여 테스트한다.
또한 자동화라는 기술도 존재하는데, 이는 수동 개입으로 인한 비효율을 막기 위해 실행과 결과가 자동으로 확인되도록 가급적 자동화(Automated)가 되어야한다. 90년대 중반 Kent Beck이 Smalltalk용 xUnit을 개발한 이후, 현재는 Java를 위한 JUnit (Eclipse 등 IDE에 통합)이 테스트 주도 개발(TDD)의 표준 도구로 자리 잡았으며, 다양한 언어(C++, Python 등)로 확장되었다.
여러 개별 객체가 통합된 복합 컴포넌트를 테스트합니다. 하위 객체들의 단위 테스트가 끝났다고 가정하고, 구성 요소 간의 인터페이스가 명세대로 동작하는지 확인하는지를 점검한다.
이때 인터페이스 유형은 데이터나 변수를 넘겨주는 파라미터 전달(Parameter), 특정 메모리 블록을 공유하는 공유 메모리(Shared memory), 서브 시스템 간 서비스 요청인 메시지 전달(Message passing) 인터페이스 등이 있다.
주로 발견되는 Interface Errors 를 살펴보자면 아래의 3가지 경우로 나눌 수 있다.
- Misuse
- 파라미터 순서를 잘못 넣는 등 호출하는 컴포넌트가 인터페이스를 잘못 사용하는 경우
- Misunderstanding
- 호출하는 측이 호출받는 쪽의 동작 방식에 대해 잘못된 가정을 하여 발생하는 오류,
- Timing errors
- 데이터를 요청하는 쪽과 제공하는 쪽의 작동 속도가 달라서 과거의 잘못된 정보에 접근하는 오류
개발 테스트의 가장 마지막 단계로, 모든 단위와 컴포넌트(외부의 기성 시스템이나 타 팀이 개발한 모듈 포함)를 하나로 통합하여 전체 시스템을 테스트한다. 완전히 통합된 환경에서 컴포넌트들이 정상적으로 호환되는지, 적절한 시점에 올바른 데이터를 전송하며 상호작용하는지를 점검한다.
개별 모듈 단위에서는 관찰할 수 없고 결합된 상태에서 비로소 나타나는 비기능적 요구사항인 창발적 특성(Emergent Behavior) 를 확인할 수 있는 필수적 단계라고 볼 수 있으며, 개발팀이 수행하는 시스템 테스트는 여전히 내부 시스템의 버그를 찾아내기 위한 결함 테스트에 목적을 둔다는 점에서 2단계인 Release Testing과는 명확하게 구별되고 1단계인 Development Testing에 포함된다.
릴리스 테스트는 개발팀 외부에서 사용할 목적으로 특정 소프트웨어의 릴리스 버전을 테스트하는 것을 말한다. 고장 여부, 명세된 기능과 성능, 신뢰성을 제대로 제공하는지를 확인하는 단계로 볼 수 있다.
주 목적은 결함 테스트(Defect Testing)인 개발 테스트와 다르게 시스템이 외부 사용자를 위해 제공되기에 충분히 훌륭한지에 대해 확신을 주기 위한 확인 테스트(Validation Testing) 의 성격을 가지고, 또한 명세서의 내용에만 의존하여 테스트를 설계하기에 블랙박스(Black-Box) 테스팅으로 진행된다.
이외에도 여러 테스트를 진행하게되는데, 사용자의 실제 사용 프로필을 반영해 시스템 성능이 허용 범위를 넘어서 수용할 수 없을 때까지 부하를 꾸준히 증가시키는 성능 테스트와 고장이 났을 때의 반응을 볼 수 있는 스트레스 테스트도 수행한다.
시스템 내부에서 아무리 포괄적인 테스트를 거치더라도 사용자의 실제 작업 환경에서 발생하는 예상치 못한 영향(신뢰성, 성능, 내구성 등)을 테스트 환경이 완벽히 복제할 수는 없으므로, 고객과 잠재 사용자가 직접 시스템 테스트에 참여하는 필수 단계다.
총 3가지의 테스트로 분류되며, 첫째로 1)알파 테스트 (Alpha testing) 는 실제 사용자가 시스템 '개발자 측의 사이트(환경)'에 와서 개발팀과 함께 소프트웨어를 테스트하는 것을 말하고 다음인 2)베타 테스트 (Beta testing) 는 다수의 일반 사용자에게 소프트웨어 릴리스를 배포하여, 사용자들 '자신의 환경'에서 자유롭게 실험해 보고 발견된 문제점들을 개발팀에 제기하도록 한다. 마지막으로 3)인수 테스트 (Acceptance testing) 는 최종 고객이 이 소프트웨어 시스템을 개발팀으로부터 인수하여 고객의 실제 운영 환경에 공식적으로 배포(deploy)할 것인지를 최종 결정하기 위해 직접 수행하는 과정을 말한다.