개요
- labmda with receiver 를 이용한 DSL
- invoke convention
코틀린의 이러한 특성을 이용하여 DSL을 어떻게 설계해나가는지 살펴보자
API에서 DSL로
API 가 깔끔하다?
- 적절한 이름을 통한 명확하게 이해할 수 있음?
- 코드의 간결성. 불필요한 구문, 번잡한 코드가 없어야 함 ( 11장에서 주로 볼 부분 )
- extension function
- infix calls
- operator overloading
- convention
- lambda with receiver
Kotest - 코틀린 테스트
코틀린 테스트는 기존 Junit의 then 절의 가독성을 DSL과 함께 매우 높여주었다. DSL에서 중위호출을 어떻게 활용하는지 살펴보자
https://github.com/kotest/kotest
class KotestSample {
@Test
fun shouldKeyword() {
val str: String = "start"
str should startWith("sta") // infix call
str should start with "sta" // = str.should(start).with("sta")
}
}
- 첫 should 함수는 kotest에서 제공해주는 것이다
should를 kotest에서 어떻게 구현했는지 살펴보자. should, startWith, neverNullMatcher 이 3가지를 살펴보다보니 generic, object, lambda 등 모든 내용을 이해하게 된다.
// kotest 구현 살펴보기
infix fun <T> T.should(matcher: Matcher<T>) {
AssertionCounter.inc()
val result = matcher.test(this)
if (!result.passed()) {
ErrorCollector.collectOrThrow(failure(result.failureMessage()))
}
}
// StringMatcherKt.class
fun startWith(prefix: String) = neverNullMatcher<String> { value ->
val ok = value.startsWith(prefix)
var msg = "${value.show().value} should start with ${prefix.show().value}"
val notmsg = "${value.show().value} should not start with ${prefix.show().value}"
if (!ok) {
for (k in 0 until min(value.length, prefix.length)) {
if (value[k] != prefix[k]) {
msg = "$msg (diverged at index $k)"
break
}
}
}
MatcherResult(ok, msg, notmsg)
}
// MatcherResult.kt
operator fun invoke(
passed: Boolean,
failureMessage: String,
negatedFailureMessage: String
) = object : MatcherResult {
override fun passed(): Boolean = passed
override fun failureMessage(): String = failureMessage
override fun negatedFailureMessage(): String = negatedFailureMessage
}
// Matcher.kt
fun <T : Any> neverNullMatcher(test: (T) -> MatcherResult): Matcher<T?> {
return object : NeverNullMatcher<T>() {
override fun testNotNull(value: T): MatcherResult {
return test(value)
}
}
}
- 두번째 should 함수는 우리가 추가 구현한 것이다
object start
infix fun String.should(x: start): StartWrapper = StartWrapper(this)
class StartWrapper(val value: String) {
infix fun with(prefix: String) =
if (!value.startsWith(prefix))
throw AssertionError("error")
else Unit
}
'Kotlin' 카테고리의 다른 글
Kotlin Effectvie 번역 - 2. Readability (0) | 2020.04.20 |
---|---|
Kotlin in Action - 1장. 코틀린이란? 왜 필요한가? (0) | 2020.04.04 |
Kotlin In Action 7장. 연산자 오버로딩과 기타 관례 (0) | 2020.04.02 |
Kotlin In Action - 5장. 람다로 프로그래밍 (0) | 2020.03.29 |
Kotlin In Action - 3장.함수 정의와 호출 (0) | 2020.03.29 |