IT/일본 IT 회사 생활

[IT 회사생활] 미경험자도 할 수 있는 프로그래밍 현장의 단위 테스트(유닛 테스트, UT)

개발자 두더지 2020. 11. 26. 23:10
728x90

일본 블로그의 글을 번역한 내용입니다. 내용에 의역과 오역이 존재할 수 있습니다. 지적해주시면 수정하도록 하겠습니다.

이 블로그 글은 작성된지 3년이 지난 글로 현재의 테스트 방법과 다를 수 있습니다.

 

이 글은 "경험 제로라도 할 수 있는 프로그래밍 현장의 단위 테스트"의 책 내용을 정리한 것입니다 (책 내용을 단순히 요약, 정리한 글이라 블로그 글 자체의 내용이 제대로 연결되어 있지 않습니다).

 

단위 테스트에 대한 기초 지식


1. 어플리케이션 개발에서는 설계에 너무 많은 시간을 들여서, 테스트에 시간을 들일 수 없는 경우가 있다.

2. 치명적인 장애 발생 가능성이 있다.

3. 아무튼 모든 상정하는 테스트를 실시하는 것이 불가능하다.

4. 테스트 케이스를 짤 필요가 있다.

5. 각 테스트 단계의 초석이 되는 단위 테스트가 중요하게 되었다.

 

고품질의 시스템

유저를 만족시키고 불안이나 불만이 나오지 않도록 하는 것

- 시스템 에러 : 데이터의 손상 등의 걱정이 생긴다.

- 느린 대응 : 화가 나서 두 번 다시 사용하지 않게 된다.

 

뭉쳐서 테스트하는 것은 금물 

- 조사 시간이 걸리고 문제가 나타나는 곳을 나누기 힘들다.

- 현장에서의 동기 부여가 잘 안 된다.

- 리스크의 연기

프로젝트 끝에 실시하는 테스트는 재현, 원인 조사, 수정, 재테스트에 시간이 걸리기 때문에, 프로젝트 초반부터 실시하는 것이 올바르다. 

 

테스트 클래스의 장점

- 반복해서 실시하는 것이 가능하다.

- 결과가 알기 쉽다.

- 테스트가 실행되는 곳을 알기 쉽다.

- degrade의 조기 발견.

 

테스트 종류와 목차

테스트 내용 상세・구체적인 예
단위 테스트 프로그램 단위의 작동을 확인 인수의 데이터 셋 계산 결과가 올바른지, DB에 올바른 데이터가 등록되어 있는지, 예외가 발생했을 때 처리가 올바르게 되는가.
종합 테스트 단위 테스트가 완료된 프로그램 간의 데이터 전달을 확인 등록 버튼을 눌렀을 때, 폼의 값이 DB에 등록되고 다음 화면에 이동하는가.
시스템 테스트 실제 환경의 데이터, 환경에서 시스템이 목적을 만족하는지 확인 외부 시스템과의 연계, 매일/주별/월별/년도별 등의 시점에 발생하는 처리, 시스템 다운
부담 테스트 시스템에 부담이 주어도 문제없이 가동하는지 확인 대량의 데이터가 존재하는 DB에 대량의 리퀘스트를 송신하고 상정 시간내에 응답이 돌아오는가, 어느 정도의 수준에서 응답이 돌아오지 않는가, 장시간 가동할 경우 메모리 손실 확인
시스템 테스트 시스템의 취약성을 공격하는 시스템 다운이나 기밀정보의 누출여부를 확인 버퍼 오버 플로우 등의 보안 취약점, SQL인젝션 등의 부적절한 접근 등을 확인

 

대표적인 테스트 방법

화이트 박스 테스트

소스 코드의 안에 구문, 분기, 조건 등을 망라하여 실행해 버그를 찾아내는 테스트.

테스트 내용
스테이트먼트 커버리지(명명 망라) 소스 코드의 모든 명명문 중에 한 번이라도 실행된 상태의 비율
브랜치 커버리지(분기 망라) 소스 코드의 모든 분기문 중에 한 번이라도 실행된 상태의 비율
컨디션 커버리지(조건 망라) 소스 코드의 분기문에 설정되어 있는 하나 하나의 조건에 대해 성립, 비성립의 경우를 실행한 것의 비율

블랙 박스 테스트

입력된 값에 대해 사양대로 결과가 나오는지 테스트한다.

다음의 두 개 테스트 모두 실시한다.

테스트 내용
동일 값 분할 방법 프로그램이 기대하는 '유효 동일 값', '무효 동일 값'를 입력하여 결과를 확인
경계값 분할 방법 프로그램의 '유효 동일값'과 '무효 동일값'의 경계가 되는 값을 입력하여 결과를 확인

 

버그의 발견은 어렵다

단위 테스트뿐만 아니라 모든 테스트에서 버그를 발견하기 위해서는 풍부한 지식과 경험이 필요하다.

입력 순서를 바꾼 경우, 다른 유저가 동일한 처리를 실시한 경우 등 테스트만으로는 알 수 없는 버그나 의도하지 않는 동작이 발생하는 경우도 생긴다.

프로젝트 멤버의 경험이 부족한 경우에는 숙련자의 리뷰나 다른 프로젝트의 버그 대응을 공유받는다.

시간이 걸리지만 단위 테스트 단계에서부터 이러한 높은 레벨로 테스트를 실시할 수 있다면 수정 비용도 낮아진다.

 

부담 테스트 실시 시점

보통 종합 테스트가 끝나면 실시하는 경우가 많지만, 보틀넥이 되는 원인은 대부분 퍼포먼스가 나오지 않는 SQL이나 메모리를 무분별하게 사용하여 구현하는 경우이다.

따라서 되도록 앞 단계에서 부담 테스트를 실시하는 것이 좋다.

 

구체적인 단위 테스트 방법과 주의사항


테스트 클래스의 기본

테스트 클래스는 JUnit의 규약에 의해 작성하면 어떠한 개발자가 보든 가독성이 높아진다.

1 . 테스트의 전제 조건을 정리한다.

2. 테스트를 실행한다.

3. 실행 결과로부터 테스트의 성공 여부를 판정한다.

단위 테스트를 즐기기 위해서는

- 테스트 범위를 좁게 한다.

- 만든 쪽부터 테스트를 실시한다.

JUnit4의 어노테이션

어노테이션 설명
@Test 테스트 메소드
@Before 모든 테스트 메소드 작동 전에 실행되는 처리 
@After 모든 테스트 메소드 작동 후에 실행되는 처리
@BeforeClass 테스트 클래스의 테스트 작동 전에 1번만 실행되는 처리
@AfterClass 테스트 클래스의 테스트 실행 후에 1번만 실행되는 처리

테스트 결과를 판정하는 메소드

- assert***

- fail

등등이 있다.

테스트 케이스 생각법

다양한 인수의 패턴을 생각할 수 있다.

- 인수가 null

- 배열 사이즈가 0

- byte의 값이 Byte.MAX_VALUE

- byte의 값이 Byte.MIN_VALUE

- byte의 값이 0

 

DB 액세스의 단위 테스트

DB의 테스트에는 주의할 점이 엄청나게 많다.

- 읽기 권한이 없는 사람에 개인 데이터가 보이지는 않는가

- 틀린 데이터가 저장되어 있지는 않은가

- DB의 상태는 항상 동일한가

- O/R 맵핑 프레임워크를 사용하고 있는 경우, SQL이 옳게 발행되고 있는가

이러한 작업을 대폭 경감, 자동화하는 툴이 'DbUnit'이다.

DbUnit을 사용한 테스트 순서

1. DbUnit에 DB와 연결을 성립 (@Before/@After)

2. 테스트 전의 DB의 상태를 백업 (@Before)

3. 테스트의 전체가 되는 데이터의 설정(@Before)

4. 테스트 실시(@Test)

5. 2번에서 백업한 데이터를 DB에 복원 (@After)

테스트 데이터의 주의점

1. SELECT의 테스트

1) 1건만 취득

(1) 모든 테스트 데이터를 별도로 한다 : select된 데이터를 명확히 하기 위해

(2) 조건을 만족하지 않는 경우 취득할 수 없는 것을 확인한다.

2) 1건 이상의 건을 취득 (검색 조건에 주 키(primary key)를 지정하지 않는 경우도 포함)

(1) 데이터는 3건 이상 (검색 조건에 합치 2건 이상, 합치하지 않는 1건 이상) : 복수의 건 취득, 데이터 맵핑이 정확한지 확인하기 위해

(2) "<", "<="를 사용하고 있는 경우, 동일한 값에 대한 테스트를 한다.

(3) AND, OR을 연결하는 경우, 그러한 조합을 망라하여 테스트한다.

2. INSERT 테스트

1) null 이외의 데이터를 등록

2) null이 허용되는 칼럼에 null을 등록

3) 칼럼 전체에 최대 행의 데이터를 등록

4) 고유 제약에 위반하는 데이터를 등록

3. UPDATE 테스트

1) null이외의 데이터를 등록

2) null이 허용되는 칼럼에 null을 등록

3) 칼럼 전체에 최대 행의 데이터를 등록

4) 갱신 대상의 레코드가 없을 때의 갱신

5) 여러 개의 레코드를 갱신

4. DELETE의 테스트

존재하지 않는 레코드의 삭제

 

DB 액세스의 테스트 케이스를 줄이는 방법

Web 어플리케이션에서는 SQL의 테스트 부족으로 인해 버그가 보틀넥이 되는 경우가 많다.

몇십줄이되는 SELECT문의 테스트나 부모 레코드 유무를 고려한 테스트 데이터 작성은 귀찮다.

테이블 설계 단계에서부터 SQL문의 테스트가 쉽다는 것을 의식하자.

테스트하기 쉬운 테이블 설계

- 아이덴테티파이(identify)의 추가 : ID 칼럼을 추가하는 것으로 테이블 결합에 필요한 칼럼이 줄어든다.

- DB제약을 사용 : 제약상 DB에 존재하지 않는 데이터의 테스트가 필요하지 않게 된다.

보존면에서 테스트 용의성을 높이자

- SQL문과 메소드의 입도(粒度)를 1대 1로 : 대상을 알기 쉽게 한다.

- 길이가 긴 SQL은 적당히 : 테스트 케이스를 만들기 어렵게 된다.

- SQL문에서의 계산은 삼가하도록 : 버그의 원이이 프로그램이나 SQL인가 구분하기 쉽게 한다.

 

외부 리소스, 시스템의 테스트

MocK오브젝트 사용 의 주의점

1) Mock오브젝트를 작성하는 데에 설계가 필요 : 작동을 모방하기 위해 사양을 이해할 필요가 있다.

2) Mock오브젝트을 작성하여 테스트해도 버그는 나온다.

3) 대상 클래스의 사양 변경이 있다면 Mock오브젝트의 수정이 필요하게 된다.

외부 리소스에 액세스하는 테스트의 주의점

1) 파일 입출력 등의 예외가 발생하는 처리와 로직을 분리한다 : 불필요한 예외를 고려할 필요가 없는 처리가 되므로 단순해 진다.

2) 메루 송신 등의 외부 시스템 연계가 필요한 경우에는 연계 부분과 로직을 분할한다.

외부 시스템 연계와 화면쪽의 테스트

단위 테스트 단계에서는 외부 시스템과 연계가 되지 않는 경우도 많으므로, Mock오브젝트, 프레임워크를 이용한다.

화면쪽의 단위 테스트

- EMMA으로 범위를 측정한다.

- 캡쳐 리플레이 도구를 사용한다.

좋은 테스트의 조건

- 명확한 목적

- 반복실행이 가능

- 환경에 의존하지 않음 (로컬/서버, OS, 실행환경, 컴파일 환경을 의식한다)

- 테스트가 독립되어 존재

- 처리의 의존 관계가 강하지 않음

 

지속적으로 테스트를 하기 위해서는

설계 단계에서부터 테스트를 고려하며, 프로젝트 멤버 내에 공유 인식을 가져 함께 클래스의 입도를 맞추고, 테스트하기 쉬운 소스코드를 작성하도록 한다.

즉 품질에 대해 프로젝트 멤버 전원이 의식을 가져야한다.

 

테스트를 의식한 개발 포인트

회귀 테스트를 자동화하기 위해서는

- SQL문의 안에서 사용하는 날짜는 모두 인수로 받는다.

- 시스템 날짜는 전부 유틸리티 클래스로 취득한다 : 날짜의 형식이나 실행 환경 의존을 방지하기 위해

발견하기 힘든 버그를 만들지 않는다

- 단위 테스트에서는 확인이 어려운 스레드 세이프를 삼가한다 (싱글톤 오브젝트에는 결과를 갖지 않도록 하는 등). 

- 문자 엔코드에 주의(엔코드를 지정하는 등) : OS에 의존하기 위해

정적 분석 툴을 사용한다

-코딩 제약을 작성

- CheckStyle에 의한 제약 체크

- FindBugs에 의한 위험한 코딩의 배제

- 포맷의 설정

바퀴의 재개발을 피한다

외부에 공개되어 있는 라이브러리를 도입하는 것으로 테스트의 필요성조차 배제할 수 있다.

개발환경은 빠르게 준비

빠른 기간 안에 준비하는 것으로 코딩 제약이나 CheckStyle, FindBugs의 규칙을 결정할 수 있다.

 

빌드 및 테스트 자동화하는 환경의 구축

지속적인 통합의 실현

개발 완료 후에 버그를 발견하여 수정하는 것이 아닌, 지속적인 빌드, 테스트를 실해하고 문제를 조기에 발견한다.

- 버전관리 시스템으로부터 소스 코드를 취득

- 빌드

- 단위 테스트

- jar이나 war로 패키지

- 결합 환경에 디플로이. 이것들을 CI 도구를 사용하여 자동으로 실시한다.


참고자료

qiita.com/disc99/items/177bdf6352de463fdc87

728x90