테스트 코드 도입은 정말 비효율적일까?

실전 사례로 보는 테스트 코드 도입

Soo
DelightRoom
7 min readFeb 20, 2024

--

테스트 코드가 중요한 이유에 대해서 모두들 많이 들어봤을것입니다.
제가 생각하는 테스트 코드가 중요한 이유는 크게 3가지인데요.

테스트 코드가 중요한 이유

  1. 리팩터링 보장
    - 리팩토링하면서 기존 기능에 사이드 이펙트가 없는지 확인할 수 있음
  2. 문서화의 역할
    - 특정 기능이 어떻게 동작해야하는지에 대한 예시를 제공, 새로운 개발자가 코드 베이스를 더 쉽게 이해할 수 있음
  3. 버그 감지
    -
    개발 초기 단계에서 버그를 발견하고 수정하는데 도움을 줄 수 있음
    - 모든 시나리오를 검증 할수는 없지만, 엣지 케이스에 대한 검증이 테스트 코드를 작성하면서 알 수 있음

테스트 코드 작성의 어려움

그렇지만 테스트 코드의 작성은 쉽지 않습니다. 왜 그럴까요?

저의 경우는 아래의 경우로 어려웠습니다.

  1. 테스트 코드를 작성하는데 시간이 너무 오래걸린다.
  2. 테스트 범위와 세부 사항을 결정하는게 어렵다.
    2–1. 어디까지 테스트 해야 할것인가?
    2–2. 무엇을 테스트 해야할것인가?

어렵다는 이유로 테스트 코드 작성은 미루어졌고, 그 과정에서 기획 변경 시 개발 구조를 크게 수정해야하는 문제가 있거나, 아니면 테스트를 하며 버그를 잡게 되는 경우가 빈번했습니다.

변화의 계기는 안드로이드팀 OKR 을 더 달성하고 싶은 마음으로 시작하게 되었습니다. 크래시를 예방하고, 크래시가 발생하더라도 재발 방지 하고싶은 마음으로 테스트 코드를 작성하게되었습니다.

안정적인 제품을 만들기 위해서 저희 팀은 BDD 스타일의 테스트 코드 작성에 정착하게 되었는데요. 이번 글에서는 kotest 를 사용하여 BDD 방식으로 테스트 케이스를 작성하는 경험을 공유하고 그 과정에서 제가 느꼈던 점들에 대하여 공유하고자 합니다.

BDD 란 무엇인가?

  • Full name 으로는 Behavior Driven Development 인데요. 한마디로 행동 주도 개발을 뜻합니다.
  • 유저의 행동 기반을 바탕으로 사용자 시나리오를 구성하여 테스트 케이스를 작성하는 방법입니다.
  • 여기서 말하는 사용자 시나리오는 각 기능의 요구사항이라고 생각하시면 됩니다.

테스트 코드 작성 과정

테스트 코드의 방식으로는 여러가지가 있지만, 이번 글에서는 Kotest 의 BehaviorSpec 을 이용한 방식을 예를 들어 보겠습니다.

BehaviorSpec 이란?

Given — When — Then 형태로 주어진 조건(Given) 에서 특정 행동(When) 을 했을때 기대하는 결과(Then) 를 나타냅니다.

  • Given : 환경 (ex : 유저가 비밀번호 변경 화면에서 )
  • When : 행위 (ex: 유효하지 않는 비밀번호를 입력하면)
  • Then : 기대결과 (ex : 비밀번호 변경 버튼이 비활성화 된다.)

테스트 코드가 기획서의 요구사항이나 사용자 스토리를 반영한다고 생각하시면 되고, 기획서의 요구사항의 경우의 수를 더 자세하게 표현할 수록 테스트 코드가 더 다양해 진다고 보시면 됩니다.

코드로 예시를 들자면 아래와 같은 형태입니다.

class ChangePasswordTest : BehaviorSpec() {
Given("비밀번호를 변경하려는 유저가") {
When("유효하지 않는 비밀번호를 입력하면") {
Then("비밀번호 변경 버튼이 비활성화된다.") {
}
}
}
}
}

실제 프로젝트 작성 예시

  • 출석 체크의 기능에 대해 예를 들어보겠습니다.
  • 알라미의 출석 체크 기능은 ‘출석 체크 시작 후 알람 해제를 연속으로 5일 해제 했을때 출석체크가 완료’ 됩니다.
  • 기존에는 출석 체크 완료의 기준이 연속 7일이였지만, 참여율을 높이기 위해 완료 기준이 5일로 변경되었습니다.
  • 출석 체크 완료 날짜가 변경되었을 때 작성했던 테스트 코드의 예시를 들어 보겠습니다.
class WakeUpCheckChallengeViewModelTest : BehaviorSpec() {
Given("유저가 출석체크 진행중이면") {
val staredTime = LocalDateTime(2023, 1, 1, 8, 0)
val fakeWakeUpCheckChallengeRepository = FakeWakeUpCheckChallengeRepository(
wakeUpCheckChallenge = ChallengeCheck(
status = ChallengeStatus.PARTICIPATING,
startedTime = staredTime.toEpochMilliseconds(),
dismissTime = 0L,
successCount = 2
),
)
When("출석 체크의 성공 날짜 기준은") {
val expectedSuccessCount = fakeWakeUpCheckChallengeRepository.getSuccessCount()
Then("7이다") {
expectedSuccessCount shouldBe 7
}
}

When("출석 체크 실패 후") {
fakeWakeUpCheckChallengeRepository.failed()
Then("출석 체크 성공 횟수는 5로 초기화 된다.") {
val expectedSuccessCount = fakeWakeUpCheckChallengeRepository.getSuccessCount()
expectedSuccessCount shouldBe 5
}
}

}
}
}

위 테스트 코드를 작성하면서 저는 테스트 코드의 장점에 대해서 실감 할 수 있었습니다.

테스트 코드의 장점

  • 알람을 실제로 울리지 않고도 기능에 대한 검증을 할 수 있다.
  • 엣지 케이스에 대한 테스트를 해볼 수 있다.
    ex) 기존에 출석체크를 5일 이상 진행한 유저가 있을때는 성공 날짜 기준을 무엇으로 잡을 것인가?

만약 테스트 코드를 작성하지 않았더라면, 기능에 대한 테스트를 매번 알람을 울리면서 테스트 하였거나 혹은 엣지 케이스에 대한 경우를 뒤늦게 확인했을것 같아요.

결론

기존에는 테스트 코드 작성이 더 오래 걸려서 비효율적이다라는 막연한 생각으로 테스트 코드를 멀리하게 되었는데요.

하지만 실제로 테스트 코드를 작성해보면서 테스트 코드가 중요한 이유에 대하 실감하게 되었습니다.

  • 리팩토링 지원 : 기존 기능을 리팩터링 할때 자신감이 생김
  • 문서화의 역할 : 출석체크 완료 기준이 7일에서 5일로 변경되었구나, 테스트 코드를 보며 알 수 있음
  • 버그 감지 : 테스트 코드를 작성하며 예외 케이스에 대한 고민을 빨리 체크 할 수 있음
  • 테스트 가능한 구조에 대한 고민 : 테스트 코드를 작성하면서 dependancy 가 덜한 코드에 구조에 대한 고민을 하게 됨

빠를 수록 돌아가라는 말이 있는데요, 테스트 코드 작성하면서 그런 경험을 할 수 있었고, 변화에 유연한 설계가 되어가고 있다고 느낄 수 있었습니다.

한번에 완벽한 테스트 코드를 작성하려고 테스트 코드를 작성하지 않는것 보다, 테스트 코드를 작성하면서 테스트 코드에 대한 고민과 더 좋은 테스트 코드를 작성하기 위해 노력하는것이 더 중요하다

한마디로 완벽한 첫 삽을 위해 시도 하지 않는 것 보다, 지속적인 노력이 더 중요하다는 사실입니다.

테스트 코드에 대한 도입에 대한 고민을 하고 계시다면, 어떤 점이 고민인지 정의 해보시고 실제로 작게나마 테스트 코드에 대한 작성을 해보시면서 그 고민이 정말인지 확인해보시는것을 추천드립니다.

https://team.alar.my/recruit/?utm_campaign=branding&utm_source=medium&utm_content=banner

--

--