•
테스트는 가장 보편적으로 사용되는 품질 개선 활동으로 다양한 기업, 학술 연구가 뒷받침하고 있는 방법이다.
•
다양한 방법으로 테스트되며 어떤 것은 개발자가 수행하고 어떤 것은 전문 테스터가 수행한다.
◦
단위 테스트는 개발자나 팀이 작성한 클래스나 메소드, 작은 프로그램을 실해앟는 것으로 완성된 시스템과는 별개로 테스트한다.
◦
컴포넌트 테스트는 여러 개발자나 팀이 참여하는 클래스, 패키지, 소형 프로그램, 다른 프로그램의 요소를 테스트하는 것으로 더 완전한 시스템과 별개로 테스트한다.
◦
통합 테스트는 여러 개발자나 팀이 만든 클래스나 패키지, 컴포넌트, 서브 시스템을 두 개 이상 결합해 실행하는 것이다.
▪
이와 같은 테스트는 전형적으로 테스트해야 하는 클래스가 두 개가 되는 순간 시작해서 전체 시스템 개발이 완료될 때까지 지속적으로 수행한다.
◦
회귀 테스트는 이전에 통과했던 테스트 집합을 가지고 소프트웨어의 결함을 찾기 위해 반복하는 것이다.
◦
시스템 테스트는 다른 소프트웨어와 하드웨어 시스템과의 통합을 포함한 최종 환경에서 소프트웨어를 실행하는 것이다.
▪
이 테스트는 보안과 성능, 자원 손실, 시간 문제, 저수준 통합에서는 테스트할 수 없는 문제를 테스트한다.
•
테스트는 일반적으로 블랙박스와 화이트박스로 나뉜다.
◦
블랙박스는 테스터가 테스트하는 항목의 내부 동작을 볼 수 없는 테스트를 가리킨다.
◦
화이트박스는 테스터가 테스트하는 항목의 내부 동작을 볼 수 있는 테스트를 가리킨다.
1. 소프트웨어 품질에서 개발자 테스트의 역할
•
테스트는 소프트웨어 품질 프로그램에서 중요한 부분 중 하나면서 유일한 부분이기도 하다.
•
동시에 대부분의 개발자가 여러 이유로 쉽게 받아들이지 못하는 어려운 활동이기도 한데 이유는 다음과 같다.
◦
테스트의 목표가 다른 개발 활동의 목표와 상반되기 때문이다. 테스트의 목표는 오류를 찾는 것이다.
◦
테스트가 오류가 있음을 의미할 순 있지만 확실하게 없다고 증명할 수는 없다.
◦
테스트 자체는 소프트웨어 품질을 향상시키지 않는다.
◦
테스트 코드에는 오류를 발견할 것이라는 가정이 필요하다.
•
가장 중요한 질문 중 하나는 일반적인 프로젝트에서 테스트에 얼마나 많은 시간을 사용해야하는 가인데, 일반적으로 전체 시간의 50%라고 말한다.
◦
다만 이는 디버깅 시간을 포함한 시간이다.
•
두 번째 질문은 개발자 테스트의 결과로 무엇을 할 것인가다.
◦
당장은 개발 중인 제품의 품질을 평가하는 데 사용할 수 있다.
◦
외에도 소프트웨어 수정 작업에 대한 가이드 역할을 수행한다.
2. 개발자 테스트에 대한 바람직한 접근 방법
•
테스트에 체계적으로 접근하면 최소한의 노력으로 오류를 발견하는 능력을 극대화할 수 있다. 다음 내용을 확실하게 이해하자.
◦
각 요구사항을 테스트해 요구사항이 신뢰성 있게 구현되었는지 확인하라.
▪
테스트 케이스를 요구사항 단계나 구현 전에 계획하도록 하라.
▪
이때 요구사항에서 자주 빠뜨리는 사항에 대한 테스트를 고려하라.
•
보안 수준, 저장소, 설치 절차, 시스템 신뢰성 등
◦
각각 연관된 설계 사항이 구현되어있는지 보장하기 위해서 테스트하라.
◦
요구사항과 설계를 테스트하는 테스트 케이스에 상세 테스트 케이스를 추가하는 데 기초 테스트를 사용하라.
◦
현재 또는 이전 프로젝트에서 발견한 오류의 체크리스트를 사용하라.
테스트를 먼저할 것인가 나중에할 것인가?
•
일반적으로 테스트 케이스를 먼저 작성하는 것이 결함이 코드에 발생한 시간과 제거되는 시간의 차이를 최소화시켜준다.
•
다음은 테스트 케이스를 먼저 작성해야 하는 여러 이유 중 하나다.
◦
코드를 작성하기 전에 테스트 케이스를 작성하는 비용이 코드 구현 후 작성 비용과 크게 차이가 없다.
◦
결함을 미리 발견하고 더 쉽게 수정할 수 있다.
◦
테스트 케이스를 먼저 작성하면 요구사항과 설계에 대해서 적어도 좀 더 생각하게 된다.
◦
테스트 케이스를 먼저 작서앟면 요구사항의 문제를 먼저 확인할 수 있다. 요구사항이 잘못된 경우, 테스트 케이스를 작성하기 어렵기 때문이다.
개발자 테스트의 한계
•
개발자 테스트는 깨끗한 테스트가 되기 쉽다.
•
개발자 테스트는 테스트 커버리지를 낙관적으로 바라보는 경향이 있다.
•
개발자 테스트는 좀 더 정교한 테스트 커버리지를 건너뛰는 경향이 있다.
3. 여러 가지 교묘한 테스트 방법
•
테스트로 프로그램이 정확하다는 것을 보증할 수 없는 이유는 프로그램이 정상적으로 동작하는 것을 보장하기 위해서 모든 입력값의 가능한 모든 조합을 테스트해야하기 때문이다.
불완전한 테스트
•
현실적으로 말해 완벽한 테스트는 불가능하므로 가장 오류를 잘 발견할 것 같은 테스트를 작성하는 것이 좋은 테스트를 작성하는 기술이다.
•
테스트 계획을 수립할 때, 기존의 테스트로도 충분히 대체될 수 있는 테스트는 제거한다.
구조적인 기초 테스트
•
프로그램에 있는 각 명령문을 적어도 한 번은 테스트해야 한다는 것이다.
•
이때 코드 커버리지 지표를 사용한다. 모든 코드가 실행될 것임을 보장하기 위한 테스트를 작성하는 단계다.
데이터 흐름 테스트
•
데이터 사용이 적어도 제어 흐름만큼 다양한 오류를 유발할 가능성이 있다는 개념에 기반을 두고 있다.
•
데이터는 다음 상태 중 하나로 존재할 수 있다.
◦
정의: 데이터가 초기화되었지만 아직 사용되지 않았다.
◦
사용: 데이터가 메소드의 인자로서 계산되거나 다른 무언가를 위해 사용된다.
◦
삭제: 데이터가 정의되었지만 어떤 방식으로 정의가 해제되었다.
◦
들어감: 제어 흐름이 변수가 사용되기 바로 전에 메소드에 들어간다.
◦
빠져나옴: 제어 흐름이 변수가 사용되고 나서 바로 다음에 메소드를 떠난다.
데이터 상태의 조합
•
일반적인 데이터 상태의 조합 흐름은 변수가 정의되고 한 번 이상 사용된 다음, 때에 따라서 삭제되는 것이다.
•
따라서 우리는 이런 일반적인 흐름을 따르지 않는 다음 패턴들을 의심의 눈초리로 한 번 보는 것이 좋다.
◦
정의-정의
▪
값이 사용되기 전에 정의가 2번..? 실제로 잘못되지 않았더라도 오류를 일으킬 가능성이 있다.
◦
정의-빠져나옴
▪
변수가 지역 변수라면 정의한 다음 사용하지 않고 빠져나오는 것은 적절하지 않다. 메소드 매개변수나 전역 변수라면 상관없다.
◦
정의-삭제
▪
변수를 정의한 다음 삭제하는 것은 변수가 불필요하거나 변수를 사용하려했던 코드가 없다는 것을 의미한다.
◦
들어감-삭제
▪
지역 변수일때 문제가 생길 수 있다.
◦
들어감-사용
▪
변수는 사용되기 전에, 정의되어야 한다.
◦
삭제-삭제
▪
변수가 두 번 삭제되어서는 안 된다. 변수는 되살아나지 않는데, 변수가 되살아나 삭제했다면 이는 문제가 생겼음을 의미한다.
◦
삭제-사용
▪
삭제된 변수를 사용하는 것은 논리적인 오류다.
◦
사용-정의
▪
변수를 사용하거나서 정의하는 것은 이상하다.
•
모순된 순서를 검사하고 나서는 모든 가능한 정의-사용 경로를 조사하는 것이 가장 중요하다.
◦
모든 정의
▪
변수의 모든 정의를 테스트한다. 즉 변수가 값을 받는 모든 곳에서 테스트한다.
▪
모든 소스코드를 조사하려면 이를 수행해야 하므로 강력한 전략은 아니다.
◦
모든 정의-사용 조합 테스트
▪
변수를 정의하고 사용하는 모든 조합을 테스트한다.
등가 분할
•
좋은 테스트 케이스는 가능한 넓은 입력 데이터를 다룬다. 만약 서로 다른 두 테스트 케이스가 같은 오류를 찾아낸다면 그중 하나만 필요하다.
•
등가 분할은 이러한 개념을 형식화하여 유지보수해야할 테스트 케이스의 수를 줄이는데 도움을 준다.
오류 추측
•
좋은 개발자들이 형식적인 테스트 기법과 함께 덜 형식적인 경험적인 여러 기법을 사용해 오류를 찾아내는 데, 오류 추측은 그 중 하나다.
•
프로그램이 오류를 가지고 있을 것이라 짐작(아마 복잡한)되는 위치를 추측하여 테스트하는 것을 의미한다.
경계 분석
•
오류를 가장 잘 찾아낼 수 있는 영역은 경계 조건이다.
•
경계 분석의 개념은 경계 조건을 조사하는 테스트 케이스를 작성하는 것이다.
나쁜 데이터
•
전형적인 나쁜 데이터는 다음과 같다.
◦
너무 적은 데이터
◦
너무 많은 데이터
◦
유효하지 않은 데이터
▪
잘못된 크기의 데이터
좋은 데이터
•
정상적인 데이터가 오류를 포함할 수 있다는 사실을 잊지 말아야 한다.
◦
명목상 케이스, 일반적이고 예상된 값
◦
최소한의 정상적인 구성
◦
최대한의 정상적인 구성
◦
이전 데이터와의 호환
4. 전형적인 오류
•
대부분의 오류는 결점이 상당히 많은 메소드 몇 개에 집중되는 경향이 있다.
◦
오류의 80%는 클래스나 메소드의 20%에서 발견된다.
•
이러한 관계는 다음과 같은 이유로 발생한다.
◦
프로젝트 메소드의 20%가 개발 비용의 80%를 차지한다.
◦
결함이 매우 많은 메소드가 차지하는 비용의 정확한 비율에 상관없이 결함이 많은 메소드는 매우 비싸다.
◦
개발 비용이 높은 메소드가 암시하는 바는 분명하다.
◦
유지보수를 위해서 문제가 있는 메소드를 피한다는 것이 암시하는 바도 분명하다.
오류의 분류
•
오류의 분류를 연구마다 결과가 다르므로 연구들의 공통적인 다음 사실들을 알고 있는 것이 낫다.
•
대부분의 오류가 발생하는 범위는 제한되어 있다.
•
많은 오류가 구현 범위 밖에 있다.
◦
빈약한 지식, 요구사항의 변동과 모순, 의사소통과 협동의 실패에 원인이 있는 경우가 많았다.
•
대부분의 구현 오류는 프로그램의 잘못이다.
•
오타는 뜻밖에도 많이 발생하는 문제다.
•
설계를 잘못 이해하는 것은 프로그램의 오류에 대한 연구에서 많이 다뤄지는 주제다.
•
대부분의 오류는 수정하기 쉽다.
•
자신이 속한 조직의 오류에 대한 경험을 측정하는 것은 좋은 생각이다.
◦
문제가 어디에 있는지를 알기 위해서 내부의 개발 프로세스를 측정하는 것부터 시작하는 것이 좋다.
결점이 있는 구현으로부터 발생하는 오류 비율
•
오류를 분류한 연구들의 결과가 결정적이지 않았으니 오류의 원인도 결정적이지 않다. 하나 분명한 점은 구현이 언제나 많은 오류를 유발한다는 점이다.
•
다음은 저자의 개인적인 생각으로부터 기인한 깨달음이다.
◦
작은 프로젝트라면 구현 결함이 모든 오류의 상당 부분을 차지한다.
◦
구현 결함은 프로젝트의 크기에 상관없이 전체 결함 중 35%의 비중을 차지한다.
◦
구현 오류가 요구사항이나 설계 오류를 수정하는 것보단 싸지만 여전히 비싸다.
테스트 자체의 오류
•
테스트 케이스는 종종 테스트하는 코드보다 오류를 포함하고 있을 가능성이 더 높다.
◦
특히 개발자가 테스트 케이스를 작성하는 경우 더욱 그런데, 신중한 설계와 구현 프로세스를 거쳐 만들어지기 보다는 즉각적으로 만들어지기 때문이다.
◦
아마 테스트 케이스는 종종 한 번만 테스트하고 버리는 것을 만드는 정도의 노력만 들이기 때문이다.
•
테스트 케이스에 있는 오류의 수를 줄이기 위한 여러 방법이 있다.
◦
작업을 점검하라.
▪
코드를 개발하는 것처럼 테스트 케이스를 주의 깊게 개발하라. 테스트를 이중으로 검사하는 것도 포함된다.
◦
소프트웨어를 개발할 때, 테스트 케이스를 계획하라.
▪
테스트에 대한 효과적인 계획을 세우는 방법은 요구사항 단계와 같은 초기단계에 시작하는 것이다.
◦
테스트 케이스를 유지하라.
▪
테스트 케이스의 품질을 위해 시간을 투자하라. 회귀 테스트와 다음 버전을 개발하기 위해서 테스트 케이스를 유ㅣㅈ하라.
◦
단위 테스트를 테스트 프레임워크에 연결하라.
▪
단위 테스트에 대한 코드를 먼저 작성하고 테스트를 마칠 때마다 그것을 JUnit과 같은 시스템 전체 테스트 프레임워크에 통합하라.
▪
통합된 테스트 프레임워크를 가지고 있으면 테스트 케이스가 버려지는 것을 방지할 수 있다.
5. 테스트 지원 도구
개별 클래스를 테스트하는 비계 구축
•
소프트웨어의 비계는 코드를 쉽게 조사할 수 있도록하는 것이 유일한 목적이다.
•
비계의 한 종류는 테스트 더블이다. 목 또는 스텁이 존재한다.
•
자신이 개발하는 프로그램에 대한 비계를 제공해주는 다양한 테스트 프레임워크를 사용하면 됟나.
6. 테스트를 향상시키는 방법
•
테스트를 향상시키는 단계는 다른 프로세스들을 개선하는 단계와 유사하다.
테스트 계획 세우기
•
프로젝트의 시작부터 테스트에 대한 계획을 세우라. 테스트를 설계나 코드 작성과 같은 수준으로 중요하게 생각하자.
•
테스트 계획을 세우는 것은 반복적으로 수행 가능한 테스트 프로세스를 만드는 요소이기도 하다.
다시 테스트하기(회귀 테스트)
•
제품이 변경되었을때, 변경되기 전에 작성한 테스트를 통과하는지, 즉 소프트웨어가 이전 상태로 돌아(회귀)가지 않았는지 확인하기 위해서 설계되고 이를 회귀 테스트라고 부른다.
자동 테스트
•
회귀 테스트를 작동하게 하는 방법은 테스트를 자동화하는 것이다.
7. 테스트 기록을 보존하는 방법
•
테스트 프로세스를 반복할 수 있도록 만드는 것 외에도 변경 사항이 프로젝트를 향상시켰는지, 손상이 발생하진 않았는지 확실하게 알 수 있도록 프로젝트를 측정해야 한다.
개인 테스트 기록
•
프로젝트 수준 뿐만 아니라 개인별로 테스트 기록을 유지하는 것이 유용하다.
요점 정리
•
개발자에 의한 테스트는 완전한 테스트 전략을 위한 핵심적인 부분이다.
•
코드를 작성하기 전에 테스트 케이스를 작성하는 것과 코드를 작성한 후에 테스트 케이스를 작성하는 것은 시간과 노력 면에서 같지만 전자가 결함-발견-디버그-수정 주기를 줄여준다.
•
아무리 여러 테스트를 고려하더라도 테스트는 좋은 소프트웨어 품질의 프로그램을 만드는 도구 중 하나다. 요구사항과 설계에서 결함을 최소화하는 방법도 수행하라.
•
결정적으로 기초 테스트와 데이터 흐름 분석, 경계 분석, 나쁜 데이터, 좋은 데이터를 사용하여 많은 테스트 케이스를 새엉할 수 있다.
•
오류는 오류를 유발할 가능성이 있는 몇 개의 클래스와 메소드에 쏠려 있다. 오류를 유발할 가능성이 있는 코드를 찾아서 재설계하라.
•
테스트 데이터가 테스트하는 코드보다 오류를 더 자주 발생시키는 경향이 있다. 테스트 개발에 주의를 기울이자.
•
자동 테스트는 일반적으로 유용하며 회귀 테스트에서는 필수적이다.
•
규칙적으로 진행하고 측정하고 테스트를 향상시키기 위해서 배운 것을 사용하는 것이 테스트 프로세스를 향상시키는 최고의 방법이다.