AAA
Arrange,Act,Assert
test
AAA
AAA(Arrange, Act, Assert) 의 목적은 테스트 케이스를 구조화 하여 테스트의 이해와 가독성을 높이는 것이다.
- Arrange describes whatever setup is needed
- Act describes the subject's behavior that's under test (and typically only describes a single line needed to invoke that behavior)
- Assert describes the verification that the subject's behavior had the desired effect by evaluating its return value or measuring a side-effect (with a spy or mock)
Testing Style 중 하나를 선택하여 테스트 코드를 작성하곤 한다. 테스트 코드를 작성하다 보면, 함수 내에서 arrange,act,assert or given,when,then 의 주석을 작성하거나 혹은 작성이 되어있지 않아 구조를 파악하는데 어려움을 겪을 때가 있다. 이때, AAA DSL 을 활용하면 주석이 필요 없으며, arrange 이후 act 과정에서 필요한 변수가 무엇인지 파악하기 쉽다는 장점이 있다.
AAA DSL 은 kotest original 인 StringSpec 또는 Scala 에서 영감을 받은 FunSpec 과 궁합이 좋다.
AAA DSL:
fun <T> arrange(block: () -> T): T {
return block()
}
fun <T> act(block: () -> T): T {
return block()
}
fun assert(block: () -> Unit) {
block()
}
Examples:
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
class UserServiceTest : StringSpec({
"should register a new user successfully" {
val userRepository = mockk<UserRepository>()
val emailValidator = mockk<EmailValidator>()
val userService = UserService(userRepository, emailValidator)
arrange {
val newUser = User(email = "test@example.com", name = "Test User")
every { userRepository.existsByEmail("test@example.com") } returns false
every { emailValidator.isValid("test@example.com") } returns true
every { userRepository.save(any()) } returns newUser
newUser
}.let { newUser ->
act {
userService.registerUser(newUser)
}
assert {
verify(exactly = 1) { userRepository.save(newUser) }
verify(exactly = 1) { emailValidator.isValid("test@example.com") }
newUser.id shouldBe 1 // assuming save() sets the ID to 1
}
}
}
"should not register if email is invalid" {
val userRepository = mockk<UserRepository>()
val emailValidator = mockk<EmailValidator>()
val userService = UserService(userRepository, emailValidator)
arrange {
val newUser = User(email = "invalid-email", name = "Test User")
every { emailValidator.isValid("invalid-email") } returns false
newUser
}.let { newUser ->
act {
shouldThrow<InvalidEmailException> {
userService.registerUser(newUser)
}
}
assert {
verify(exactly = 0) { userRepository.save(any<User>()) }
}
}
}
"should not register existing user" {
val userRepository = mockk<UserRepository>()
val emailValidator = mockk<EmailValidator>()
val userService = UserService(userRepository, emailValidator)
arrange {
val existingUser = User(email = "test@example.com", name = "Existing User")
every { userRepository.existsByEmail("test@example.com") } returns true
existingUser
}.let { existingUser ->
act {
shouldThrow<UserAlreadyExistsException> {
userService.registerUser(existingUser)
}
}
assert {
verify(exactly = 0) { userRepository.save(any<User>()) }
}
}
}
})