8장. 소프트웨어 테스팅
소프트웨어 테스팅 개요
프로그램 테스팅
•
프로그램이 의도한 대로 수행하는지를 보여주고 사용하기 전에 프로그램 결함을 발견하는 것을 목적으로 한다.
•
소프트웨어를 테스트할 때는 인공 데이터를 사용하여 프로그램을 실행한다.
•
테스트 실행 결과에서 오류, 이상 현상 또는 프로그램의 비기능적 속성에 대한 정보를 확인한다.
•
오류의 존재는 드러낼 수 있지만 오류의 부재는 증명할 수 없다.
•
정적 검증 기법도 포함하는 더 일반적인 검증과 확인 프로세스의 일부이다.
프로그램 테스팅 목표
1.
개발자와 고객에게 소프트웨어가 요구사항을 만족한다는 것을 보여주기
•
맞춤 소프트웨어: 요구사항 문서의 모든 요구사항에 대해 최소한 하나의 테스트가 있어야 함
•
일반 소프트웨어 제품: 제품 릴리스에 포함될 모든 시스템 기능과 기능들의 조합에 대한 테스트가 있어야 함
2.
소프트웨어의 동작이 잘못되거나 바람직하지 않거나 명세에 맞지 않는 상황 발견
•
결함 테스팅은 시스템 크래시, 다른 시스템과의 원치 않는 상호작용, 잘못된 계산, 데이터 손상 등 바람직하지 않은 시스템 동작을 찾아내는 것과 관련
검증(Verification)과 확인(Validation)
•
검증: "제품을 올바르게 구축하고 있는가"
◦
소프트웨어가 명세에 부합해야 한다.
•
확인: "올바른 제품을 구축하고 있는가"
◦
소프트웨어가 사용자가 실제로 요구하는 것을 수행해야 한다.
V&V 신뢰도
•
V&V의 목적은 시스템이 '목적에 적합'하다는 신뢰를 구축하는 것
•
시스템의 목적, 사용자 기대, 마케팅 환경에 따라 달라짐
◦
소프트웨어 목적: 소프트웨어가 조직에 얼마나 중요한지에 따라 신뢰 수준이 결정됨
◦
사용자 기대: 사용자가 특정 종류의 소프트웨어에 대해 낮은 기대를 가질 수 있음
◦
마케팅 환경: 프로그램의 결함을 찾는 것보다 제품을 시장에 일찍 출시하는 것이 더 중요할 수 있음
검사와 테스팅
소프트웨어 검사 (정적 검증)
•
문제를 발견하기 위해 정적 시스템 표현을 분석하는 것과 관련
•
도구 기반 문서 및 코드 분석으로 보완될 수 있음
소프트웨어 테스팅 (동적 검증)
•
제품 동작을 실행하고 관찰하는 것과 관련
•
시스템이 테스트 데이터로 실행되고 운영 동작이 관찰됨
검사의 장점
•
테스팅 중에는 오류가 다른 오류를 가릴 수 있지만, 검사는 정적 프로세스이므로 오류 간의 상호작용을 걱정할 필요가 없다.
•
시스템의 불완전한 버전도 추가 비용 없이 검사할 수 있다.
•
프로그램 결함을 찾는 것뿐만 아니라 표준 준수, 이식성, 유지보수성 등 프로그램의 더 광범위한 품질 속성도 고려할 수 있다.
검사와 테스팅의 관계
•
검사와 테스팅은 상호 보완적인 검증 기법이며 서로 대립하는 것이 아니다.
•
V&V 프로세스 중에 둘 다 사용되어야 한다.
•
검사는 명세와의 적합성은 확인할 수 있지만 고객의 실제 요구사항과의 적합성은 확인할 수 없다.
•
검사는 성능, 사용성 등과 같은 비기능적 특성은 확인할 수 없다.
개발 테스팅
개발 테스팅의 구성
시스템을 개발하는 팀이 수행하는 모든 테스팅 활동
1.
단위 테스팅
•
개별 프로그램 단위나 객체 클래스를 테스트
•
객체나 메소드의 기능성 테스팅에 중점
2.
컴포넌트 테스팅
•
여러 개별 단위를 통합하여 복합 컴포넌트를 만들어 테스트
•
컴포넌트 인터페이스 테스팅에 중점
3.
시스템 테스팅
•
시스템의 일부 또는 전체 컴포넌트를 통합하여 시스템 전체를 테스트
•
컴포넌트 상호작용 테스팅에 중점
단위 테스팅
•
개별 컴포넌트를 독립적으로 테스트하는 프로세스
•
결함 테스팅 프로세스
•
단위의 종류:
◦
객체 내의 개별 함수나 메소드
◦
여러 속성과 메소드를 가진 객체 클래스
◦
기능에 접근하기 위해 정의된 인터페이스를 가진 복합 컴포넌트
객체 클래스 테스팅
완전한 클래스 테스트 커버리지 포함 사항:
•
객체와 관련된 모든 연산 테스팅
•
모든 객체 속성 설정 및 조회
•
모든 가능한 상태에서 객체 실행
상속은 정보가 지역화되지 않기 때문에 객체 클래스 테스트 설계를 더 어렵게 만든다.
자동화된 테스팅
•
가능한 한 단위 테스팅은 자동화되어야 하며, 수동 개입 없이 테스트가 실행되고 확인되어야 한다.
•
자동화된 단위 테스팅에서는 JUnit과 같은 테스트 자동화 프레임워크를 사용한다.
•
단위 테스팅 프레임워크는 특정 테스트 케이스를 만들기 위해 확장할 수 있는 일반적인 테스트 클래스를 제공한다.
자동화된 테스트 구성요소
1.
설정 부분: 테스트 케이스(입력과 예상 출력)로 시스템을 초기화
2.
호출 부분: 테스트할 객체나 메소드를 호출
3.
검증 부분: 호출 결과를 예상 결과와 비교. 검증이 참이면 테스트 성공, 거짓이면 실패
단위 테스트 케이스 선택
테스트 케이스는 다음을 보여줄 수 있어야 한다:
•
예상대로 사용될 때 테스트하는 컴포넌트가 수행해야 할 작업을 수행한다는 것
•
컴포넌트에 결함이 있다면 테스트 케이스에 의해 드러날 수 있어야 함
이는 두 가지 유형의 단위 테스트 케이스로 이어진다:
1.
정상 동작 테스트: 프로그램의 정상 동작을 반영하고 컴포넌트가 예상대로 작동함을 보여줌
2.
비정상 입력 테스트: 테스팅 경험을 바탕으로 일반적인 문제가 발생하는 곳을 테스트. 비정상 입력을 사용하여 적절히 처리되고 컴포넌트를 충돌시키지 않는지 확인
테스팅 전략
1.
파티션 테스팅
•
공통 특성을 가지고 같은 방식으로 처리되어야 하는 입력 그룹을 식별
•
각 그룹에서 테스트를 선택해야 함
2.
가이드라인 기반 테스팅
•
테스팅 가이드라인을 사용하여 테스트 케이스를 선택
•
이러한 가이드라인은 프로그래머가 컴포넌트를 개발할 때 자주 범하는 오류 종류에 대한 이전 경험을 반영
일반적인 테스팅 가이드라인
•
시스템이 모든 오류 메시지를 생성하도록 하는 입력 선택
•
입력 버퍼를 오버플로우시키는 입력 설계
•
동일한 입력이나 입력 시리즈를 여러 번 반복
•
잘못된 출력이 생성되도록 강제
•
계산 결과가 너무 크거나 너무 작게 강제
컴포넌트 테스팅
•
소프트웨어 컴포넌트는 종종 여러 상호작용하는 객체로 구성된 복합 컴포넌트
•
정의된 컴포넌트 인터페이스를 통해 이러한 객체의 기능에 접근
•
복합 컴포넌트 테스팅은 컴포넌트 인터페이스가 명세에 따라 동작하는지 보여주는 데 중점을 둬야 함
인터페이스 테스팅
목적: 인터페이스 오류나 인터페이스에 대한 잘못된 가정으로 인한 결함을 탐지
인터페이스 유형
•
매개변수 인터페이스: 한 메소드나 프로시저에서 다른 것으로 전달되는 데이터
•
공유 메모리 인터페이스: 프로시저나 함수 간에 공유되는 메모리 블록
•
절차적 인터페이스: 다른 서브시스템에서 호출될 프로시저 세트를 캡슐화하는 서브시스템
•
메시지 전달 인터페이스: 다른 서브시스템에서 서비스를 요청하는 서브시스템
인터페이스 오류
1.
인터페이스 오용: 호출 컴포넌트가 다른 컴포넌트를 호출하면서 인터페이스 사용에서 오류를 범함 (예: 매개변수 순서 오류)
2.
인터페이스 오해: 호출 컴포넌트가 호출된 컴포넌트의 동작에 대한 잘못된 가정을 포함
3.
타이밍 오류: 호출된 컴포넌트와 호출하는 컴포넌트가 다른 속도로 동작하여 오래된 정보에 접근
인터페이스 테스팅 가이드라인
•
호출된 프로시저의 매개변수가 범위의 극단값에 있도록 테스트 설계
•
포인터 매개변수를 항상 null 포인터로 테스트
•
컴포넌트가 실패하도록 하는 테스트 설계
•
메시지 전달 시스템에서 스트레스 테스팅 사용
•
공유 메모리 시스템에서 컴포넌트가 활성화되는 순서를 다양하게 변경
시스템 테스팅
•
개발 중 시스템 테스팅은 시스템 버전을 만들기 위해 컴포넌트를 통합한 다음 통합된 시스템을 테스트하는 것과 관련
•
시스템 테스팅의 초점은 컴포넌트 간의 상호작용을 테스트하는 것
•
시스템 테스팅은 컴포넌트가 호환되고, 올바르게 상호작용하며, 인터페이스를 통해 적절한 시간에 올바른 데이터를 전송하는지 확인
•
시스템 테스팅은 시스템의 창발적 동작을 테스트
시스템과 컴포넌트 테스팅
•
시스템 테스팅 중에는 별도로 개발된 재사용 가능한 컴포넌트와 기성 시스템이 새로 개발된 컴포넌트와 통합될 수 있음
•
다른 팀 구성원이나 하위 팀에서 개발한 컴포넌트가 이 단계에서 통합될 수 있음
•
시스템 테스팅은 개별 프로세스가 아닌 집단적 프로세스
유스케이스 테스팅
•
시스템 상호작용을 식별하기 위해 개발된 유스케이스를 시스템 테스팅의 기반으로 사용할 수 있음
•
각 유스케이스는 보통 여러 시스템 컴포넌트를 포함하므로 유스케이스를 테스트하면 이러한 상호작용이 발생하게 됨
•
유스케이스와 관련된 시퀀스 다이어그램은 테스트되고 있는 컴포넌트와 상호작용을 문서화
테스팅 정책
•
완전한 시스템 테스팅은 불가능하므로 필요한 시스템 테스트 커버리지를 정의하는 테스팅 정책이 개발될 수 있음
테스팅 정책의 예
•
메뉴를 통해 접근되는 모든 시스템 기능을 테스트해야 함
•
같은 메뉴를 통해 접근되는 기능의 조합(예: 텍스트 서식)을 테스트해야 함
•
사용자 입력이 제공되는 경우, 모든 기능을 올바른 입력과 잘못된 입력 모두로 테스트해야 함
테스트 주도 개발 (TDD)
테스트 주도 개발
•
테스팅과 코드 개발을 상호 교차하는 프로그램 개발 접근법
•
테스트를 코드보다 먼저 작성하고 테스트를 '통과'하는 것이 개발의 중요한 동력
•
테스트를 위한 증분과 함께 코드를 증분적으로 개발
•
개발한 코드가 테스트를 통과할 때까지 다음 증분으로 이동하지 않음
•
익스트림 프로그래밍과 같은 애자일 방법의 일부로 도입되었지만, 계획 기반 개발 프로세스에서도 사용 가능
TDD 프로세스 활동
1.
필요한 기능성의 증분을 식별 (보통 작고 몇 줄의 코드로 구현 가능해야 함)
2.
이 기능성에 대한 테스트를 작성하고 자동화된 테스트로 구현
3.
구현된 다른 모든 테스트와 함께 테스트를 실행 (초기에는 기능성을 구현하지 않았으므로 새 테스트는 실패)
4.
기능성을 구현하고 테스트를 다시 실행
5.
모든 테스트가 성공적으로 실행되면 다음 기능성 청크 구현으로 이동
테스트 주도 개발의 이점
1.
코드 커버리지
•
작성하는 모든 코드 세그먼트에 최소한 하나의 관련 테스트가 있으므로 작성된 모든 코드에 최소한 하나의 테스트가 있음
2.
회귀 테스팅
•
프로그램이 개발됨에 따라 회귀 테스트 스위트가 증분적으로 개발됨
3.
디버깅 단순화
•
테스트가 실패하면 문제가 있는 위치가 명확해야 함. 새로 작성된 코드를 확인하고 수정해야 함
4.
시스템 문서화
•
테스트 자체가 코드가 수행해야 할 작업을 설명하는 문서의 한 형태
회귀 테스팅
•
변경이 이전에 작동하던 코드를 '망가뜨리지' 않았는지 시스템을 테스트하는 것
•
수동 테스팅 프로세스에서는 회귀 테스팅이 비용이 많이 들지만, 자동화된 테스팅에서는 간단하고 직관적
•
프로그램에 변경이 있을 때마다 모든 테스트가 다시 실행됨
•
변경이 커밋되기 전에 테스트가 '성공적으로' 실행되어야 함
릴리스 테스팅
릴리스 테스팅
•
개발 팀 외부에서 사용하기 위해 의도된 시스템의 특정 릴리스를 테스트하는 프로세스
•
릴리스 테스팅 프로세스의 주요 목표는 시스템 공급자에게 시스템이 사용하기에 충분히 좋다고 확신시키는 것
•
시스템이 지정된 기능성, 성능, 신뢰성을 제공하고 정상 사용 중에 실패하지 않는다는 것을 보여줘야 함
•
릴리스 테스팅은 보통 시스템 명세에서만 테스트가 도출되는 블랙박스 테스팅 프로세스
릴리스 테스팅과 시스템 테스팅
릴리스 테스팅은 시스템 테스팅의 한 형태이지만 중요한 차이점이 있다:
1.
별도 팀: 시스템 개발에 관여하지 않은 별도 팀이 릴리스 테스팅을 담당해야 함
2.
목적의 차이:
•
개발 팀의 시스템 테스팅은 시스템의 버그 발견에 중점 (결함 테스팅)
•
릴리스 테스팅의 목적은 시스템이 요구사항을 만족하고 외부 사용에 충분한지 확인 (확인 테스팅)
요구사항 기반 테스팅
•
각 요구사항을 검토하고 이에 대한 테스트나 테스트들을 개발하는 것과 관련
•
요구사항 기반 테스팅 예시 (Mentcare 시스템):
◦
환자가 특정 약물에 알레르기가 있다고 알려진 경우, 해당 약물 처방 시 시스템 사용자에게 경고 메시지가 발행되어야 함
◦
처방자가 알레르기 경고를 무시하기로 선택한 경우, 이를 무시한 이유를 제공해야 함
시나리오 테스팅
•
전형적인 사용 시나리오를 발명하고 이를 사용하여 테스트 케이스를 도출하는 것과 관련
•
시나리오는 시스템 사용의 실제 세션을 반영해야 함
성능 테스팅
•
릴리스 테스팅의 일부는 성능, 신뢰성과 같은 시스템의 창발적 속성을 테스트하는 것을 포함할 수 있음
•
테스트는 시스템의 사용 프로파일을 반영해야 함
•
성능 테스트는 보통 시스템 성능이 허용할 수 없는 수준이 될 때까지 부하를 꾸준히 증가시키는 일련의 테스트를 계획하는 것과 관련
•
스트레스 테스팅은 시스템을 의도적으로 과부하시켜 실패 동작을 테스트하는 성능 테스팅의 한 형태
사용자 테스팅
사용자 테스팅
•
사용자나 고객이 시스템 테스팅에 대한 입력과 조언을 제공하는 테스팅 프로세스의 단계
•
포괄적인 시스템 및 릴리스 테스팅이 수행되었더라도 사용자 테스팅은 필수적
•
이유: 사용자의 작업 환경의 영향이 시스템의 신뢰성, 성능, 사용성, 견고성에 주요한 영향을 미치며, 이는 테스팅 환경에서 복제될 수 없음
사용자 테스팅의 유형
1.
알파 테스팅
•
소프트웨어의 (선별된 그룹) 사용자가 개발 팀과 함께 개발자 사이트에서 소프트웨어를 테스트
2.
베타 테스팅
•
소프트웨어의 릴리스가 (더 많은) 사용자에게 제공되어 실험하고 시스템 개발자에게 발견한 문제를 제기할 수 있도록 함
3.
수용 테스팅
•
고객이 시스템 개발자로부터 수용하고 고객 환경에 배포할 준비가 되었는지 결정하기 위해 시스템을 테스트. 주로 맞춤 시스템용
수용 테스팅 프로세스
1.
수용 기준 정의
2.
수용 테스팅 계획
3.
수용 테스트 도출
4.
수용 테스트 실행
5.
테스트 결과 협상
6.
시스템 거부/수용
애자일 방법과 수용 테스팅
•
애자일 방법에서는 사용자/고객이 개발 팀의 일부이며 시스템의 수용 가능성에 대한 결정을 내릴 책임이 있음
•
테스트는 사용자/고객에 의해 정의되고 변경 시 자동으로 실행되는 다른 테스트와 통합됨
•
별도의 수용 테스팅 프로세스가 없음
•
여기서 주요 문제는 포함된 사용자가 '전형적'이고 모든 시스템 이해관계자의 이익을 대표할 수 있는지 여부
핵심 내용
•
테스팅은 프로그램에서 오류의 존재만 보여줄 수 있으며, 남아있는 결함이 없다는 것을 증명할 수는 없다.
•
개발 테스팅은 소프트웨어 개발 팀의 책임이다. 별도 팀이 고객에게 릴리스되기 전에 시스템을 테스트할 책임을 져야 한다.
•
개발 테스팅은 개별 객체와 메소드를 테스트하는 단위 테스팅, 관련 객체 그룹을 테스트하는 컴포넌트 테스팅, 부분적 또는 완전한 시스템을 테스트하는 시스템 테스팅을 포함한다.
•
소프트웨어를 테스트할 때는 다른 시스템에서 결함을 발견하는 데 효과적이었던 테스트 케이스 유형을 선택하기 위해 경험과 가이드라인을 사용하여 소프트웨어를 '망가뜨리려고' 시도해야 한다.
•
가능한 한 자동화된 테스트를 작성해야 한다. 테스트는 시스템에 변경이 있을 때마다 실행할 수 있는 프로그램에 포함된다.
•
테스트 주도 개발(TDD)은 테스트할 코드보다 먼저 테스트를 작성하는 개발 접근법이다.
•
시나리오 테스팅은 전형적인 사용 시나리오를 발명하고 이를 사용하여 테스트 케이스를 도출하는 것과 관련된다.
•
수용 테스팅은 소프트웨어가 운영 환경에서 배포되고 사용하기에 충분한지 결정하는 것을 목표로 하는 사용자 테스팅 프로세스이다.