아키텍처에서 왜 테스트가 중요할까?
현실적인 필요성
- QA보다 더 생산적으로 일할 수 있다. 앱이 확장될 수록 QA로 확인할 수 없는 다양성이 존재할 수 있다.
- 테스트 없이 결과를 확인하기 위해 로그 및 디버깅 작업은 비용이 큰 작업
좋은 설계를 촉진한다.
- 단일 책임 원칙, 개방 폐쇄 원칙을 위배하고 있는 클래스는 테스트하기 힘들기 떄문에, 테스트를 위해 해당 원칙을 준수해야 한다.
코딩 생산성
- Fail 되지 않은 코드가 잘 움직이고 있다는 심리적 안정성이 코드에 집중할 수 있게 한다.
- 실제로 테스팅 구현을 함께하면 개발 시간이 짧아 진다.
협업을 촉진한다.
- 효율적인 코드 리뷰가 가능하다.
- 문서로서의 테스트 코드는 특정 API의 기능, 원작자의 의도를 확인할 수 있다.
- 코드 담당자가 아니라도 테스트 코드 떄문에 코드를 수정할 수 있다.(코드 제작자의 의도와 다르게 구현하면 테스트가 실패하기 떄문이다.)
테스트 범위의 구분
End to end Test
- 사용자의 상호작용 시나리오를 테스트하며, 사용자와 어플리케이션의 상호작용이 잘 이루어지는지 확인한다.
Integration Test
- 서로 다른 시스템들의 상호작용이 잘 이루어지는지 확인한다.
- 외부 라이브러리 및 데이터베이스를 묶어 검증하기 위해 테스트한다.
Unit Test
- 실행 가능한 소프트웨어중 가장 작은 부분을 테스트한다.
테스트를 구성하는 비율은 아래와 같이 UnitTest가 가장 많은 피라미드 구성을 띄어야 하며, 모래시계와 같이 E2E가 많거나, 역삼각형 패턴은 피해야 한다.
안드로이드 테스트의 종류
Local Test (test 폴더)
- JVM 위에서 동작하며, 디바이스가 아닌 JVM 실행 환경 위에서만 동작한다.
- 디바이스를 직접 동작시키지 않기 때문에 빠른 속도가 장점이다.
- 대표적으로 Junit 라이브러리가 있다.
Local Test (androidx.test)
- Robolectric라이브러리는 에뮬레이터에서만 동작하는 Activity, Fragment를 JVM 실행환경에서 실행할 수 있게 한다.
- 상당수의 Unit Test는 여기서 구현된다.
- 여러 객체, 클래스 간의 상호작용을 필요로 하는 Integration Test도 여기서 구현될 수 있다.
Instrumented test
- 에뮬레이터 혹은 실제 기기에서 동작하는 작업을 테스트 할 수 있다.
- 안드로이드 프레임 워크에 종속성이 있는 테스트.
- 안정성/호환성 테스트와 성능 테스트를 진행한다.
- Espresso 라이브러리를 사용한다.
무엇을 테스트 해야할까?
- 에러가 발생할 수 있는 모든 기능을 테스트 해야 하며, 테스트는 의미있는 테스트여야 한다.
- 수정 변경되는 모든 기능을 테스트 해야 한다. -> 사용자의 요청에 따라서 상태 변화, 상태가 View에 적용되는지 테스트한다.
좋은 테스트 예시 : Edge Cases
- Boundary Condition : 8 글자 이하로 비밀번호를 입력 받을 때 8글자를 넣기
- 네트워크 에러 : 400, 500 에러 코드에 따라서 응답 변화
- 잘못된 데이터 : 포멧이 틀린 json 문자열을 검증할 수 있음.
- 저장소 오버플로
- 중요 객체의 재생성 상황 : 설정 변경 상황(화면 전환, 다크모드)