////
Search
Duplicate
🩴

22. 개발자 테스트

테스트는 가장 보편적으로 사용되는 품질 개선 활동으로 다양한 기업, 학술 연구가 뒷받침하고 있는 방법이다.
다양한 방법으로 테스트되며 어떤 것은 개발자가 수행하고 어떤 것은 전문 테스터가 수행한다.
단위 테스트는 개발자나 팀이 작성한 클래스나 메소드, 작은 프로그램을 실해앟는 것으로 완성된 시스템과는 별개로 테스트한다.
컴포넌트 테스트는 여러 개발자나 팀이 참여하는 클래스, 패키지, 소형 프로그램, 다른 프로그램의 요소를 테스트하는 것으로 더 완전한 시스템과 별개로 테스트한다.
통합 테스트는 여러 개발자나 팀이 만든 클래스나 패키지, 컴포넌트, 서브 시스템을 두 개 이상 결합해 실행하는 것이다.
이와 같은 테스트는 전형적으로 테스트해야 하는 클래스가 두 개가 되는 순간 시작해서 전체 시스템 개발이 완료될 때까지 지속적으로 수행한다.
회귀 테스트는 이전에 통과했던 테스트 집합을 가지고 소프트웨어의 결함을 찾기 위해 반복하는 것이다.
시스템 테스트는 다른 소프트웨어와 하드웨어 시스템과의 통합을 포함한 최종 환경에서 소프트웨어를 실행하는 것이다.
이 테스트는 보안과 성능, 자원 손실, 시간 문제, 저수준 통합에서는 테스트할 수 없는 문제를 테스트한다.
테스트는 일반적으로 블랙박스와 화이트박스로 나뉜다.
블랙박스는 테스터가 테스트하는 항목의 내부 동작을 볼 수 없는 테스트를 가리킨다.
화이트박스는 테스터가 테스트하는 항목의 내부 동작을 볼 수 있는 테스트를 가리킨다.

1. 소프트웨어 품질에서 개발자 테스트의 역할

테스트는 소프트웨어 품질 프로그램에서 중요한 부분 중 하나면서 유일한 부분이기도 하다.
동시에 대부분의 개발자가 여러 이유로 쉽게 받아들이지 못하는 어려운 활동이기도 한데 이유는 다음과 같다.
테스트의 목표가 다른 개발 활동의 목표와 상반되기 때문이다. 테스트의 목표는 오류를 찾는 것이다.
테스트가 오류가 있음을 의미할 순 있지만 확실하게 없다고 증명할 수는 없다.
테스트 자체는 소프트웨어 품질을 향상시키지 않는다.
테스트 코드에는 오류를 발견할 것이라는 가정이 필요하다.
가장 중요한 질문 중 하나는 일반적인 프로젝트에서 테스트에 얼마나 많은 시간을 사용해야하는 가인데, 일반적으로 전체 시간의 50%라고 말한다.
다만 이는 디버깅 시간을 포함한 시간이다.
두 번째 질문은 개발자 테스트의 결과로 무엇을 할 것인가다.
당장은 개발 중인 제품의 품질을 평가하는 데 사용할 수 있다.
외에도 소프트웨어 수정 작업에 대한 가이드 역할을 수행한다.

2. 개발자 테스트에 대한 바람직한 접근 방법

테스트에 체계적으로 접근하면 최소한의 노력으로 오류를 발견하는 능력을 극대화할 수 있다. 다음 내용을 확실하게 이해하자.
각 요구사항을 테스트해 요구사항이 신뢰성 있게 구현되었는지 확인하라.
테스트 케이스를 요구사항 단계나 구현 전에 계획하도록 하라.
이때 요구사항에서 자주 빠뜨리는 사항에 대한 테스트를 고려하라.
보안 수준, 저장소, 설치 절차, 시스템 신뢰성 등
각각 연관된 설계 사항이 구현되어있는지 보장하기 위해서 테스트하라.
요구사항과 설계를 테스트하는 테스트 케이스에 상세 테스트 케이스를 추가하는 데 기초 테스트를 사용하라.
현재 또는 이전 프로젝트에서 발견한 오류의 체크리스트를 사용하라.

테스트를 먼저할 것인가 나중에할 것인가?

일반적으로 테스트 케이스를 먼저 작성하는 것이 결함이 코드에 발생한 시간과 제거되는 시간의 차이를 최소화시켜준다.
다음은 테스트 케이스를 먼저 작성해야 하는 여러 이유 중 하나다.
코드를 작성하기 전에 테스트 케이스를 작성하는 비용이 코드 구현 후 작성 비용과 크게 차이가 없다.
결함을 미리 발견하고 더 쉽게 수정할 수 있다.
테스트 케이스를 먼저 작성하면 요구사항과 설계에 대해서 적어도 좀 더 생각하게 된다.
테스트 케이스를 먼저 작서앟면 요구사항의 문제를 먼저 확인할 수 있다. 요구사항이 잘못된 경우, 테스트 케이스를 작성하기 어렵기 때문이다.

개발자 테스트의 한계

개발자 테스트는 깨끗한 테스트가 되기 쉽다.
개발자 테스트는 테스트 커버리지를 낙관적으로 바라보는 경향이 있다.
개발자 테스트는 좀 더 정교한 테스트 커버리지를 건너뛰는 경향이 있다.

3. 여러 가지 교묘한 테스트 방법

테스트로 프로그램이 정확하다는 것을 보증할 수 없는 이유는 프로그램이 정상적으로 동작하는 것을 보장하기 위해서 모든 입력값의 가능한 모든 조합을 테스트해야하기 때문이다.

불완전한 테스트

현실적으로 말해 완벽한 테스트는 불가능하므로 가장 오류를 잘 발견할 것 같은 테스트를 작성하는 것이 좋은 테스트를 작성하는 기술이다.
테스트 계획을 수립할 때, 기존의 테스트로도 충분히 대체될 수 있는 테스트는 제거한다.

구조적인 기초 테스트

프로그램에 있는 각 명령문을 적어도 한 번은 테스트해야 한다는 것이다.
이때 코드 커버리지 지표를 사용한다. 모든 코드가 실행될 것임을 보장하기 위한 테스트를 작성하는 단계다.

데이터 흐름 테스트

데이터 사용이 적어도 제어 흐름만큼 다양한 오류를 유발할 가능성이 있다는 개념에 기반을 두고 있다.
데이터는 다음 상태 중 하나로 존재할 수 있다.
정의: 데이터가 초기화되었지만 아직 사용되지 않았다.
사용: 데이터가 메소드의 인자로서 계산되거나 다른 무언가를 위해 사용된다.
삭제: 데이터가 정의되었지만 어떤 방식으로 정의가 해제되었다.
들어감: 제어 흐름이 변수가 사용되기 바로 전에 메소드에 들어간다.
빠져나옴: 제어 흐름이 변수가 사용되고 나서 바로 다음에 메소드를 떠난다.

데이터 상태의 조합

일반적인 데이터 상태의 조합 흐름은 변수가 정의되고 한 번 이상 사용된 다음, 때에 따라서 삭제되는 것이다.
따라서 우리는 이런 일반적인 흐름을 따르지 않는 다음 패턴들을 의심의 눈초리로 한 번 보는 것이 좋다.
정의-정의
값이 사용되기 전에 정의가 2번..? 실제로 잘못되지 않았더라도 오류를 일으킬 가능성이 있다.
정의-빠져나옴
변수가 지역 변수라면 정의한 다음 사용하지 않고 빠져나오는 것은 적절하지 않다. 메소드 매개변수나 전역 변수라면 상관없다.
정의-삭제
변수를 정의한 다음 삭제하는 것은 변수가 불필요하거나 변수를 사용하려했던 코드가 없다는 것을 의미한다.
들어감-삭제
지역 변수일때 문제가 생길 수 있다.
들어감-사용
변수는 사용되기 전에, 정의되어야 한다.
삭제-삭제
변수가 두 번 삭제되어서는 안 된다. 변수는 되살아나지 않는데, 변수가 되살아나 삭제했다면 이는 문제가 생겼음을 의미한다.
삭제-사용
삭제된 변수를 사용하는 것은 논리적인 오류다.
사용-정의
변수를 사용하거나서 정의하는 것은 이상하다.
모순된 순서를 검사하고 나서는 모든 가능한 정의-사용 경로를 조사하는 것이 가장 중요하다.
모든 정의
변수의 모든 정의를 테스트한다. 즉 변수가 값을 받는 모든 곳에서 테스트한다.
모든 소스코드를 조사하려면 이를 수행해야 하므로 강력한 전략은 아니다.
모든 정의-사용 조합 테스트
변수를 정의하고 사용하는 모든 조합을 테스트한다.

등가 분할

좋은 테스트 케이스는 가능한 넓은 입력 데이터를 다룬다. 만약 서로 다른 두 테스트 케이스가 같은 오류를 찾아낸다면 그중 하나만 필요하다.
등가 분할은 이러한 개념을 형식화하여 유지보수해야할 테스트 케이스의 수를 줄이는데 도움을 준다.

오류 추측

좋은 개발자들이 형식적인 테스트 기법과 함께 덜 형식적인 경험적인 여러 기법을 사용해 오류를 찾아내는 데, 오류 추측은 그 중 하나다.
프로그램이 오류를 가지고 있을 것이라 짐작(아마 복잡한)되는 위치를 추측하여 테스트하는 것을 의미한다.

경계 분석

오류를 가장 잘 찾아낼 수 있는 영역은 경계 조건이다.
경계 분석의 개념은 경계 조건을 조사하는 테스트 케이스를 작성하는 것이다.

나쁜 데이터

전형적인 나쁜 데이터는 다음과 같다.
너무 적은 데이터
너무 많은 데이터
유효하지 않은 데이터
잘못된 크기의 데이터

좋은 데이터

정상적인 데이터가 오류를 포함할 수 있다는 사실을 잊지 말아야 한다.
명목상 케이스, 일반적이고 예상된 값
최소한의 정상적인 구성
최대한의 정상적인 구성
이전 데이터와의 호환

4. 전형적인 오류

대부분의 오류는 결점이 상당히 많은 메소드 몇 개에 집중되는 경향이 있다.
오류의 80%는 클래스나 메소드의 20%에서 발견된다.
이러한 관계는 다음과 같은 이유로 발생한다.
프로젝트 메소드의 20%가 개발 비용의 80%를 차지한다.
결함이 매우 많은 메소드가 차지하는 비용의 정확한 비율에 상관없이 결함이 많은 메소드는 매우 비싸다.
개발 비용이 높은 메소드가 암시하는 바는 분명하다.
유지보수를 위해서 문제가 있는 메소드를 피한다는 것이 암시하는 바도 분명하다.

오류의 분류

오류의 분류를 연구마다 결과가 다르므로 연구들의 공통적인 다음 사실들을 알고 있는 것이 낫다.
대부분의 오류가 발생하는 범위는 제한되어 있다.
많은 오류가 구현 범위 밖에 있다.
빈약한 지식, 요구사항의 변동과 모순, 의사소통과 협동의 실패에 원인이 있는 경우가 많았다.
대부분의 구현 오류는 프로그램의 잘못이다.
오타는 뜻밖에도 많이 발생하는 문제다.
설계를 잘못 이해하는 것은 프로그램의 오류에 대한 연구에서 많이 다뤄지는 주제다.
대부분의 오류는 수정하기 쉽다.
자신이 속한 조직의 오류에 대한 경험을 측정하는 것은 좋은 생각이다.
문제가 어디에 있는지를 알기 위해서 내부의 개발 프로세스를 측정하는 것부터 시작하는 것이 좋다.

결점이 있는 구현으로부터 발생하는 오류 비율

오류를 분류한 연구들의 결과가 결정적이지 않았으니 오류의 원인도 결정적이지 않다. 하나 분명한 점은 구현이 언제나 많은 오류를 유발한다는 점이다.
다음은 저자의 개인적인 생각으로부터 기인한 깨달음이다.
작은 프로젝트라면 구현 결함이 모든 오류의 상당 부분을 차지한다.
구현 결함은 프로젝트의 크기에 상관없이 전체 결함 중 35%의 비중을 차지한다.
구현 오류가 요구사항이나 설계 오류를 수정하는 것보단 싸지만 여전히 비싸다.

테스트 자체의 오류

테스트 케이스는 종종 테스트하는 코드보다 오류를 포함하고 있을 가능성이 더 높다.
특히 개발자가 테스트 케이스를 작성하는 경우 더욱 그런데, 신중한 설계와 구현 프로세스를 거쳐 만들어지기 보다는 즉각적으로 만들어지기 때문이다.
아마 테스트 케이스는 종종 한 번만 테스트하고 버리는 것을 만드는 정도의 노력만 들이기 때문이다.
테스트 케이스에 있는 오류의 수를 줄이기 위한 여러 방법이 있다.
작업을 점검하라.
코드를 개발하는 것처럼 테스트 케이스를 주의 깊게 개발하라. 테스트를 이중으로 검사하는 것도 포함된다.
소프트웨어를 개발할 때, 테스트 케이스를 계획하라.
테스트에 대한 효과적인 계획을 세우는 방법은 요구사항 단계와 같은 초기단계에 시작하는 것이다.
테스트 케이스를 유지하라.
테스트 케이스의 품질을 위해 시간을 투자하라. 회귀 테스트와 다음 버전을 개발하기 위해서 테스트 케이스를 유ㅣㅈ하라.
단위 테스트를 테스트 프레임워크에 연결하라.
단위 테스트에 대한 코드를 먼저 작성하고 테스트를 마칠 때마다 그것을 JUnit과 같은 시스템 전체 테스트 프레임워크에 통합하라.
통합된 테스트 프레임워크를 가지고 있으면 테스트 케이스가 버려지는 것을 방지할 수 있다.

5. 테스트 지원 도구

개별 클래스를 테스트하는 비계 구축

소프트웨어의 비계는 코드를 쉽게 조사할 수 있도록하는 것이 유일한 목적이다.
비계의 한 종류는 테스트 더블이다. 목 또는 스텁이 존재한다.
자신이 개발하는 프로그램에 대한 비계를 제공해주는 다양한 테스트 프레임워크를 사용하면 됟나.

6. 테스트를 향상시키는 방법

테스트를 향상시키는 단계는 다른 프로세스들을 개선하는 단계와 유사하다.

테스트 계획 세우기

프로젝트의 시작부터 테스트에 대한 계획을 세우라. 테스트를 설계나 코드 작성과 같은 수준으로 중요하게 생각하자.
테스트 계획을 세우는 것은 반복적으로 수행 가능한 테스트 프로세스를 만드는 요소이기도 하다.

다시 테스트하기(회귀 테스트)

제품이 변경되었을때, 변경되기 전에 작성한 테스트를 통과하는지, 즉 소프트웨어가 이전 상태로 돌아(회귀)가지 않았는지 확인하기 위해서 설계되고 이를 회귀 테스트라고 부른다.

자동 테스트

회귀 테스트를 작동하게 하는 방법은 테스트를 자동화하는 것이다.

7. 테스트 기록을 보존하는 방법

테스트 프로세스를 반복할 수 있도록 만드는 것 외에도 변경 사항이 프로젝트를 향상시켰는지, 손상이 발생하진 않았는지 확실하게 알 수 있도록 프로젝트를 측정해야 한다.

개인 테스트 기록

프로젝트 수준 뿐만 아니라 개인별로 테스트 기록을 유지하는 것이 유용하다.

요점 정리

개발자에 의한 테스트는 완전한 테스트 전략을 위한 핵심적인 부분이다.
코드를 작성하기 전에 테스트 케이스를 작성하는 것과 코드를 작성한 후에 테스트 케이스를 작성하는 것은 시간과 노력 면에서 같지만 전자가 결함-발견-디버그-수정 주기를 줄여준다.
아무리 여러 테스트를 고려하더라도 테스트는 좋은 소프트웨어 품질의 프로그램을 만드는 도구 중 하나다. 요구사항과 설계에서 결함을 최소화하는 방법도 수행하라.
결정적으로 기초 테스트와 데이터 흐름 분석, 경계 분석, 나쁜 데이터, 좋은 데이터를 사용하여 많은 테스트 케이스를 새엉할 수 있다.
오류는 오류를 유발할 가능성이 있는 몇 개의 클래스와 메소드에 쏠려 있다. 오류를 유발할 가능성이 있는 코드를 찾아서 재설계하라.
테스트 데이터가 테스트하는 코드보다 오류를 더 자주 발생시키는 경향이 있다. 테스트 개발에 주의를 기울이자.
자동 테스트는 일반적으로 유용하며 회귀 테스트에서는 필수적이다.
규칙적으로 진행하고 측정하고 테스트를 향상시키기 위해서 배운 것을 사용하는 것이 테스트 프로세스를 향상시키는 최고의 방법이다.