유닛 테스트
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
유닛 테스트는 소프트웨어 개발 과정에서 개별적인 코드 조각, 즉 '유닛'의 정확성을 검증하는 방법이다. 1950년대부터 소프트웨어 공학의 중요한 부분으로 자리 잡아왔으며, 코드의 문제점을 조기에 발견하고, 변경의 용이성을 높이며, 통합을 간소화하고, 시스템의 동작을 문서화하는 데 기여한다. 유닛 테스트는 테스트 주도 개발(TDD)과 같은 방법론에서 핵심적인 역할을 하며, 자동화된 테스트 프레임워크를 통해 효율성을 높일 수 있다. 하지만 모든 오류를 잡아낼 수 없으며, 현실적인 테스트 환경 설정의 어려움, 개발 과정에서의 엄격한 규율 필요성 등의 한계도 존재한다.
더 읽어볼만한 페이지
- 유닛 테스트 - 메소드 스텁
메소드 스텁은 소프트웨어 개발에서 상위 모듈 검사 시 미완성된 하위 모듈을 대신하는 가상 모듈로, 모듈 간 의존성 해결, 개발 속도 향상, 독립적인 테스트 환경 구축을 목적으로 사용된다. - 유닛 테스트 - 모의 객체
모의 객체는 소프트웨어 테스트에서 실제 객체의 동작을 흉내 내는 가짜 객체로, 테스트를 어렵게 만드는 실제 객체의 문제점을 해결하고 빠르고 안정적인 테스트를 가능하게 하지만, 구현에 지나치게 의존할 수 있다는 한계가 있다. - 익스트림 프로그래밍 - 워드 커닝햄
워드 커닝햄은 미국의 컴퓨터 프로그래머로, 최초의 위키 사이트 WikiWikiWeb을 만들고 기술 부채 개념을 창안했으며, 소프트웨어 개발 방법론 발전에 기여했다. - 익스트림 프로그래밍 - JUnit
JUnit은 자바 환경에서 단위 테스트를 위한 프레임워크로, 반복적인 테스트 실행을 통해 버그 수정에 용이하며, 어노테이션 기반의 간편한 테스트 코드 작성과 IDE 통합을 지원하여 개발 효율성을 높인다. - 소프트웨어 테스트 - 보안 취약점
보안 취약점은 시스템의 설계, 구현, 운영, 관리상 결함이나 약점으로, 위협에 의해 악용되어 시스템 보안 정책을 위반할 수 있는 요소이며, ISO 27005, IETF RFC 4949, NIST SP 800-30, ENISA 등 다양한 기관에서 정의하고 있다. - 소프트웨어 테스트 - A/B 테스트
A/B 테스트는 두 가지 이상의 대안을 비교하여 더 나은 성과를 판단하는 방법으로, 웹사이트, 애플리케이션 등 다양한 분야에서 사용자 인터페이스 등을 테스트하며 통계적 가설 검정을 기반으로 한다.
유닛 테스트 | |
---|---|
개요 | |
목적 | 개별 소프트웨어 구성 요소의 정확성 검증 |
대상 | 소스 코드의 개별 유닛 (함수, 모듈, 객체 등) |
방법 | 독립적으로 격리된 코드 동작 검증 |
다른 이름 | 모듈 테스트 컴포넌트 테스트 |
상세 정보 | |
목표 | 소프트웨어 설계 및 구현의 초기 단계에서 결함 발견 및 수정 |
장점 | 코드 품질 향상 개발 속도 향상 유지보수 용이성 증대 |
자동화 | 자동화된 테스트 케이스를 통해 반복적인 테스트 수행 |
테스트 케이스 | 특정 입력에 대한 예상되는 출력을 정의 |
모의 객체 (Mock object) | 실제 객체의 동작을 흉내 내는 가짜 객체 |
역할 | 테스트 대상 유닛의 의존성을 격리하고, 예상되는 상호 작용을 검증 |
관련 용어 | 테스트 더블 (Test double) |
테스트 주도 개발 (TDD) | 테스트 케이스를 먼저 작성하고, 그 후에 테스트를 통과하는 코드를 작성하는 개발 방법론 |
개발자 및 테스터 | 개발자와 테스터가 협력하여 유닛 테스트를 수행 |
장점 상세 | 개발 초기 단계에서 버그 식별 및 수정 가능 코드 변경 시 잠재적인 문제점 조기 발견 가능 전체적인 코드 품질 향상 및 유지보수 비용 절감 |
추가 정보 | |
테스트 범위 | 개별 유닛의 모든 가능한 실행 경로를 테스트하는 것이 이상적이지만, 실제로는 제한된 범위 내에서 수행 |
테스트 환경 | 실제 환경과 유사한 환경에서 테스트를 수행하여 신뢰성 확보 |
지속적인 통합 (CI) | 코드 변경 사항이 있을 때마다 자동으로 유닛 테스트를 수행하여 코드 품질 유지 |
2. 역사
유닛 테스트는 소프트웨어 공학 초창기부터 존재해왔다. 초기에는 주로 수동으로 테스트를 진행했지만, 시간이 지나면서 자동화된 테스트와 테스트 프레임워크가 등장하며 발전했다.
1989년, 켄트 백은 SUnit의 초기 형태인 "Simple Smalltalk Testing: With Patterns"를 통해 Smalltalk용 테스트 프레임워크를 선보였다.[8] 1997년에는 켄트 백과 에리히 감마가 협력하여 자바 개발자들 사이에서 널리 사용되는 유닛 테스트 프레임워크 JUnit을 개발하고 배포했다.[8] 구글(Google)은 2005년에서 2006년 사이에 자동화된 테스트를 도입했다.[9]
2. 1. 초기 역사
유닛 테스트는 대규모 소프트웨어 시스템의 더 작은 부분을 개별적으로 테스트하는 원칙으로, 소프트웨어 공학 초창기부터 존재해왔다. 1956년 6월, H.D. 베닝턴은 미국 해군이 주최한 디지털 컴퓨터를 위한 첨단 프로그래밍 방법 심포지엄에서 SAGE 프로젝트를 발표하며, 코딩 단계 이후 구성 요소 서브 프로그램을 사양에 맞춰 검증하는 "매개변수 테스트"와, 이어서 구성 요소들을 함께 묶는 "어셈블리 테스트"를 수행하는 사양 기반 방식을 제시했다.[2][3]1964년 머큐리 프로젝트의 소프트웨어에서도 비슷한 방식이 설명되었는데, 서로 다른 프로그램에서 개발된 개별 유닛은 통합 전에 "유닛 테스트"를 거쳤다.[4] 1969년에는 유닛 테스트, 컴포넌트 테스트, 통합 테스트 등 보다 구조화된 테스트 방법론이 등장하여, 개별적으로 작성된 부분과 이들을 더 큰 블록으로 점진적으로 조립하는 과정을 검증하였다.[5] 1960년대 말에 채택된 MIL-STD-483[6] 및 MIL-STD-490과 같은 일부 공개 표준은 대규모 프로젝트에서 유닛 테스트가 널리 수용되는 데 기여했다.
당시 유닛 테스트는 코딩된 테스트나 캡처 및 재생 테스트 도구를 사용하여 대화형[3] 또는 자동화된[7] 방식으로 이루어졌다.
2. 2. 발전 과정
유닛 테스트는 대규모 소프트웨어 시스템의 더 작은 부분을 개별적으로 테스트하는 원칙으로, 소프트웨어 공학 초기부터 존재해 왔다. 1956년 6월, H.D. 베닝턴은 미국 해군 주최 디지털 컴퓨터를 위한 첨단 프로그래밍 방법 심포지엄에서 SAGE 프로젝트를 발표하며, 코딩 단계 이후 구성 요소 서브 프로그램을 사양에 맞춰 검증하는 "매개변수 테스트"와, 이어서 구성 요소들을 함께 묶는 "어셈블리 테스트"를 수행하는 사양 기반 방식을 제시했다.[2][3]1964년 머큐리 프로젝트의 소프트웨어에서도 비슷한 방식이 설명되었는데, 서로 다른 프로그램에서 개발된 개별 유닛은 통합 전에 "유닛 테스트"를 거쳤다.[4] 1969년에는 유닛 테스트, 컴포넌트 테스트, 통합 테스트 등 보다 구조화된 테스트 방법론이 등장하여, 개별적으로 작성된 부분과 이들을 더 큰 블록으로 점진적으로 조립하는 과정을 검증했다.[5] 1960년대 말에 채택된 MIL-STD-483[6] 및 MIL-STD-490과 같은 일부 공개 표준은 대규모 프로젝트에서 유닛 테스트가 널리 수용되는 데 기여했다.
당시 유닛 테스트는 코딩된 테스트나 캡처 및 재생 테스트 도구를 사용하여 대화형[3] 또는 자동화된[7] 방식으로 이루어졌다. 1989년, 켄트 백은 "[https://web.archive.org/web/20150315073817/http://www.xprogramming.com/testfram.htm Simple Smalltalk Testing: With Patterns]"에서 Smalltalk를 위한 테스트 프레임워크(나중에 SUnit이라고 불림)를 설명했다. 1997년, 켄트 백과 에리히 감마는 JUnit을 개발하여 배포했는데, 이 유닛 테스트 프레임워크는 자바 개발자들 사이에서 인기를 얻었다.[8] 구글(Google)은 2005~2006년경 자동화된 테스트를 도입했다.[9]
2. 3. 자동화 및 프레임워크 등장
1989년, 켄트 벡은 SUnit의 초기 형태인 "Simple Smalltalk Testing: With Patterns"를 통해 Smalltalk용 테스트 프레임워크를 선보였다.[8] 1997년에는 켄트 벡과 에리히 감마가 협력하여 자바 개발자들 사이에서 널리 사용되는 유닛 테스트 프레임워크 JUnit을 개발하고 배포했다.[8] 구글은 2005년에서 2006년 사이에 자동화된 테스트를 도입했다.[9]3. 정의
유닛 테스트는 프로그램의 각 부분을 격리하여 개별 구성 요소가 올바르게 작동하는지 확인하는 것이다.[32] 유닛 테스트는 코드의 일부가 충족해야 하는 엄격하고 명시된 계약을 규정한다.
테스트 케이스는 특정 테스트를 실행하기 위한 입력, 실행 조건, 예상 결과 및 기타 관련 정보가 명시된 문서이다.
3. 1. 단위 (Unit)
유닛은 테스트 대상 시스템(SUT)이 나타내는 단일 행위로 정의되며, 일반적으로 요구 사항에 해당한다. 이는 함수나 모듈(절차적 프로그래밍) 또는 메서드나 클래스(객체 지향 프로그래밍)를 의미할 수 있지만, 함수/메서드, 모듈 또는 클래스가 항상 유닛에 해당하는 것은 아니다. 시스템 요구 사항 관점에서 시스템의 경계만 관련이 있으며, 따라서 외부에서 보이는 시스템 행위에 대한 진입점만이 유닛을 정의한다.[10]3. 2. 실행 (Execution)
유닛 테스트는 수동으로 또는 자동화된 테스트 실행을 통해 수행할 수 있다. 자동화된 테스트는 테스트를 자주 실행하고, 인건비 없이 테스트를 실행하며, 일관되고 반복 가능한 테스트를 수행하는 것과 같은 이점을 포함한다.테스트는 종종 테스트 중인 코드를 작성하고 수정하는 프로그래머가 수행한다. 유닛 테스트는 코드를 작성하는 과정의 일부로 간주될 수 있다.
단위 테스트는 일반적으로 자동화되어 있지만, 수동으로 실행되는 경우도 있다. IEEE는 어느 쪽이 다른 쪽보다 낫다고 여기지 않는다[41]。수동 단위 테스트에서는 단계별 절차서를 이용할 수 있다. 그러나 단위 테스트를 하는 목적은 유닛을 분리하고, 그 유닛의 정확성을 검증하는 것이다. 자동화는 이를 달성하기 위한 효율성이 높고, 이 문서에 기재된 많은 이점을 갖는다. 반대로, 신중하게 계획되지 않은 경우, 부주의한 수동 단위 테스트 케이스는 많은 소프트웨어 컴포넌트를 포함하는 통합 테스트 케이스로 실행되어, 그 결과 전부가 아니더라도, 대부분, 본래 단위 테스트에서 달성해야 할 목표에 도달하지 못할 가능성이 있다.
자동화된 접근 방식을 사용하면서도 독립적인 효과를 완전히 실현하기 위해, 테스트 대상 유닛이나 코드 본체는, 그 자연 환경 밖의 프레임워크 내에서 실행된다. 다시 말해, 원래 작성된 제품 또는 호출 측의 컨텍스트 밖에서 실행된다. 그러한 독립된 형태의 테스트에서는, 테스트 대상 코드와 다른 유닛 또는 제품 내의 데이터 공간 사이의 불필요한 의존 관계가 드러난다. 의존 관계는, 그 후, 제거할 수 있다.
자동화 프레임워크를 사용하면서, 개발자는 유닛의 정확성을 검증하기 위해, 기준을 코드 내에 반영한다. 테스트 케이스 실행 시에는, 프레임워크의 로그 중에, 기준에 미치지 못하는 테스트가 기록된다. 많은 프레임워크는 자동으로 실패한 테스트 케이스에 플래그를 붙여, 일괄 보고한다. 장애의 심각도에 따라, 프레임워크는, 후속 테스트를 중지하는 경우가 있다.
결과적으로, 전통적으로 단위 테스트는 프로그래머에게, 분리되고, 응집된 코드 본체를 작성하도록 동기를 부여한다. 이것을 항상 수행하면, 소프트웨어 개발에서 건전한 습관이 길러진다. 디자인 패턴, 단위 테스트, 리팩토링을 조합하면, 최선의 해결책이 나오게 된다.
3. 3. 테스트 기준 (Testing Criteria)
개발 과정에서 프로그래머는 유닛의 정확성을 검증하기 위해 테스트 기준, 즉 양호하다고 알려진 결과를 코드화할 수 있다.테스트 실행 중에 프레임워크는 기준에 부합하지 않는 테스트를 로그하고 요약 보고서에 보고한다.
이를 위해 가장 일반적으로 사용되는 접근 방식은 테스트 - 함수 - 예상 값이다.
4. 장점
유닛 테스트는 설계를 충족하고 의도한 대로 작동하는지 확인하기 위한 것이다.[11] 가장 작은 테스트 가능한 유닛에 대한 테스트를 먼저 작성한 다음, 해당 유닛 간의 복합적인 동작에 대한 테스트를 작성함으로써 복잡한 애플리케이션에 대한 포괄적인 테스트를 구축할 수 있다.[11]
유닛 테스트는 다음과 같은 장점들을 가진다.
- 문제점 조기 발견: 개발 주기 초기에 프로그래머의 구현상 버그, 유닛 명세의 결함 또는 누락된 부분을 찾아낸다.
- 변경 용이성: 프로그래머는 리팩토링 이후에도 유닛 테스트를 통해 모듈이 올바르게 작동하는지 확인할 수 있다. (\[\[회귀 테스트]])
- 통합 간소화: 상향식 테스트 방식에서 유닛 테스트는 유닛 자체의 불확실성을 줄여 통합 테스트를 더 쉽게 만든다.
- 시스템 동작 문서화: 유닛 테스트는 코드의 기능을 이해하고 사용하는 데 도움이 되는 일종의 문서 역할을 한다.
- 설계 개선: 테스트 주도 개발에서 유닛 테스트는 설계 요소로 활용되어 구현이 설계에 부합하는지 확인하는 역할을 한다.
유닛 테스트는 코드의 일부가 충족해야 하는 엄격하고 명시된 계약을 규정한다.[32] 그 결과, 코드 품질을 높이고, 유지보수를 쉽게 하며, 시스템의 안정성을 향상시킨다.
4. 1. 문제점 조기 발견
유닛 테스트의 목적은 프로그램의 각 부분을 고립시켜서 각각의 부분이 정확하게 동작하는지 확인하는 것이다. 즉, 프로그램을 작은 단위로 쪼개서 각 단위가 정확하게 동작하는지 검사하고, 이를 통해 문제 발생 시 정확하게 어느 부분이 잘못되었는지를 재빨리 확인할 수 있게 해준다. 따라서 프로그램의 안정성이 높아진다. 유닛 테스트는 개발 시간을 증가시키는 것처럼 보이지만, 개발 기간 중 대부분을 차지하는 디버깅 시간을 단축시킴으로써 여유로운 프로그래밍을 가능하게 한다.[1]개발 주기 초기에 유닛 테스트는 문제를 찾아낸다. 여기에는 프로그래머의 구현상 버그와 유닛에 대한 명세의 결함 또는 누락된 부분이 모두 포함된다. 철저한 테스트 집합을 작성하는 과정은 작성자가 입력, 출력 및 오류 조건을 생각하게 하여 유닛의 원하는 동작을 보다 명확하게 정의하도록 한다.[12][13][14]
코딩이 시작되기 전이나 코드가 처음 작성되었을 때 버그를 발견하는 비용은 나중에 해당 버그를 감지, 식별 및 수정하는 비용보다 훨씬 저렴하다. 릴리스된 코드의 버그는 소프트웨어 최종 사용자에게도 비용이 많이 드는 문제를 야기할 수 있다. 코드가 제대로 작성되지 않으면 유닛 테스트를 수행하는 것이 불가능하거나 어려울 수 있으므로, 유닛 테스트는 개발자가 함수와 객체를 더 나은 방식으로 구조화하도록 강제할 수 있다.
익스트림 프로그래밍, 스크럼 모두에서 많이 사용되는 테스트 주도 개발 (TDD)에서는 코드 자체를 작성하기 전에 단위 테스트가 생성된다. 테스트에 통과하면 해당 코드가 완성된 것으로 간주된다. 대규모 코드 베이스를 개발하는 경우, 코드를 변경했을 때 또는 빌드 자동화 프로세스 과정에서 동일한 단위 테스트가 해당 기능에 대해 빈번하게 실행된다. 단위 테스트가 실패한 경우에는 변경된 코드 또는 테스트 자체의 버그로 간주된다. 단위 테스트는 장애나 장애의 위치를 쉽게 추적할 수 있다. 단위 테스트는 테스터나 클라이언트에게 코드를 넘기기 전에 개발 팀에게 문제를 경고하므로, 개발 프로세스의 초기 단계에 수행된다.
4. 2. 변경 용이성
프로그래머는 유닛 테스트를 믿고 언제든지 리팩토링을 할 수 있다. 리팩토링 후에도 유닛 테스트를 통해 해당 모듈이 의도대로 작동하는지 확인할 수 있다. 이를 회귀 테스트(Regression testing영어)라 한다.[15] 코드를 어떻게 수정하더라도 문제점을 빠르게 파악할 수 있고, 수정된 코드가 정확하게 동작하는지 쉽게 알 수 있으므로 프로그래머는 더욱 의욕적으로 코드를 변경할 수 있다.지속적인 유닛 테스트 환경에서는 변경 사항이 있을 때마다 유닛 테스트가 실행 파일과 코드의 의도된 용도를 정확하게 반영한다. 확립된 개발 업무 및 유닛 테스트의 커버리지에 따라, 최신 정보를 게재한 정확도를 유지할 수 있다.
단위 테스트는 소프트웨어 개발에서 더 빈번한 릴리스를 가능하게 한다. 개발자는 개별 구성 요소를 격리하여 테스트함으로써 문제를 신속하게 식별하고 해결할 수 있으며, 이는 더 빠른 반복 및 릴리스 주기로 이어진다. 단위 테스트는 프로그래머가 나중에 코드를 리팩토링하거나 시스템 라이브러리를 업그레이드할 때, 해당 모듈이 여전히 올바르게 작동하는지 확인(회귀 테스트)할 수 있게 해준다. 모든 함수 또는 메서드에 대한 테스트 케이스를 작성하여 변경으로 인해 오류가 발생할 때 신속하게 식별할 수 있도록 하는 것이다.
4. 3. 통합 간소화
유닛 테스트는 유닛 자체의 불확실성을 줄여주므로 상향식 테스트 방식에서 유용하다. 프로그램의 일부분을 먼저 테스트한 다음 해당 부분들의 합을 테스트함으로써, 통합 테스트가 훨씬 쉬워진다. 정교하게 계층화된 단위 테스트가 곧 통합 테스트는 아니다. 주변 유닛과의 통합은 단위 테스트가 아닌 통합 테스트에 포함되어야 한다.4. 4. 시스템 동작 문서화
일부 프로그래머들은 유닛 테스트가 코드의 기능을 이해하고 사용하는 데 도움이 되는 일종의 문서 역할을 한다고 주장한다. 유닛의 기능과 사용법을 배우려는 개발자는 유닛 테스트를 검토하여 이를 파악할 수 있다.[11]유닛 테스트 케이스는 유닛의 성공에 필수적인 특성을 구현할 수 있다. 이러한 특성은 유닛의 적절하거나 부적절한 사용, 그리고 유닛이 포착해야 하는 부정적인 동작을 나타낼 수 있다. 많은 소프트웨어 개발 환경에서는 개발 중인 제품을 문서화하기 위해 코드에만 의존하지 않지만, 유닛 테스트는 이러한 중요한 특성을 문서화한다.[1]
유닛 테스트는 시스템의 살아있는 기술 문서를 제공한다. 개발자는 유닛 테스트를 통해 유닛의 기능과 사용 방법을 이해하고, 유닛의 API의 기본을 파악할 수 있다. 유닛 테스트는 코드의 일부가 충족해야 하는 엄격하고 명시된 계약을 규정한다.[32]
하지만 일반적인 수작업 문서는 프로그램 구현과 동떨어져 구식이 되기 쉽다. (예: 설계 변경, 기능 추가, 문서 업데이트 습관 부족 등)
4. 5. 설계 개선
테스트 주도 개발(TDD)에서 유닛 테스트는 실제 코드를 작성하는 동안 작성된다.[32] 개발자는 작동하는 코드로 시작하여 필요한 동작에 대한 테스트 코드를 추가한다. 그런 다음 테스트를 통과시키기에 충분한 코드를 추가하고, 코드를 리팩토링(테스트 코드 포함)하여 적절하게 만든 후, 다른 테스트를 추가하는 방식으로 반복한다.테스트 주도형 접근 방식으로 소프트웨어 개발을 수행하는 경우, 유닛 테스트가 정식 설계의 대안이 된다. 각 단위 테스트는 클래스, 메서드, 관찰 가능한 동작을 지정하는 설계 요소로 볼 수 있다. 다음은 Java 예시이다.
먼저 `Adder`라는 인터페이스가 있고, 인수가 없는 생성자를 가진 구현 클래스 `AdderImpl`이 있어야 한다. `Adder` 인터페이스는 두 개의 인수를 받아 정수를 반환하는 `add`라는 메서드를 가진다. 또한, 경곗값으로 대표적인 값이나 작은 범위의 값에 대해 어설션을 통해 이 메서드의 동작을 규정한다.
이 경우 단위 테스트가 먼저 작성되므로, 요구되는 해결책의 형태와 거동을 나타내는 설계 문서의 역할을 한다. 세부 구현은 프로그래머에게 맡겨진다. "가장 간단하게 작동하는 것" 관례에 따르면, 테스트를 통과할 수 있는 가장 간단한 해결책이 된다.
단위 테스트를 설계 수단으로 사용하는 데에는 중요한 이점이 있다. 설계 문서(단위 테스트 자체)를 통해 구현이 설계에 부합하는지 확인할 수 있다. 단위 테스트 설계 방법에서는 개발자가 설계대로 해결책을 구현하지 않으면 테스트는 절대 통과하지 못한다.
UML 다이어그램은 오늘날 무료 도구를 통해 대부분의 현대적인 프로그래밍 언어에 대해 쉽고 자동으로 추출할 수 있게 되었다(보통 IDE의 확장 기능으로 이용 가능).
5. 한계 및 단점
테스팅은 프로그램의 모든 오류를 잡아내지 못하며, 유닛 테스트도 마찬가지이다. 유닛 테스트는 정의상 유닛 자체의 기능만 테스트하기 때문에 통합 오류나 더 광범위한 시스템 수준의 오류는 잡아내지 못한다. 따라서 다른 소프트웨어 테스팅 활동과 함께 수행해야 한다.[16]
정교한 유닛 테스트 계층 구조는 통합 테스팅과 같지 않다. 주변 유닛과의 통합은 통합 테스트에 포함되어야 하지만 유닛 테스트에는 포함되지 않아야 한다. 통합 테스팅은 일반적으로 여전히 인간의 수동 테스팅에 크게 의존한다. 고수준 또는 전역 범위 테스트는 자동화하기 어려울 수 있으므로 수동 테스팅이 더 빠르고 저렴하게 보일 수 있다.
소프트웨어 테스팅은 조합 문제이므로, 작성된 코드 라인에 대해 더 많은 테스트 코드가 필요할 수 있다. 이는 시간이 걸리는 작업이며, 비결정적이거나 여러 스레드가 관련된 문제처럼 테스트하기 어려운 문제도 있다. 또한 유닛 테스트 코드는 테스트하는 코드만큼 버그가 있을 가능성이 있다. 프레드 브룩스는 ''신화적-인간-월''에서 "크로노미터 두 개를 가지고 항해하지 마라. 하나 또는 셋을 가져가라"라고 인용했다.[16]
현실적이고 유용한 유닛 테스트를 설정하는 것은 어려운 과제이다. 테스트 중인 애플리케이션의 일부가 전체 시스템의 일부처럼 동작하도록 관련된 초기 조건을 생성해야 하기 때문이다. 이러한 초기 조건이 올바르게 설정되지 않으면, 테스트는 현실적인 맥락에서 코드를 실행하지 못하게 되며, 이는 유닛 테스트 결과의 가치와 정확성을 감소시킨다.[37]
임베디드 시스템 소프트웨어의 유닛 테스트에는 다른 환경에는 없는 과제가 존재한다. 소프트웨어가 최종적으로 실행되는 것과는 다른 플랫폼 상에서 개발되기 때문에, 데스크톱 프로그램에서 테스트가 가능하더라도, 실제 배포 환경에서 쉽게 테스트 프로그램을 실행할 수 없다.[39]
5. 1. 모든 오류 검출 불가
테스팅은 프로그램의 모든 오류를 잡아내지 못한다. 가장 간단한 프로그램을 제외하고는 모든 실행 경로를 평가할 수 없기 때문이다. 이 문제는 정지 문제의 상위 집합이며, 결정 불가능하다. 유닛 테스트도 마찬가지이다. 또한 유닛 테스트는 정의상 유닛 자체의 기능만 테스트한다. 따라서 여러 유닛에서 수행되는 기능이나 성능과 같은 비기능적 테스트 영역, 통합 오류, 더 광범위한 시스템 수준의 오류는 잡아내지 못한다. 유닛 테스팅은 다른 소프트웨어 테스팅 활동과 함께 수행해야 한다. 유닛 테스팅은 특정 오류의 존재 유무만 보여줄 수 있으며, 오류가 전혀 없음을 증명할 수는 없다.[16]소프트웨어 테스팅은 조합 문제이다. 예를 들어, 모든 부울 결정 문은 최소 두 개의 테스트, 즉 "참" 결과와 "거짓" 결과가 있는 테스트가 필요하다. 결과적으로 프로그래머는 작성된 모든 코드 라인에 대해 종종 3~5줄의 테스트 코드가 필요하다.[36] 이는 시간이 걸리며, 비결정적이거나 여러 스레드가 관련된 문제처럼 테스트하기 어려운 문제도 있다. 또한 유닛 테스트 코드는 테스트하는 코드만큼 버그가 있을 가능성이 높다. 프레드 브룩스는 ''신화적-인간-월''에서 "크로노미터 두 개를 가지고 항해하지 마라. 하나 또는 셋을 가져가라"라고 인용했다.[16]
5. 2. 현실적인 테스트 설정의 어려움
현실적이고 유용한 유닛 테스트를 설정하는 것은 어려운 과제이다. 테스트 중인 애플리케이션의 일부가 전체 시스템의 일부처럼 동작하도록 관련된 초기 조건을 생성해야 하기 때문이다. 이러한 초기 조건이 올바르게 설정되지 않으면, 테스트는 현실적인 맥락에서 코드를 실행하지 못하게 되며, 이는 유닛 테스트 결과의 가치와 정확성을 감소시킨다.[37]유닛 테스트로 의도한 효과를 얻기 위해서는 소프트웨어 개발 프로세스 전체에서 엄격한 규율이 필요하다. 실행된 테스트뿐만 아니라 소스 코드 또는 소프트웨어 내의 다른 유닛에 추가된 모든 변경에 대한 정확한 기록을 보존하는 것이 필수적이다. 버전 관리 시스템의 사용이 필수적이다. 만약 새로운 유닛 버전이 이전에 통과했던 특정 테스트에서 실패한다면, 버전 관리 소프트웨어는 그 이후에 유닛에 적용된 소스 코드의 변경 목록을 제공할 수 있다.
또한, 실패한 테스트 케이스가 매일 검증되고 즉시 처리되도록 보장하는 지속 가능한 프로세스를 구현하는 것이 필수적이다.[38] 이러한 처리가 구현되지 않고 팀의 워크플로우에 통합되지 않으면, 애플리케이션은 유닛 테스트 스위트와의 동기화를 잃고, 거짓 양성이 증가하여 테스트 스위트의 유효성이 저하될 수 있다.
5. 3. 개발 과정 전반의 규율 필요
유닛 테스트로부터 의도된 이점을 얻기 위해서는 소프트웨어 개발 과정 전반에 걸쳐 엄격한 규율이 필요하다. 유닛 테스트는 특정 오류의 존재나 부재를 보여줄 뿐, 오류가 없다는 것을 증명하는 것은 아니기 때문에, 다른 소프트웨어 테스트 활동과 함께 실시해야 한다.[36]소프트웨어 테스트는 조합 문제이다. 예를 들어, 2가지로 분기되는 판정 스테이트먼트는 결과가 참이 되는 경우와 거짓이 되는 경우, 최소 2개의 테스트가 필요하다. 많은 경우 작성 코드 1행당 프로그래머는 3~5행의 테스트 코드를 작성해야 한다.[36] 또한, 유닛 테스트용 코드에는 최소한 테스트 대상 코드와 같은 정도의 버그가 존재할 가능성이 있다.
현실적이고 유용한 테스트를 설정하는 것도 어려운 과제이다. 테스트 대상 애플리케이션의 일부가 완전한 시스템의 일부처럼 작동하도록 관련 초기 조건을 작성해야 하는데, 이러한 초기 조건이 제대로 설정되지 않으면 테스트에서 현실적인 컨텍스트로 코드가 실행되지 않아 유닛 테스트 결과의 가치와 정확성이 감소한다.[37]
유닛 테스트로 의도한 효과를 얻기 위해서는 소프트웨어 개발 프로세스 전체에서 엄격한 규율이 필요하다. 실행된 테스트뿐만 아니라 소스 코드 또는 소프트웨어 내의 다른 유닛에 추가된 모든 변경에 대한 정확한 기록을 보존하는 것이 필수적이며, 버전 관리 시스템의 사용이 필수적이다.
또한, 실패한 테스트 케이스가 매일 검증되고 즉시 처리되도록 하기 위한 지속 가능한 프로세스를 구현하는 것이 필수적이다.[38] 이러한 처리가 구현되지 않으면 애플리케이션의 진화가 유닛 테스트 스위트와의 동기화를 잃고 거짓 양성이 증가하며 테스트 스위트의 유효성이 저하될 것이다.
5. 4. 버전 관리 필요성
버전 관리 시스템을 사용하는 것이 중요하다. 만약 유닛의 이후 버전이 이전에 통과했던 특정 테스트를 실패하는 경우, 버전 관리 소프트웨어는 그 이후 해당 유닛에 적용된 소스 코드 변경 사항(있는 경우) 목록을 제공할 수 있다.[36]유닛 테스트로 의도한 효과를 얻기 위해서는, 엄격한 규율이 소프트웨어 개발 프로세스 전체에서 필요하다. 실행된 테스트뿐만 아니라, 소스 코드 또는 소프트웨어 내의 다른 유닛에 추가된 모든 변경 사항에 대한 정확한 기록을 보존하는 것이 필수적이다.[38]
5. 5. 정기적인 검토 필요성
테스트 케이스 실패를 정기적으로 검토하고 즉시 해결하기 위한 지속 가능한 프로세스를 구현하는 것도 필수적이다.[17] 만약 그러한 프로세스가 구현되지 않고 팀의 작업 흐름에 깊이 스며들지 않는다면, 애플리케이션은 유닛 테스트 스위트와 동기화되지 않게 발전하여, 오탐을 증가시키고 테스트 스위트의 효과를 감소시킬 것이다.실패한 테스트 케이스가 매일 검증되고, 즉시 처리되도록 하기 위한, 지속 가능한 프로세스를 구현하는 것이 필수적이다.[38] 이러한 처리가 구현되지 않고, 팀의 워크플로우에 깊이 뿌리내리지 않은 경우, 애플리케이션의 진화가 유닛 테스트 스위트와의 동기화를 잃고, 거짓 양성이 증가하며, 테스트 스위트의 유효성이 저하될 것이다.
5. 6. 임베디드 시스템의 한계
임베디드 시스템 소프트웨어는 최종적으로 실행되는 플랫폼과 다른 플랫폼에서 개발되기 때문에, 데스크톱 프로그램에서 가능한 것처럼 실제 배포 환경에서 테스트 프로그램을 쉽게 실행할 수 없다.[18] 이와 같이 임베디드 시스템 소프트웨어의 유닛 테스트에는 다른 환경에는 없는 과제가 존재한다.[39]5. 7. 외부 시스템 통합 테스트의 한계
유닛 테스트는 메서드가 애플리케이션 외부와 상호 작용할 때 어려움을 겪는다. 예를 들어, 데이터베이스와 연동하는 메서드는 데이터베이스 상호 작용에 대한 모의(mock)를 만들어야 하는데, 이는 실제 데이터베이스 상호 작용만큼 완전하지 않을 수 있다.[19]클래스 간의 참조 때문에 한 클래스에 대한 테스트가 다른 클래스에 영향을 줄 수 있다. 특히 데이터베이스에 의존하는 클래스를 테스트할 때, 데이터베이스와 통신하는 코드를 작성하는 것은 단위 테스트의 범위를 벗어나는 것이며, 성능 문제를 일으킬 수 있고, 통합 테스트의 성격을 띠게 되어 실패 원인을 파악하기 어렵게 만든다.
따라서, 소프트웨어 개발자는 데이터베이스 쿼리 주위에 추상 인터페이스를 만들고, 모의 객체를 구현해야 한다. 이를 통해 코드를 추상화하고, 유닛을 더 철저하게 테스트할 수 있게 되어, 고품질의 유지보수 가능한 유닛을 만들 수 있다.
하지만 가장 간단한 프로그램조차도 모든 실행 경로를 평가하기 어렵기 때문에, 유닛 테스트만으로는 모든 오류를 발견할 수 없다. 유닛 테스트는 유닛 자체의 기능만 검사하므로, 통합 오류나 시스템 레벨의 오류는 발견할 수 없다. 따라서 유닛 테스트는 다른 소프트웨어 테스트와 함께 실시해야 한다.
소프트웨어 테스트는 조합 문제이기도 하다. 예를 들어, 두 갈래로 나뉘는 결정문은 최소 두 개의 테스트(참, 거짓)가 필요하다. 이로 인해 코드 1줄당 3~5줄의 테스트 코드가 필요할 수 있으며, 이는 시간과 노력이 많이 드는 작업일 수 있다.[36] 또한, 유닛 테스트 코드 자체에도 버그가 있을 수 있다.
현실적이고 유용한 테스트를 설정하는 것도 어렵다. 테스트 대상 애플리케이션이 실제 시스템처럼 작동하도록 초기 조건을 만들어야 하는데, 이것이 제대로 설정되지 않으면 테스트 결과의 가치가 떨어진다.[37]
유닛 테스트의 효과를 얻으려면, 소프트웨어 개발 프로세스 전반에 걸쳐 엄격한 규칙이 필요하다. 실행된 테스트와 소스 코드 변경 사항을 정확하게 기록하고 버전 관리 시스템을 사용하는 것이 필수적이다. 또한, 실패한 테스트 케이스를 매일 확인하고 즉시 처리하는 지속적인 프로세스를 구현해야 한다.[38] 그렇지 않으면 유닛 테스트 스위트와 애플리케이션의 동기화가 깨져 거짓 양성이 증가하고, 테스트 스위트의 유효성이 저하될 수 있다.
임베디드 시스템 소프트웨어의 유닛 테스트는, 소프트웨어가 최종 실행 환경과 다른 플랫폼에서 개발되기 때문에, 데스크톱 프로그램처럼 테스트하기 어렵다는 추가적인 문제가 있다.[39]
6. 응용
유닛 테스트는 다양한 소프트웨어 개발 방법론 및 프레임워크에서 활용된다. 익스트림 프로그래밍(XP)에서는 테스트 주도 개발(TDD)을 통해 유닛 테스트를 코드 작성 전에 작성하며, 이는 코드 품질 향상과 리팩토링을 용이하게 한다.[40] xUnit과 같이 자동화된 테스트 프레임워크는 테스트 실행을 자동화하여 효율성을 높인다.[42]
D, Go, 루비, 파이썬 등은 유닛 테스트를 기본적으로 지원하며, 다른 많은 언어들도 표준 또는 서드 파티 유닛 테스트 프레임워크를 제공한다.[21][24][29][26] 구글은 C++를 위한 유닛 테스트 라이브러리인 구글테스트를 제공한다.[44]
프로그래밍 언어별 유닛 테스트 지원 현황은 다음과 같다.
내장 지원 | 표준 프레임워크 지원 | 라이브러리/프레임워크 지원 |
---|---|---|
6. 1. 익스트림 프로그래밍 (XP)
테스트 주도 개발(TDD)에서는 코드를 작성하기 전에 유닛 테스트를 작성한다. 개발자는 먼저 필요한 동작에 대한 테스트 코드를 추가하고, 이 테스트를 통과하는 데 필요한 최소한의 코드를 작성한다. 그 후 코드를 리팩토링(테스트 코드 포함)하여 개선하고, 다시 새로운 테스트를 추가하는 과정을 반복한다.[40]유닛 테스트는 자동화된 유닛 테스트 프레임워크에 기반한 익스트림 프로그래밍(XP)의 핵심 요소이다. 이러한 프레임워크는 xUnit과 같은 서드 파티 제품일 수도 있고, 개발 팀 내부에서 자체적으로 만들 수도 있다.[40]
XP에서는 TDD를 위해 유닛 테스트를 활용한다. 개발자는 소프트웨어 요구 사항이나 결함을 드러내는 유닛 테스트를 작성하는데, 이 테스트는 초기에는 실패한다. 이는 요구 사항이 아직 구현되지 않았거나, 기존 코드의 결함을 의도적으로 드러내기 때문이다. 이후 개발자는 테스트를 통과하기 위한 가장 간단한 코드를 작성한다.[40]
시스템의 대부분 코드는 유닛 테스트를 거치지만, 모든 코드 경로를 테스트하는 것은 아니다. XP는 "모든 실행 경로 테스트" 대신 "깨질 수 있는 모든 것 테스트" 전략을 따른다.[40]
테스트 코드는 구현 코드와 동일한 품질로 유지 관리되며, 중복은 제거된다. 개발자는 유닛 테스트 코드를 테스트 대상 코드와 함께 코드 저장소에 릴리스한다. XP의 철저한 유닛 테스트는 코드 개발과 리팩토링을 더 쉽고 자신감 있게 만들고, 코드 통합을 간소화하며, 정확한 문서를 제공하고, 모듈식 설계를 가능하게 한다. 유닛 테스트는 회귀 테스트로도 지속적으로 실행된다.[40]
유닛 테스트는 Emergent Design(창발적 설계) 개념에도 중요하다. 창발적 설계는 리팩토링에 크게 의존하기 때문에 유닛 테스트가 필수적이다.[40]
6. 2. 자동화된 테스트 프레임워크
자동화된 테스트 프레임워크는 테스트 실행을 자동화하여 테스트 작성 및 실행 속도를 높여준다. 이러한 프레임워크는 다양한 프로그래밍 언어에 맞게 개발되었다.[20]일반적으로 이러한 프레임워크는 서드 파티 형태로 제공되며, 통합 개발 환경(IDE)이나 컴파일러와 함께 제공되지 않는 경우가 많다.[20]
xUnit으로 통칭되는 오픈 소스 프레임워크, TBrun, JustMock, Isolator.NET, Isolator++, Parasoft Test (C/C++test, Jtest, dotTEST), Testwell CTA++, VectorCAST/C++와 같은 상용 솔루션 등 다양한 종류의 테스트 프레임워크가 존재한다.[42]
프레임워크 없이도 어설션, 예외 처리 등을 활용하여 단위 테스트를 수행할 수 있다. 프레임워크 도입이 어려운 상황에서는 이러한 방식이 유용할 수 있으며, 테스트가 아예 없는 것보다는 낫다. 그러나 프레임워크가 구축되면 테스트 추가가 더 쉬워진다.[20][42]
일부 프레임워크는 고급 테스트 기능이 부족하여 수동 코딩이 필요할 수 있다. 매개변수화된 단위 테스트(PUT)는 매개변수를 사용하는 테스트로, JUnit 4 및 다양한 .NET 테스트 프레임워크에서 지원된다. 테스트에 적합한 매개변수는 수동으로 작성하거나, 테스트 프레임워크를 통해 자동으로 생성할 수 있다. QuickCheck와 같은 테스트 입력 생성 도구도 있다.[20]
6. 3. 언어 지원
D, Go, 루비, 파이썬은 유닛 테스트를 기본으로 지원한다. 유닛 테스트 프로세스를 단순화시켜주는 유닛 테스트 프레임워크는 여러 언어로 개발되고 있다. 구글에서도 재능 기부 활동의 하나로 구글테스트[44]라는 C++를 위한 유닛 테스트 라이브러리를 제공하고 있다. (맥OS 엑스코드도 지원)일부 프로그래밍 언어는 유닛 테스트를 직접 지원한다. 이러한 언어의 문법은 라이브러리(타사 또는 표준)를 가져오지 않고도 유닛 테스트를 직접 선언할 수 있도록 한다. 또한, 유닛 테스트의 부울 조건은 유닛 테스트가 아닌 코드에서 사용되는 부울 표현식과 동일한 구문으로 표현될 수 있다.
내장 유닛 테스트를 지원하는 언어는 다음과 같다.
표준 유닛 테스트 프레임워크를 지원하는 언어는 다음과 같다.
일부 언어는 내장 유닛 테스트 지원은 없지만, 확립된 유닛 테스트 라이브러리 또는 프레임워크를 가지고 있다. 이러한 언어는 다음과 같다.
ABAP |
C++ |
C# |
클로저[30] |
엘릭서 |
자바 |
자바스크립트 |
오브젝티브-C |
펄 |
PHP |
파워셸[31] |
R with testthat |
스칼라 |
Tcl |
Visual Basic .NET |
Xojo with XojoUnit |
7. 한국의 단위 테스트 현황 및 발전 방향
(이전 출력이 없으므로, 수정할 내용이 없습니다. 이전 출력을 제공해주시면 수정 작업을 진행하겠습니다.)
참조
[1]
서적
Automated Defect Prevention: Best Practices in Software Management
null
Wiley-IEEE Computer Society Press
[2]
간행물
Production of large computer programs
Office of Naval Research, Department of the Navy
1956
[3]
간행물
Production of large computer programs (reprint of the 1956 paper with an updated foreword)
https://dl.acm.org/d[...]
IEEE Computer Society Press
1987-03-01
[4]
서적
Proceedings of the 1964 19th ACM national conference
Association for Computing Machinery
1964-01-01
[5]
서적
Proceedings of the 1969 24th national conference
Association for Computing Machinery
1969-08-26
[6]
서적
MIL-STD-483 Military standard: configuration management practices for systems, equipment, munitions, and computer programs
United states, Department of Defense
1970-12-31
[7]
간행물
The value of a proper software quality assurance methodology
https://dl.acm.org/d[...]
1978-01-01
[8]
서적
Java Unit Testing with JUnit 5 : Test Driven Development with JUnit 5
null
Apress
2017
[9]
서적
Software engineering at Google : lessons learned from programming over time
O'Reilly
2020
[10]
서적
Test-Driven Development by Example
Addison-Wesley
2002
[11]
서적
Unit Test Frameworks: Tools for High-Quality Software Development
https://books.google[...]
O'Reilly Media, Inc.
2004
[12]
간행물
Understanding and Controlling Software Costs
http://faculty.ksu.e[...]
2016-05-13
[13]
웹사이트
Test Early and Often
https://msdn.microso[...]
Microsoft
[14]
웹사이트
Prove It Works: Using the Unit Test Framework for Software Testing and Validation
http://www.ni.com/wh[...]
National Instruments
2017-08-21
[15]
웹사이트
You Still Don't Know How to Do Unit Testing (and Your Secret is Safe with Me)
https://stackify.com[...]
2023-03-10
[16]
서적
The Mythical Man-Month
Addison-Wesley
[17]
웹사이트
Change Code Without Fear: Utilize a regression safety net
http://www.ddj.com/d[...]
2008-02-08
[18]
웹사이트
Making Unit Testing Practical for Embedded Development
https://www.electron[...]
2020-07-20
[19]
웹사이트
Unit Tests And Databases
http://wiki.c2.com/?[...]
2024-01-29
[20]
웹사이트
Intermediate Coverage Goals
http://www.bullseye.[...]
2009-03-24
[21]
웹사이트
Unit Tests - D Programming Language
http://dlang.org/spe[...]
D Language Foundation
2017-08-05
[22]
웹사이트
How to Write Tests
https://doc.rust-lan[...]
2015–2023
[23]
웹사이트
Crystal Spec
https://crystal-lang[...]
crystal-lang.org
2017-09-18
[24]
웹사이트
testing - The Go Programming Language
http://golang.org/pk[...]
golang.org
2013-12-03
[25]
웹사이트
Unit Testing · The Julia Language
https://docs.juliala[...]
docs.julialang.org
2022-06-15
[26]
웹사이트
unittest -- Unit testing framework
https://docs.python.[...]
2016-04-18
[27]
웹사이트
RackUnit: Unit Testing
http://docs.racket-l[...]
PLT Design Inc.
2019-02-26
[28]
웹사이트
RackUnit Unit Testing package part of Racket main distribution
https://pkgs.racket-[...]
PLT Design Inc.
2019-02-26
[29]
웹사이트
Minitest (Ruby 2.0)
http://ruby-doc.org/[...]
Ruby-Doc.org
[30]
웹사이트
API for clojure.test - Clojure v1.6 (stable)
https://clojure.gith[...]
2015-02-11
[31]
웹사이트
Pester Framework
https://github.com/p[...]
2016-01-28
[32]
서적
Automated Defect Prevention: Best Practices in Software Management
http://www.wiley.com[...]
Wiley-IEEE Computer Society Press
[33]
웹사이트
Towards a Framework for Differential Unit Testing of Object-Oriented Programs
http://people.engr.n[...]
2012-07-23
[34]
웹사이트
Mocks aren't Stubs
http://martinfowler.[...]
2008-04-01
[35]
웹사이트
第9回 テストで重要なのは見極めること
https://www.itmedia.[...]
ITmedia
2014-02-19
[36]
웹사이트
Alberto Savoia sings the praises of software testing
http://searchsoftwar[...]
2007-09-20
[37]
웹사이트
Unit Testing Best Practices
http://www.parasoft.[...]
2009-07-01
[38]
웹사이트
Change Code Without Fear: Utilize a regression safety net
http://www.ddj.com/d[...]
2008-02-06
[39]
웹사이트
Making Unit Testing Practical for Embedded Development
http://electronicdes[...]
2011-11-23
[40]
웹사이트
Agile Emergent Design
http://www.agilesher[...]
Agile Sherpa
2010-08-03
[41]
간행물
IEEE Standard for Software Unit Testing: An American National Standard, ANSI/IEEE Std 1008-1987
http://aulas.carloss[...]
IEEE Standards Board
[42]
웹사이트
Intermediate Coverage Goals
http://www.bullseye.[...]
2006-2008
[43]
웹사이트
unittest -- Unit testing framework
http://docs.python.o[...]
1999-2012
[44]
Google Test
Google Test
http://code.google.c[...]
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com