카테고리 없음
Unit Testing 6장
eumjo_o
2023. 6. 30. 18:34
6장 단위 테스트 스타일
6.1 단위 테스트의 세 가지 스타일
- 출력 기반 테스트 output based testing
- 상태 기반 테스트 state based testing
- 통신 기반 테스트 communication based testing
6.1.1 출력 기반 테스트 정의
💡 출력 기반 테스트 output based testing 테스트 대상 시스템(SUT)에 입력을 넣고 생성되는 출력을 점검하는 방식
- 전역 상태나 내부 상태를 변경하지 않는 코드에만 적용되므로 반환 값만 검증하면 됨
- 이러한 테스트 스타일은 부작용이 없고, SUT 작업 결과는 호출자에게 반환하는 값 뿐
⇒ 함수형 프로그래밍 functional programming
6.1.2 상태 기반 스타일 정의
💡 상태 기반 테스트 state based testing 작업이 완료된 후 시스템 최종 상태를 확인하는 것 상태: SUT, 협력자 중 하나, 데이터베이스, 파일 시스템 등과 같은 프로세스 외부 의존성의 상태 등
6.1.3 통신 기반 스타일 정의
💡 통신 기반 테스트 communication based testing 목을 사용해 테스트 대상 시스템과 협력자 간의 통신을 검증 : SUT의 협력자를 목으로 대체하고, SUT가 협력자를 올바르게 호출하는지 검증
- 고전파 - 상태 기반 스타일 선호
- 런던파 - 통신 기반 스타일 선호
- ⇒ 두 분파 모두 출력 기반 테스트 사용
6.2 단위 테스트 스타일 비교
- 회귀 방지
- 리팩터링 내성
- 빠른 피드백
- 유지 보수성
6.2.1 회귀 방지와 피드백 속도 지표로 스타일 비교하기
회귀 방지 지표 결정 요인
- 테스트 중에 실행되는 코드의 양
- 코드 복잡도
- 도메인 유의성
6.2.2 리팩터링 내성 지표로 스타일 비교하기
💡 리팩터링 내성 - 리팩터링 중에 발생하는 거짓 양성(허위 경보) 수에 대한 척도
- 출력 기반 테스트가 테스트 품질이 좋다.
- 구현 세부 사항에 거의 결합되지 않으므로 리팩터링 내성이 있고,
- 작고 간결하므로 유지 보수하기도 좋다.
6.2.3 유지 보수성 지표로 스타일 비교하기
- 상태 기반 테스트
- 비공개 상태를 노출하지 않도록 해야 한다.
- 출력 기반 테스트보다 크기가 커서 유지 보수가 쉽지 않다.
- 헬퍼 메서드와 값 객체를 사용해 유지 보수성 문제를 완화할 수 있지만 제거할 수는 없다.
- 통신 기반 테스트
- 애플리케이션 경계를 넘어서 외부 환경에 부작용이 보이는 통신만 확인해야 한다
- 통신 기반 테스트의 유지 보수성은 출력 기반 테스트 및 상태 기반 테스트와 비교할 때 좋지 않다
- 목은 공간을 많이 차지하는 경향이 있어서 테스트 가독성이 떨어진다
6.2.4 스타일 비교하기: 결론
출력 기반 상태 기반 통신 기반
리팩터링 내성을 지키기 위해 필요한 노력 | 낮음 | 중간 | 중간 |
유지비 | 낮음 | 중간 | 높음 |
6.3 함수형 아키텍처 이해
6.3.1 함수형 프로그래밍이란?
💡 함수형 프로그래밍 - 수학적 함수로 된 프로그래밍
수학적 함수 - 숨은 입출력이 없는 함수(메서드)
- 하나의 입력, 하나의 출력, method signature
- 수학에서의 함수 예 - f(x) = x+1
public int Increment(int x) {
return x + 1;
}
int y = Increment(4);
int y = 5; // 동일한 구문임.
- 테스트가 짧고 간결, 유지 보수에 쉬우며, 거짓 양성 빈도 낮음
- 부작용과 예외가 숨은 출력에 해당한다
- 부작용 - 메서드 시그니처에 표시되지 않은 출력
int x = 0; // x의 변경 -> 부작용 -> 숨은 출력 public int Increment() { x++; return x; }
- 내외부 상태에 대한 참조 - DateTime.Now, 데이터베이스 등
6.3.2 함수형 아키텍처란?
- 함수형 프로그래밍의 목표
- 부작용을 완전히 없애는 것이 아니라 비즈니스 로직을 처리하는 코드와 부작용을 일으키는 코드를 분리하는 것
- 결정을 내리는 코드와 해당 결정에 따라 작용하는 코드 유형을 구분해서 비즈니스 로직을 처리하는 코드와 부작용을 일으키는 코드를 분리가능하다.
- 두 계층을 잘 분리하려면, 가변 셀이 의사 결정을 추가하지 않게끔 결정을 나타내는 클래스에 정보가 충분히 있는지 확인해야 한다.
- 목표는 출력 기반 테스트로 함수형 코어를 두루 다루고 가변 셀을 훨씬 더 적은 수의 통합 테스트에 맡기는 것
- 함수형 아키텍처
- 부작용을 다루는 코드를 최소화하면서 순수 함수(불변) 방식으로 작성한 코드의 양을 극대화한다.
- 가변 셀은 모든 입력을 수집
- 함수형 코어는 결정을 생성
- 셀은 결정을 부작용으로 변환
- 해당 결정을 따라 작용하는 코드 - 함수형 코어에 입력 데이터를 제공하고 데이터베이스와 같은 프로세스 외부 의존성에 부작용을 적용해 그 결정을 해석, 가변 셀(mutable shell)
💡 캡술화와 불변성 소프트웨어 프로젝트의 지속적인 성장을 가능하게 하는 것
객체지향 프로그래밍은 작동 부분을 캡슐화해 코드를 이해할 수 있게 한다. 함수형 프로그래밍은 작동 부분을 최소화해 코드를 이해할 수 있게 한다.
6.3.3 함수형 아키텍처와 육각형 아키텍쳐 비교
- 함수형 아키텍처
- 모든 부작용을 도메인 계층 밖으로 밀어낸다.
- 함수형 아키텍처는 모든 코드를 함수형 코어와 가변 셀이라는 두 가지 범주로 나눈다.
- 가변 셀은 입력 데이터를 함수형 코어에 공급하고, 코어가 내린 결정을 부작용으로 변환한다.
- 육각형 아키텍처
- 결정과 실행을 분리하는 함수형 아키텍처와 매우 유사.
- 도메인 계층과 애플리케이션 서비스 계층을 구별한다.
- 도메인 계층은 비지니스 로직에 책임이 있는 반면, 애플리케이션 서비스 계층은 데이터베이스나 SMTP 서비스와 같이 외부 애플리케이션과의 통신에 책임이 있다.
6.4 함수형 아키텍쳐와 출력 기반 테스트로의 전환
함수형 아키텍처로 리팩터링하는 두 단계
- 프로세스 외부 의존성에서 목으로 변경
- 목에서 함수형 아키텍처로 변경
6.4.1 감사 시스템 소개
6.4.2 테스트를 파일 시스템에서 분리하기 위한 목 사용
- 테스트가 파일 시스템에 접근하지 않게되므로, 테스트도 만족되고 유지비용도 절감된다.
- 리팩터링 후에도 회귀 방지와 리팩터링 내성이 나빠지지 않는다.
초기 버전 목 사용
회귀 방지 | 좋음 | 좋음 |
리팩터링 내성 | 좋음 | 좋음 |
빠른 피드백 | 나쁨 | 좋음 |
유지 보수성 | 나쁨 | 중간 |
6.4.3 함수형 아키텍처로 리팩터링하기
- 인터페이스 뒤로 부작용을 숨기고 해당 인터페이스를 주입하는 대신 부작용을 클래스 외부로 완전히 이동할 수 있다.
- 가변 셀과 함수형 코어는 함수형 아키텍처를 형성한다.
- 가변 셀은 작업 디렉터리에서 파일과 해당 내용을 수집해 함수형 코어에게 준다음, 반환 값을 파일 시스템의 변경 사항으로 변환한다.
- 더이상 복잡한 목 설정이 필요없고, 단순한 입출력만 필요하므로 테스트 가독성을 크게 향상시킨다.
초기 버전 목 사용 출력 기반
회귀 방지 | 좋음 | 좋음 | 좋음 |
리팩터링 내성 | 좋음 | 좋음 | 좋음 |
빠른 피드백 | 나쁨 | 좋음 | 좋음 |
유지 보수성 | 나쁨 | 중간 | 좋음 |
6.4.4. 예상되는 추가 개발
- 작업 디렉터리가 비어있는 경우 새로운 파일 작성
- 기존 파일에 새 레코드 추가
- 현재 파일의 항목 수가 한도를 초과할 때 다른 파일 작성
6.5 함수형 아키텍처의 단점 이해하기
6.5.1 함수형 아키텍처 적용 가능성
- 함수형 코어의 클래스는 협력자로 작동하면 안 되고, 작업의 결과인 값으로 작동해야 함
6.5.2 성능 단점
- 함수형 아키텍처와 전통적인 아키텍처 사이의 선택은 성능과 코드 유지 보수성(제품코드와 테스트 코드 모두) 간의 절충
- 함수형 아키텍처는 유지 보수성 향상을 위해 성능을 희생한다.
6.5.3 코드베이스 크기 증가
- 모든 코드베이스를 함수형 아키텍처로 전환할 수는 없다.
- 함수형 아키텍처를 전략적으로 적용해야하며, 시스템의 복잡도와 중요성을 고려해야 한다.
- 코드베이스가 단순하거나 중요하지 않으면, 함수형 아키텍처에 필요한 초기 투자는 별 효과가 없다.