구린내 시리즈 .단언 룰렛 변덕스러운 테스트 깨지기 쉬운 테스트 잦은 디버깅 수동 조정 느린 테스트
3.
단언 룰렛 Assertion Roulette 증상 (symptoms) 무슨 단언문이 실패했지 ? 영향 (impact) 테스트 실패시 어느 단언문인지 모르면 ? 최악의 예 ? scm 에서 테스트 뻑 , 그러나 내 환경에서는 재현 안됨 . -> 귀찬아짐
4.
단언 룰렛 Assertion Roulette 그렇다면 원인은 ? 1. 욕심쟁이 테스트 - 한번에 너무 많은 기능을 검증하는 테스트 단언문이 실패해도 계속 실행하고 싶은 욕구가 생긴다면 그 테스트는 욕심쟁이 . 근본 원인 a. 하나의 테스트 메소드에 여러 테스트 -> 단위테스트 숫자가 줄어듬 -> 한큐에 해버리고 빨리빨리 굿 ! -> But 그건 수동 테스트의 이야기 완전 자동 테스트에서는 나쁘다 ! b. 고객 테스트를 xUnit 자동화시 발생 가능 , 적당히 짧게 만들도록 신경써야 함 .
5.
단언 룰렛 Assertion Roulette 욕심쟁이 테스트 해결책 뭐 쪼개고 줄이면 되지 . extract method refactoring 테스트에 정말 필요한건 빼고 다 제거하는게 좋을지도 .. xUnit 고객 테스트 자동화시 복잡한 픽스처 설치에는 뒷문 설치를 고려 초반부와 후반부를 나눠서 결함 국소화 Defect Localization 짧게 만들어 읽기 좋은 테스트 , 문서를 대신할 수 있는 테스트를 만들자 ! 蛇足 ) 테스트 좋은 이야기이나 이게 왜 단언 룰렛에 들어가는가 ?
6.
단언 룰렛 Assertion Roulette 원인 투 ! 2. 빠진 단언 메세지 테스가 실패한다 . -> 메세지가 없어 ! 뭐야 이건 ! 蛇足 ) 이것이 진정 룰렛 원인 . 단언 메세지가 없거나 , 똑같을 때 . 해결책 식별가능한 단언 메세지 넣기 . IDE 와 잘 결합된 그래픽 테스트 실행기 .
7.
변덕스러운 테스트 Erratic Test 증상 어떨 때는 통과하고 어덜 때는 실패하고 미치는 영향 빼면 테스트로 검증 안된 코드가 생기고 , 내버려두면 또 다른 실패를 알아차리기 힘들어짐 문제 해결을 위한 조언 정보를 모아야 한다 ! 어디에서 성공하고 어디에서 실패하고 , 모든 테스트가 실행되는지 일부만 실행되는지 , 테스트 스위트를 연달아서 여러번 실행했을 때 , 여러 테스트 실행기에서 동시에 테스트를 실행할 때 등등 정보를 통한 잠재 후보군 걸러내기 .
8.
변덕스러운 테스트 Erratic Test 문제 해결을 위한 조언 서로 반응하는 테스트 서로 반응하는 테스트 스위트 외로운 테스트 자원 누수 자원 낙관주의 반복 안 되는 테스트 테스트 실행 전쟁 비결정적 테스트
9.
변덕스러운 테스트 Erratic Test 원인 1. 서로 반응하는 테스트 ( 외로운 테스트도 서로 반응하는 테스트의 일종 ) 증상 혼자서 잘 돌아가던 테스트가 갑자기 실패했다 ! - 스위트에 다른 테스트를 추가 혹은 삭제 - 같은 스위트의 다른 테스트가 실패 - 테스트의 이름을 바꾸거나 위치가 바뀜 - 새로운 버전의 테스트 실행기를 설치 근본 원인 공유 픽스처를 통한 테스트간의 의존 서로 반응하는 메커니즘 - 데이타베이스 때문일 수도 , 좀더 미묘한 것일 수도 ... - static 을 왠만하면 피하자 (singleton 이나 registry 로 대체 ) 꼭 써야 한다면 실행할 때마다 초기화 !
10.
변덕스러운 테스트 Erratic Test 서로 반응하는 이유 - 다른 테스트 픽스처 설치 단계에서 생성한 픽스처에 의존 - 다른 테스트의 SUT 실행 단계에서 변경한 SUT 에 의존 - 같이 실행된 두 테스트 사이의 어떤 상호 배타적인 싱행으로 인해 생긴 충돌 관계가 없어지는 이유들 - 스위트에서 제거 - 더이상 sut 의 상태를 변경하지 않게 수정 - sut 의 상태를 변경하려는 시도가 실패 - 문제가 되는 테스트보다 뒤에 실행 충돌 하는 이유들 - 스위트에 추가 - 가장 먼저 실행 - 의존하고 있는 테스트보다 먼저 실행 거짓 양성 (false positive) 의 가능성 다른 테스트에 의존하면 안됨 ! 우리 Unit test 하는 거자나요 !
11.
변덕스러운 테스트 Erratic Test 해결책 신선한 픽스처를 쓰는 것 ! 공유해야 한다면 Immutable 로 Lazy Setup 도 한가지 방법 중복 코드는 -> 생성 메소드에 다른 테스트에 있는 픽스처 설치 코드는 -> 테스트 도우미로 뒷처리 잘하자 . automated fixture teardown 도 생각해보자 . 정기적인 테스트 역주행으로 정신차리고 살자 !
12.
변덕스러운 테스트 Erratic Test 2. 서로 반응하는 테스트 스위트 이것도 공유자원 때문 책에서는 자원 생성을 예로 듬 . 문제 테스트를 까서 찾아봐야함 하나씩 지우면서 찾는것도 한가지 방법 서로 반응하는 테스트가 여러개 있을 테스트 스윗 내에 여러개 있을으도 있으니 주의 해결책 신성한 픽스처 immutable fixture 자동 텍스처 해체
13.
변덕스러운 테스트 Erratic Test 3. 자원 누수 Test 나 Sut 가 제한된 자원을 소모한다 . 증상 테스트가 점차 느려진다 갑자기 실패한다 . 근본 원인 제한 된 리소스를 할당하거나 , 해제하는 것에 실패 . - SUT 가 리소스를 제대로 해제하는것에 실패했을때 ( 발견하자마자고치는 것이 좋다 ) - test 자체가 리소스 할당이나 해체를 제대로 못했을때
14.
변덕스러운 테스트 Erratic Test 해결책 자원 해제를 제대로 해줘야함 guaranteed In-ilne Teardown 이나 automated Teardown 을 고려 자원 pool 의 사이즈를 1 로 해두면 , leak 감지가 쉬움 .
15.
변덕스러운 테스트 Erratic Test 4. 자원 낙관주의 테스트가 결과값이 비결정적인 외부 리소스에 의존할때 . 증상 다른 환경에서는 실패 근본원인 다른 환경에선는 지원 안되는 리소스 사용시 해결책 Fresh fixture -> 언제나 어느 환경에서나 사용가능하게 꼭 외부리소스를 사용 해야한다면 -> source code repository 에
16.
변덕스러운 테스트 Erratic Test 5. 반복 불가능한 테스트 처음 실행과 다르게 동작하는 테스트 ( 이전 실행 된 자기자신과 상호작용 ) 증상 처음 성공 -> 쭉 실패 또는 처음 실패 -> 쭉 성공 근본 원인 테스트에서 공유 픽스처를 변경할 경우 . Database sandbox, Lazy Setup 여도 동일한 문제 발생할 수 있음 . 해결책 신선한 픽스처 !! 완전한 격리를 위해서는 데이터베이스 샌드박스도 가짜 데이타베이스로 바꿔라 꼭 데이타베이스를 써야한다면 키값을 Distinct 하게 . 자동 해체도 한가지 방법
17.
변덕스러운 테스트 Erratic Test 6. 테스트 실행 전쟁 여러 사람이 동시에 테스트 실행시 무작위로 에러남 . 증상 데이타 베이스와 같이 공유된 외부 리소스에 의존하는 test 를 돌릴 때 에러가 났다 안났따 . 영향 테스트 실행전쟁은 deadline 에 가까워져 바쁠 때 더 잘일어날 수 있음 ! 근본원인 여러 테스트에서 열리고 쓰이는 파일 - 다른 테스트에서 이미 열어서 사용하고 있는 파일을 access 할 때 데이타베이스의 레코드 - 다른 test 가 update 할 때 같은 레코드를 update 나 delete 를 시도할 때 - 다른 테스트 때문에 read lock 걸린 레코드를 update 나 delete 시도할 때
18.
변덕스러운 테스트 Erratic Test 해결책 역시나 Fresh Fixture 각각 테스트의 own Database Sandbox ( 내 테스트끼리는 막지는 못함 ) 역시나 Immutable Shared Fixture 뒷처리 때문이라면 Automated Teardown ( 해결책은 아닐지라도 빈도를 낮춰 줄 수도 .)
19.
변덕스러운 테스트 Erratic Test 7. 비결정적 테스트 혼자실행할때도 무작위로 테스트 실패 증상 매번 결과가 다르다 . ( 테스트 실행 전쟁도 아니다 .) 근본원인 실행될때마다 다른값을 쓸때 발생가능 . ex)Distinct 한 key 만들 때 입력값에 따라 동작이 달라지는 SUT 에서 - 음수를 다르게 취급하거나 허용 범위가 있을때 - 문자열 길이 제한을 고려 안했을 때 . ( 이런 임의의 입력값을 쓰면 테스트에 대한 이해가 떨어지고 , 반복성도 안좋아짐 ) 테스트 내의 조건문도 문제 ( Flexible < Nondeterministic 더 중요 )
20.
변덕스러운 테스트 Erratic Test 해결책 조건문 로직 모두 제거 . 무작위 값 -> 결정적인 값 ( 이로 인해 테스트가 너무 많아지면 인자값을 받는 테스트 혹은 데이터 주도 테스트 )
21.
깨지기 쉬운 테스트 Fragile Test test 가 구동되는데 영향을 안미치게 SUT 를 변경했는데도 실패하는 테스트 . 증상 시스템이나 sut 동작을 변경했더니 잘 되던 테스트가 컴파일이 안되거나 실패한다 . 4 가지 민감함 때문임 ( 인터페이스 , 동작 , 데이터 , 문맥 ) 미치는 영향 깨지기 쉬운 테스트가 있으면 변경사항이 있을 때 마다 매번 확인해야 함으로 유지 비용 Up. 애자일의 점증적 인도시 더 치명적 . 문제 해결을 위한 조언 테스트 실패의 패턴을 찾아라 ! 컴파일이 안될때 -> 인터페이스에 민감함 코드를 고친 후 오작동 코드 제거시 제대로 동작 -> 동작에 민감함 코드를 안고쳤다면 혹은 제거해도 그대로 동작 -> 데이터에 민감함 or 문맥에 민감함
깨지기 쉬운 테스트 Fragile Test 원인 간접 테스팅 , 욕심쟁이 테스트의 신호일 수도 . 테스트하기 힘든 소프트웨어 , 테스트 대역을 제대로 못 사용 -> 작은 부분 테스트가 힘들어짐 -> 깨지기 쉬운 테스트 결국 4 가지 민감함으로 귀결 .
24.
깨지기 쉬운 테스트 Fragile Test 1. 인터페이스에 민감함 증상 정적타입 언어 -> 컴파일 실패 동적타입 언어 -> Runtime Error 인터페이스가 api 일 수도 ui 일 수도 . 해결책 내부에서만 사용되는 인터페이스일 경우 -> sut api 캡슐화 엄격한 관리 -> 메소드 제거 이전 deprecated 등의 규약으로 하위 호완성 확보
25.
깨지기 쉬운 테스트 Fragile Test 2. 동작에 민감함 증상 기능 추가나 버그 수정후 테스트가 실패 근본 원인 회기 테스트 일 수도 있으나 다음과 같을 경우 - 회귀 테스트에서 SUT 테스트 전 상태를 설치하는 기능이 변경 - 회귀 테스트에서 SUT 테스트 후 상태를 검증하는 기능이 변경 - 회귀 테스트에서 픽스처 해체 코드가 변경 SUT 가 너무 큰 범위를 테스트 하는 것일 수도 있음 . 해결책 픽스처 설치 과정에서 잘못된 가정을 하는 코드는 생성 메소드 안으로 캡슐화 . 맞춤 단언문이나 검증 메소드로 캡슐화 . 이런 캡슐화로 변경할 코드양을 줄일 수 있음 .
26.
깨지기 쉬운 테스트 Fragile Test 3. 데이터에 민감함 증상 다음과 같을 때 테스트 실패 - sut 의 테스트 전 상태가 들어있는 데이터베이스에 데이터를 추가 - 데이터베이스의 레코드 update or delete - 공유 픽스처 설치 코드 변경 - 공유 픽스처가 첫 번째 테스트가 실행되기 전에 변경 근본 원인 테스트가 원하는 데이터가 데이터 베이스에 존재한다는 가정하에 실행되는 경우 . 해결책 데이터베이스의 최근 변경에 영향을 안받게 . 가정이 잘못되었는지 확인 . 데이터 입력과 테스트시의 데이터가 다를 수도 . 신선한 픽스처 사용 ! 데이터베이스 분할 스키마 사용 . 데이터가 똑바로 변경 되었는지 확인 .( 델타단언문으로 스냅샷 비교 )
27.
깨지기 쉬운 테스트 Fragile Test 4. 문맥에 민감함 증상 잘돌아가다 알수 없는 이유로 실패 근본 원인 - 검증하려는 기능이 시간이나 날짜와 의존 관계 - SUT 에서 의존하는 코드나 시스템의 동작이 변경 작은 일부분이 아닌 욕심쟁이 테스트일경우 문맥에 민감함이 생김 . SUT 가 다른 시스템의 입력에 의존할 경우 입력 값이 바뀌어도 문제 될 수있음 . 해결책 SUT 의 모든 입력값을 제어해야함 . 다른 시스템에 의존한다면 테스트 스텁 사용 . ex) 시스템 시계를 가상시계로 바꿔 테스트 .
28.
깨지기 쉬운 테스트 Fragile Test 그외 원인들 심하게 명세된 소프트웨어 - 동작에 민감함 . 광범위한 모의 객체를 사용해가며 목적이 아닌 과정에 집중 민감한 동등 - 객체를 문자열로 바꿔 비교하는 등 목적에 상관없는 동작에 민감함 깨지기 쉬운 픽스처 - 표준 픽스처를 변경했더니 깨짐 . 데이터나 문맥 민감함과 같음 .
29.
잦은 디버깅 Frequent Debugging 테스트 실패 원인을 찾으려면 직접 디버깅을 해야 한다 . 증상 출력만으로 문제 파악이 어려움 -> 디버깅을 해야한다 . 원인 결함 국소화가 부족 . 테스트 메세지 부족 . 상위레벨 테스트 작성하면서 단위 테스트 부족 - 클래스에서 생긴 논리 에러를 알려줄 단위테스트가 없다 . - 클래스 통합 과정에서 생긴 에러를 찾아 줄 컴포넌트 테스트가 없다 .( 모의 객체의 광범휘한 사용후 ) 드문 테스트 실행 때문에도 생김 . 미치는 영향 디버깅은 느리고 지루함 . 생산성 악화 . 해결 패턴 좋은 테스트 더 작성 , 훌륭한 단위 테스트는 결함 국소화 보장 . ( 클래스테스트 -> 컴포넌트테스트 )
30.
수동 조정 Manual Intervention 테스트 실행할 때마다 수작업 . 증상 테스트 실행 전에 직접 입력 또는 테스트 실행 결과 사람이 직접 검증 영향 잦은 디버깅 높은 테스트 유지 비용 자동 통합빌드와 회귀 테스트가 불가능 원인 1. 수동 픽스처 설치 ( 서버 설치 , 서버 프로세스 실행 , 미리만든 픽스처 설치 스크립트 실행등 ) 픽스처 설치 단계에 자동화를 싱경 안쓰면 이렇게 된다 자동 테스트가 목적이란걸 잊지 말자 . 2. 수동 결과 검증 증상 : 테스트의 성공 여부와 SUT 의 정상 동작과 거리가 있다 . 해결책 : 단언 메소드 호출 같은 결과 검증 로직으로 자체 검사 . 3. 수동 이벤트 발생 ex: 네트워크 선이 끊긴 상황 , 데이터 베이스 연결이 끊긴 경우 , UI 에서 버튼 누른 경우 해결책 : 자동화할 방법을 찾아라 simulate 하거나 test stub 을 사용하거나 .
31.
느린 테스트 Slow Tests 증상 느리다 . 영향 느린 테스트 -> 생산성 저하 tdd 로 개발할 때 치명적 여러가지 간접 비용 발생 - 코드 통합 후 모든 테스트가 실행되는 동안 기다려야 해 다음 코드 통합이 드려짐 - 테스트 실행을 기다리는 동안 다른 사람을 훼방 놓는 바람에 버려지는 시간 (?) - 이전 테스트 실행으로 생긴 버그 잡느라 버려지는 시간 . ( 테스트가 길어져 뭐가 문제 될만한 거였는지 까먹는다 .) 문제 해결을 위한 조언 그냥 막대기 ( 테스트 실행결과 ) 를 보고 있다가 오래걸린다 싶은거 찾기 . 프로파일러 사용 . 혹은 setup,teardown 에 시간 기록 로그로 자체 프로파일링 .
32.
느린 테스트 Slow Tests 원인 1. 느린 컴포넌트 사용 SUT 의 어떤 컴포넌트 응답이 굉장히 느리다 . 근본원인 데이터베이스를 쓰는 것도 원인이 될 수 있ㅇ므 . 해결책 느린 컴포넌트를 테스트 대역으로 교체 . 메모리서 돌아가는 가짜 데이터베이스를 사용
33.
느린 테스트 Slow Tests 2. 일반 픽스처 모든 테스트에서 동일한 덩치큰 픽스처를 계속 생성하다보니 균일하게 느려지는 증상 . 근본원인 모든 테스트에서 매번 신선한 픽스처로 거대한 일반 픽스처를 생성 테스트 숫자에 비례해서 늘어남 . 해결책 일반 픽스처를 -> 공유 픽스처로 구현 . 변덕스러운 테스트를 피하기 위해 immutable 로 만드는 것이 좋음 . 테스트마다 수행하는 픽스처 설치 작업량을 줄이는 것이 좋다 .
34.
느린 테스트 Slow Tests 3. 비동기 테스트 일부 테스트가 지나치게 느리다 . 이런 테스트에는 명시적인 지연이 들어있다 . 근본원인 스레드나 프로세스 생성후 기다렸다 검증하는 경우 . 미치는 영향 2 초 정도야 별거 아니라고 할 수 있지만 이런 테스가 쌓이면 골치 아프다 . 해결책 비동기는 피하라 . 테스트 가능한 컴포넌트 뽑아내기 . 대강 만든 실행기
35.
느린 테스트 Slow Tests 4. 너무 많은 테스트 테스트를 아무리 빨리 실행시켜도 너무 많아서 오래걸린다 . 근본원인 정말 시스템이 클 수도 , 겹치는 내용이 많을 수도 , 너무 자주 실행할 수도 해결책 언제나 모든 실행 X -> 모든 테스트를 정기적으로 실행 O 부분 스위트를 테스트하면서 한밤중에 모든 테스트 자동화 테스트 = ' 빌드 파이프라인 ' 시스템의 크기가 크다면 시스템을 나눠라 ! ( 여러개의 독립적인 하위시스템이나 컴포넌트 ) 이렇게 나눠진 컴포넌트같의 인터페이스가 변경되면 코드도 갱신 -> 문서로서의 테스트 역할 ! 모든 컴포넌트 한꺼번에 실행이 필수지만 , 커밋 전 스위트까지 이럴 필욘 없다 .