제가 지금까지 테스트 관련 여러가지 포스팅을 다뤄왔지만
이번 포스팅은 테스트 짜는법을 1도 모른다고 가정하고 진행하겠습니다.
이 포스팅을 통해 테스트 짜는법을 배워가면 좋겠습니다.
테스트 코드란?
네 정말 당연합니다.
테스트 코드는 말그대로 여러분이 작성한 어플리케이션의 코드를 굳이 빌드해서
실행하지 않고 편하게 테스트 해볼 수 있도록 짜두는 것이 테스트 코드입니다.
그래서 왜 짜야하는지 아직 느낌이 안오시죠?
아니 그냥 일일히 빌드해서 테스트 할 수 있잖아? ㅋㅋ
테스트 코드 짜면 시간이 두배인데?? ㄷㄷ
이런 의문이 들 수 있습니다.
예를 들어봅시다.
여러분이 게시판 어플리케이션을 만들었다고 해봅시다.
기능 1: 게시글을 업로드 한다.
기능 2: 게시글을 삭제 한다.
대충 이정도의 기능이 있다고 해봅시다.
여러분은 해당 앱이 돌아가는지 테스트 하려면 빌드 후 직접 게시글을 업로드 & 삭제하겠죠?
테스트하는데 시간이 대략 1분정도 들겠네요 ^^
근데 여러분이 만든 게시판 어플이 대박이나서 매우 많은 기능을 추가하고
유지보수하게 되었습니다.
여러분은 이제 업데이트 전에 앱이 잘돌아가는지 확인해야합니다.
그런데 기능을 보니
기능 1: 게시글을 업로드 한다.
기능 2: 게시글을 삭제 한다.
...
기능 65: 댓글을 두번 누르면 삭제 팝업을 띄운다.
기능 66: 댓글을 꾹 누르면 감정표현 이모티콘을 띄운다.
WOW!!! 처음에 기능이 매우 적었던 어플리케이션이 이정도로 규모가 커졌네요?
이걸 일일히 테스트하는데 시간이 꽤 걸릴거 같죠?
직접 빌드해서 테스트 다하는데 이번엔 시간이 무려 20분이나 걸리네요...
만약 테스트 코드를 짜둔다면?
앱을 빌드해서 개발자가 일일히 기능들을 테스트하지 않아도 되고
그냥 작성된 테스트코드 실행 한 번만 하면
기능 1 ~ 기능 66 까지 아주 빠르게 테스트가 완료됩니다.
상상만해도 편하죠??
결론적으로 테스트를 개발자가 일일히 안해도 되니
유지보수에 시간이 단축됩니다.
일반적으로 테스트 코드는 BDD(Behavior-Driven-Development) 라는 형식을 따라서 짭니다.
이게 뭐나고요?
Given-When-Then 이라는 틀안에서 짜는겁니다.
Given : 주어진 조건
When : 특정 상황
Then : 검증
또 테스트 함수의 이름은 ~ 할때 ~ 를 해야한다 라는 식으로 의도가 명확해야합니다.
한번 예시를 보겠습니다.
interface FeedRepository {
suspend fun loadFeeds(results: Int, inc: String) : ApiResponse<FeedResponse?>
}
이런 형태의 Repository를 테스트 하고싶어요
그렇다면
크게 이러한 순서로 접근해줍시다.
우리가 테스트 하고싶은 Repository는
interface FeedRepository {
suspend fun loadFeeds(results: Int, inc: String) : ApiResponse<FeedResponse?>
}
해당 Repository입니다.
class FeedRepositoryTest {
}
Test 폴더안에 해당 테스트 클래스를 생성해줍니다.
class FeedRepositoryTest {
private val repository: FeedRepository = FeedRepositoryImpl(TestNetworkProvider.feedService)
}
우리는 FeedRepository를 테스트하고싶으니 해당 객체를 만들어줍시다.
class FeedRepositoryTest {
//테스트 스레드 풀 지정을 위한 디스패처
private val dispatcher = StandardTestDispatcher()
//테스트에서 사용할 코루틴 스코프
private val testScope = TestScope(dispatcher)
private val repository: FeedRepository = FeedRepositoryImpl(TestNetworkProvider.feedService)
@Before
fun setUp() {
//테스트 실행 전 사용할 스레드 풀을 Test 스레드풀로 고정
Dispatchers.setMain(dispatcher)
}
@After
fun tearDown() {
//테스트 실행 후 Test 스레드풀 고정 해제
Dispatchers.resetMain()
}
}
@Before : 테스트 함수 실행전 실행
@After : 테스트 함수 후 실행
Coroutine을 통해 테스트 할 것이기 때문에 TestScope 세팅을 해줍니다.
class FeedRepositoryTest {
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
private val repository: FeedRepository = FeedRepositoryImpl(TestNetworkProvider.feedService)
@Before
fun setUp() {
Dispatchers.setMain(dispatcher)
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
@Test
fun `피드를_10개_요청했을_때_피드를_10개_반환한다`() = testScope.runTest {
//Given : 주어진 조건은 Feed 10개, 이름 사진 이메일
val result = 10
val inc = "name,picture,email"
//When : Repository로 Feed 요청을 했을 때
val testResult = repository.loadFeeds(result, inc) as ApiResponse.Success
//Then : 불러온 Feed의 개수는 10개이다.
assertEquals(10, testResult.data?.results?.size)
}
}
이렇게 테스트 함수를 작성후 통과하는지 확인해줍니다.
잘 통과 하는군요!
Repository가 아니라 다른클래스라고 하더라도 테스트 짜는방법은 다 비슷비슷합니다.
Given-When-Then 꼭 지키세요!
경력 개발자는 테스트 짤 줄 아는게 당연하다고 생각합니다.
하지만 신입이라면?
사실 신입한테 테스트를 기대하진 않습니다.
하지만 현업자 입장에서 신입개발자가 테스트 코드를 짤 줄 안다하면 보는 눈이 달라질 겁니다.
기능 구현하기도 바쁜 신입이 테스트도 고려한다고?
솔직히 호감이 갑니다.
괜히 채용공고 우대사항에 테스트 코드 작성경험이 있는게 아닙니다.
완벽하게 이해하는게 아니더라도 테스트 코드 흉내라도 내서 경쟁력을 가지면 좋겠습니다.
여러분이 선망하는 기업은 테스트 코드 무조건 짠다고 장담합니다.