# ETC

* 테스트
* 입력/출력
* 그 밖의 코틀린 기능
* 스프링 프레임워크
* 코루틴과 구조적 동시성

## 테스트

### 테스트 클래스 수명주기 설정

> Junit 5의 테스트 수명주기를 기본값인 테스트 함수당 한 번 대신
>
> 클래스 인스턴스당 한 번씩 인스턴스화 하고 싶다면,
>
> `@TestInstance` 애노테이션이나 junit-platform.properties 파일의 `lifecycle.default` 속성을 설정하자.

Junit 5는 클래스에서 `@TestInstance` 애노테이션을 사용해 수명주기를 명시할 수 있다.

* 테스트 인스턴스의 수명주기를 PER\_CLASS로 설정하면 테스트 함수의 양과 상관 없이 테스트 인스턴스가 딱 하나만 생성

```kotlin
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // 모든 테스트의 테스트 클래스 인스턴스가 오직 하나
class JUnit5ListTests {
    // 인스턴스화와 원소 초기화가 오직 한번
    private val strings = listOf("this", "is", "a", "list", "of", "strings")

    // 테스트 할 때마다 실행 전 다시 초기화
    private lateinit var modifiable : MutableList<Int>

    @BeforeEach
    fun setUp() {
        // 테스트 할 때마다 실행 전 다시 초기화
        modifiable = mutableListOf(3, 1, 4, 1, 5)
        println("Before: $modifiable")
    }

    @AfterEach
    fun finish() {
        println("After: $modifiable")
    }

    @Test
    fun addElementsToList() {
        modifiable.add(9)
        modifiable.add(2)
        modifiable.add(6)
        modifiable.add(5)
        assertEquals(9, modifiable.size)
    }

    @Test
    fun size() {
        println("Testing size")
        assertEquals(6, strings.size)
        assertEquals(5, modifiable.size)
    }

    @Test
    fun accessBeyondEndThrowsException() {
        println("Testing out of bounds exception")
        assertThrows<ArrayIndexOutOfBoundsException> { strings[99] }
        assertEquals(6, strings.size)
    }
}
```

`@TestInstance`를 각 테스트 클래스에 반복하는 대신 모든 테스트의 수명주기를 properties 파일에 설정 가능

* 클래스 패스(src/test/resource)에 junit-platform.properties 파일을 생성하여 테스트 수명주기 설정 가능

```properties
junit.jupiter.testinstance.lifecycle.default = per_class
```

***

### 테스트에 데이터 클래스 사용하기

> 원하는 모든 속성을 캡슐화하는 데이터 클래스 생성하기

`assertAll` 함수의 장점은 Executable 인스턴스의 단언이 1개 이상 실패하더라도 Executable 인스턴스를 모두 실행

코틀린 data 클래스에는 이미 equals 메소드가 올바르게 구현되어 있으므로 테스트 프로세스 간소화

* 단 하나의 assertion이 모든 속성을 테스트

assertj에서 제공하는 contains 메소드를 이용해 컬렉션의 모든 원소를 확인할 수 있다.

```kotlin
data class Book(
    val isbn: String,
    val title: String,
    val author: String,
    val published: LocalDate
)

...

@Test
fun `check all elements in list`() {
    val badBook = books[2].copy(title = "Modern Java Cookbook")
    val found = arrayOf(books[2], books[0], books[1]) // Add badBook to see the assertion fail
    val expected = books
    assertThat(found).contains(*expected)
}
```

***

### 기본 인자와 함께 도움 함수 사용하기

> 각 인자에 기본값을 제공하도록 클래스를 수정하지 말고
>
> 기본값을 생성하는 팩토리 함수를 추가하자.

최상위 레벨의 유틸리티 클래스에 팩토리 함수를 위치시키면 테스트에서 팩토리 함수를 재활용할 수 있다.

```kotlin
fun createBook(
    isbn: String = "149197317X",
    title: String = "Modern Java Recipes",
    author: String = "Ken Kousen",
    published: LocalDate = LocalDate.parse("2017-08-26")
) = Book(isbn, title, author, published)

val mjr = createBook()

...

data class MultiAuthorBook(
    val isbn: String,
    val title: String,
    val authors: List<String>,
    val published: LocalDate
)

fun createMultiAuthorBook(
    isbn: String = "9781617293290",
    title: String = "Kotlin in Action",
    authors: List<String> = listOf("Dimitry Jeremov", "Svetlana Isakova"),
    published: LocalDate = LocalDate.parse("2017-08-26")
) = MultiAuthorBook(isbn, title, authors, published)
```

***

### 여러 데이터에 JUnit 5 테스트 반복하기

> Junit 5에는 쉼표로 구분된 값(CSV)과 팩토리 메소드가 포함된 옵션과 함께 데이터 소스를 명시할 수 있는 파라미터화된 테스트가 있다.

👉🏻 **CSV 데이터를 사용해 파리미터화된 테스트 수행**

```kotlin
@ParameterizedTest
@CsvSource("1, 1", "2, 1", "3, 2",
    "4, 3", "5, 5", "6, 8", "7, 13",
    "8, 21", "9, 34", "10, 55")
fun `first 10 Fibonacci numbers (csv)`(n: Int, fib: Int) =
    assertThat(fibonacci(n)).isEqualTo(fib)
```

Junit 5에서는 팩토리 메소드를 사용해 테스트 데이터를 생성할 수 있다.

👉🏻 **파라미터 소스로서 인스턴스 함수에 접근**

```kotlin
private fun fibnumbers() = listOf(
    Arguments.of(1, 1), Arguments.of(2, 1),
    Arguments.of(3, 2), Arguments.of(4, 3),
    Arguments.of(5, 5), Arguments.of(6, 8),
    Arguments.of(7, 13), Arguments.of(8, 21),
    Arguments.of(9, 34), Arguments.of(10, 55))
    
@ParameterizedTest(name = "fibonacci({0}) == {1}")
    @MethodSource("fibnumbers")
    fun `first 10 Fibonacci numbers (instance method)`(n: Int, fib: Int) =
        assertThat(fibonacci(n)).isEqualTo(fib)
```

테스트 수명주기가 기본 옵션인 Lifecycle.PER\_METHOD 라면 테스트 데이터 소스 함수를 동반 객체 안에 위치시켜야 한다.

```kotlin
companion object {
    // needed if parameterized test done with Lifecycle.PER_METHOD
    @JvmStatic
    fun fibs() = listOf(
        Arguments.of(1, 1), Arguments.of(2, 1),
        Arguments.of(3, 2), Arguments.of(4, 3),
        Arguments.of(5, 5), Arguments.of(6, 8),
        Arguments.of(7, 13), Arguments.of(8, 21),
        Arguments.of(9, 34), Arguments.of(10, 55))
}

@ParameterizedTest(name = "fibonacci({0}) == {1}")
@MethodSource("fibs")
fun `first 10 Fibonacci numbers (companion method)`(n: Int, fib: Int) =
    assertThat(fibonacci(n)).isEqualTo(fib)
```

***

### 파라미터화된 테스트에 data 클래스 사용하기

> 입력 값과 예상 값을 감싸는 data 클래스를 만들고
>
> 만든 data 클래스 기반의 테스트 데이터를 생성하는 함수를 테스트 메소드 소스로서 사용하자.

```kotlin
@JvmOverloads
tailrec fun fibonacci(n: Int, a: Int = 0, b: Int = 1): Int =
when (n) {
    0 -> a
    1 -> b
    else -> fibonacci(n - 1, b, a + b)
}
```

👉🏻 **입력과 예상되는 출력을 담는 데이터 클래스 정의**

* 데이터 클래스는 이미 toString이 재정의되어 있으므로
* 입력과 출력 쌍을 나타내는 data 클래스를 인스턴스화하는 파라미터화된 테스트를 사용하는 테스트 메소드를 작성할 수 있다.

```kotlin
data class FibonacciTestData(val number: Int, val expected: Int)

...

@ParameterizedTest
@MethodSource("fibonacciTestData")
fun `check fibonacci using data class`(data: FibonacciTestData) {
    assertThat(fibonacci(data.number)).isEqualTo(data.expected)
}

private fun fibonacciTestData() = Stream.of(
    FibonacciTestData(number = 1, expected = 1),
    FibonacciTestData(number = 2, expected = 1),
    FibonacciTestData(number = 3, expected = 2),
    FibonacciTestData(number = 4, expected = 3),
    FibonacciTestData(number = 5, expected = 5),
    FibonacciTestData(number = 6, expected = 8),
    FibonacciTestData(number = 7, expected = 13)
)
```

## 입력/출력

### use로 리소스 관리하기

> 확실하게 리소스를 닫고 싶지만 코틀린은 자바의 try-with-resource 구문을 지원하지 않는다.
>
> kotlin.io 패키지의 use 또는 java.io.Reader의 `useLines` 확장 함수를 사용하자.

**File.useLines**

* useLines의 첫 번째 선택적 인자는 문자 집합이며 기본값은 UTF-8
* 두 번째 인자는 파일의 줄을 나타내는 Sequence를 제네릭 인자 T로 매핑하는 람다

```kotlin
public inline fun <T> File.useLines(
    charset: Charset = Charsets.UTF_8, 
    block: (Sequence<String>) -> T
): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    // BufferedReader를 생성하고 BufferedReader의 use 함수에 처리를 위임
    return bufferedReader(charset).use { block(it.lineSequence()) }
}

...

fun get10LongestWordsInDictionary() =
    File("/usr/share/dict/words").useLines { line ->
        line.filter { it.length > 20 }
            .sortedByDescending(String::length)
            .take(10)
            .toList()
    }

@Test @EnabledOnOs(OS.MAC)
internal fun `10 longest words in dictionary`() {
    get10LongestWordsInDictionary().forEach { word -> println("$word (${word.length})") }
}
```

**use 함수의 시그니처**

```kotlin
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R
```

***

### 파일에 기록하기

> File 클래스의 확장 함수에는 일반적인 **자바 입출력 메소드**뿐만 아니라 **출력 스트림**과 **Writer를 리턴하는 확장 함수**가 있다.

`forEachLine` 함수를 사용해서 파일을 순회할 수 있다.

* 파일이 매우 크지 않다면 File의 `readLines`를 호출해 모든 줄이 담긴 컬렉션을 획득할 수도 있다.
* `useLines` 함수를 사용해 파일의 줄마다 호출되는 함수를 제공할 수 있다.
* 파일의 크기가 작다면 `readText` 또는 `readBytes`를 사용해 전체 내용을 각각 문자열이나 바이트 배열로 읽어올 수 있다.

파일에 존재하는 내용을 모두 교체하고 싶다면 `writeText` 함수를 사용

```kotlin
File("myfile.txt).writeText("My data")
```

File 클래스에는 파일에 데이터를 추가하는 `appendText`라는 확장 함수가 있다.

* `writeText`와 `appendText` 함수는 `writeBytes`와 `appendBytes`에 기록 작업을 위임힌다.
* `writeBytes`와 `appendBytes`는 기록이 끝나면 `use` 함수를 사용해 파일을 확실히 닫는다.

개발자는 `OutputStreamWriter`와 `BufferedWriter`를 리턴하는 `writer`(printWriter)와 `bufferedWriter` 함수를 사용할 수도 있다.

```kotlin
File(fileName).printWriter().use { writer ->
    writer.println(data) }
```

## 그 밖의 코틀린 기능

### 코틀린 버전 알아내기

> 코트를 작성해 현재 사용 중인 코틀린 버전을 알려면,
>
> KotlinVersion 클래스 동반 객체의 CURRENT 속성을 사용하자.

👉🏻 **코틀린 버전 비교하기**

```kotlin
@Test
internal fun `comparison of KotlinVersion instances work`() {
    val v12 = KotlinVersion(major = 1, minor = 2)
    val v1341 = KotlinVersion(1, 3, 41)
    assertAll(
        { assertTrue(v12 < KotlinVersion.CURRENT) },
        { assertTrue(v1341 <= KotlinVersion.CURRENT) },
        { assertEquals(KotlinVersion(1, 3, 41),
            KotlinVersion(major = 1, minor = 3, patch = 41)) }
    )
}

@Test
internal fun `current version is at least 1_3`() {
    assertTrue(KotlinVersion.CURRENT.isAtLeast(major = 1, minor = 3))
    assertTrue(KotlinVersion.CURRENT.isAtLeast(major = 1, minor = 3, patch = 40))
}
```

***

### 반복적으로 람다 실행하기

> 주어진 람다 식을 여러 번 실행하고 싶다면 코틀린 내장 repeat 함수를 사용하자.

```kotlin
/**
 * times: 반복할 횟수
 * action: 실행할 (Int) -> Unit 형식의 함수
 */
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
    contract { callsInPlace(action) }

    for (index in 0 until times) {
        action(index)
    }
}

...

/*
 * Counting: 0
 * Counting: 1
 * Counting: 2
 * Counting: 3
 * Counting: 4
 */
repeat(5) {
    println("Counting: $it")
}
```

***

### 완벽한 when 강제하기

👉🏻 **컴파일러에게 else 절을 요구하도록 강요**

```kotlin
val <T> T.exhaustive: T
    get() = this

@Test
fun `test`() {
    fun printMod3Exhaustive(n: Int) {
        when (n % 3) {
            0 -> println("$n % 3 == 0")
            1 -> println("$n % 3 == 1")
            2 -> println("$n % 3 == 2")
            else -> println("Houston, we have a problem...")
        }.exhaustive
    }

    (1..10).forEach { printMod3Exhaustive(it) }
}
```

***

### 실행 가능한 클래스 만들기

> 클래스에서 단일 함수를 간단하게 호출하고 싶다면,
>
> 함수를 호출할 클래스에서 `invoke` 연산자 함수를 재정의하자.

```kotlin
data class Assignment(
    val name: String,
    val craft: String
)

data class AstroResult(
    val message: String,
    val number: Int,
    val people: List<Assignment>
)

...

class AstroRequest {
    companion object {
        private const val ASTRO_URL = "http://api.open-notify.org/astros.json"
    }

    // invoke 연산자 함수를 통해 실행 가능한 클래스로 사용
    operator fun invoke(): AstroResult =
        Gson().fromJson(URL(ASTRO_URL).readText(), AstroResult::class.java)
}

...

@Test
fun `get people in space`() {
    // val request = AstroRequest() // AstroRequest 클래스 인스턴스화
    // val result = request() // 함수처럼 클래스를 호출(invoke 호출)
    var result = AstroRequest()()
    assertAll(
        { assertEquals("success", result.message) },
        { assertTrue { result.number >= 0 } },
        { assertEquals(result.number, result.people.size) }
    )
    }
```

`invoke` 연산자 함수를 제공하고 클래스 레퍼런스에 괄호를 추가하면 클래스 인스턴스를 바로 실행할 수 있다.

* 원한다면 필요한 인자를 추가한 invoke 함수 중복도 추가할 수 있다.

***

### 경과 시간 측정하기

> 코드 블록이 실행되는 데 걸린 시간을 알고 싶다면, measureTimeMillis 또는 measureNanoTime 함수를 사용하자.

**measureTimeMillis 함수의 구현**

```kotlin
public inline fun measureTimeMillis(block: () -> Unit): Long {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    val start = System.currentTimeMillis()
    block()
    return System.currentTimeMillis() - start
}
```

👉🏻 **코드 블록의 경과 시간 측정하기**

```Kotlin
fun doubleIt(x: Int): Int {
    Thread.sleep(100L)
    println("doubling $x with on thread ${Thread.currentThread().name}")
    return x * 2
}

/**
 * This machine has 10 processors
 * doubling 1 with on thread main
 * ...
 * doubling 8 with on thread main
 * Sequential stream took 845ms
 * doubling 6 with on thread main
 * doubling 8 with on thread ForkJoinPool.commonPool-worker-2
 * ...
 * doubling 1 with on thread ForkJoinPool.commonPool-worker-6
 * Parallel stream took 106ms
 */
fun main() {
    println("This machine has ${Runtime.getRuntime().availableProcessors()} processors")

    var time = measureTimeMillis {
        IntStream.rangeClosed(1, 8)
            .map { doubleIt(it) }
            .sum()
    }
    println("Sequential stream took ${time}ms")

    time = measureTimeMillis {
        IntStream.rangeClosed(1, 8)
            .parallel()
            .map { doubleIt(it) }
            .sum()
    }
    println("Parallel stream took ${time}ms")

}
```

{% hint style="info" %}
더 정확한 성능 측정을 원한다면 오픈 JDK의 자바 마이크로벤치마크 도구[JMH, Java Microbenchmark Harness](https://github.com/openjdk/jmh)프로젝트를 사용하자.
{% endhint %}

***

### 스레드 시작하기

**스레드 확장 함수의 시그니처**

```kotlin
public fun thread(
    start: Boolean = true,
    isDaemon: Boolean = false,
    contextClassLoader: ClassLoader? = null,
    name: String? = null,
    priority: Int = -1,
    block: () -> Unit
): Thread 
```

👉🏻 **다수의 스레드를 임의의 간격으로 시작하기**

```kotlin
/**
 * Thread-0 for 0 after 2ms
 * Thread-1 for 1 after 345ms
 * Thread-2 for 2 after 370ms
 * Thread-3 for 3 after 153ms
 * Thread-4 for 4 after 138ms
 * Thread-5 for 5 after 879ms
 */
(0..5).forEach { n ->
    val sleepTime = Random.nextLong(range = 0..1000L)
    thread {
        Thread.sleep(sleepTime)
        println("${Thread.currentThread().name} for $n after ${sleepTime}ms")
    }.join()
}
```

Thread 확장 함수는 자신의 본문에서 생성한 스레드를 리턴하므로, 스레드의 join 메소드를 사용해서 모든 스레드를 순차적으로 호출하게 만들 수 있다.

```kotlin
(0..5).forEach { n ->
    thread {
    // ...
    }.join() // 리턴된 스레드의 invoke 메소드를 사용할 수 있다.
}
```

***

### TODO로 완성 강제하기

👉🏻 **TODO 함수의 구현**

* 효율성을 이유로 소스는 인라인되어 있고, 함수가 호출될 때 NotImplementedError 발생

```kotlin
public inline fun TODO(reason: String): Nothing = 
    throw NotImplementedError("An operation is not implemented: $reason")

...

fun main() {
    TODO(reason = "none, really")
}
```

***

### Random의 무작위 동작 이해하기

👉🏻 **nextInt 함수**

```kotlin
@Test
internal fun `nextInt with no args gives any Int`() {
    val value = Random.nextInt()
    assertTrue(value in Int.MIN_VALUE..Int.MAX_VALUE)
}

@Test
internal fun `nextInt with a range gives value between 0 and limit`() {
    val value = Random.nextInt(10)
    assertTrue(value in 0..10)
}

@Test
internal fun `nextInt with min and max gives value between them`() {
    val value = Random.nextInt(5, 10)
    assertTrue(value in 5..10)
}

@Test
internal fun `nextInt with range returns value in range`() {
    val value = Random.nextInt(7..12)
    assertTrue(value in 7..12)
}
```

👉🏻 **시드 값과 함께 난수 생성기 사용하기**

```kotlin
@Test
internal fun `Random function produces a seeded generator`() {
    val r1 = Random(12345)
    val nums1 = (1..10).map { r1.nextInt() }

    val r2 = Random(12345)
    val nums2 = (1..10).map { r2.nextInt() }

    // println(nums1)
    assertEquals(nums1, nums2)
}
```

## 스프링 프레임워크

코틀린으로 스프링 애플리케이션을 작성할 때 사용할 수 있는 몇 가지 기술들

### 확장을 위해 스프링 관리 빈 클래스 오픈하기

> 확장을 위해 자동으로 필요한 스프링 관리 클래스를 열어주는 코틀린 스프링 플러그인을 빌드 파일에 추가하자

코틀린은 기본적으로 **정적으로 결합**한다.

* 클래스가 open 키워드를 사용해 확장을 위한 열림으로 표시되지 않으면 메소드 **재정의 또는 클래스 확장이 불가능**하다.
* 코틀린은 이 문제를 `all-open` 플러그인으로 해결한다.
* `all-open` 플러그인은 클래스와 클래스에 포함된 함수에 명시적으로 open 키워드를 추가하지 않고 명시적인 open 애노테이션으로 클래스를 설정한다.

`kotlin-spring` 플러그인은 아래 애노테이션으로 클래스를 열도록 설정되어 있다.

* @Component
* @Async
* @Transactional
* @Cacheable
* @SpringBootTest

```groovy
implementation("org.jetbrains.kotlin.plugin.spring:org.jetbrains.kotlin.plugin.spring.gradle.plugin:2.0.21")
```

{% hint style="info" %}
`kotlin-spring`이 제공하는 것보다 더 많이 필요하다면 all-open 플러그인도 추가할 수 있지만 거의 필요 없다.
{% endhint %}

***

### 코틀린 data 클래스로 퍼시스턴스 구현하기

> data 클래스로 JPA를 사용하고 싶다면 kotlin-jpa 플러그인을 추가하자.

JPA 관점에서 data 클래스는 두 가지 문제가 있다.

1️⃣ JPA는 모든 속성에 기본값을 제공하지 않는 이상 기본 생성자가 필수지만 data 클래스는 기본 생성자가 없다.

* no-arg 플러그인은 인자가 없는 생성자를 추가할 클래스를 선택할 수 있고
* 기본 생성자 추가를 호출하는 애너테이션을 정의할 수도 있다.

kotlin-jpa 플러그인은 no-arg 플러그인을 기반으로 만들어 졌고, 아래 애노테이션으로 자동 표시된 클래스에 기본 생성자를 추가한다.

* `@Entity`
* `@Embeddable`
* `@MappedSuperclass`

```kotlin
kotlin("plugin.jpa") version "1.2.71"
```

2️⃣ val 속성과 함께 data 클래스를 생성하면 불변 객체가 생성되는데, JPA는 불변 객체와 더불어 잘 동작하도록 설계되지 않았다.

* 엔티티로 사용하고 싶은 코틀린 클래스에 (data 클래스 대신) 필드 값을 변경할 수 있게 속성에 var 타입을 사용하는 단순 클래스 사용을 추천

👉🏻 **데이터베이스 테이블로 매핑되는 코틀린 클래스**

```kotlin
@Entity
class Article(
    var title: String,
    var headline: String,
    var content: String,
    @ManyToOne var author: User,
    var slug: String = title.toSlug(),
    var addedAt: LocalDateTime = LocalDateTime.now(),
    @Id @GeneratedValue var id: Long? = null)

@Entity
class User(
    var login: String,
    var firstname: String,
    var lastname: String,
    var description: String? = null,
    @Id @GeneratedValue var id: Long? = null)
```

ref. [Building web applications with Spring Boot and Kotlin](https://spring.io/guides/tutorials/spring-boot-kotlin)

***

### 의존성 주입하기

> 코틀린 스프링은 생성자 주입을 제공하지만 필드 주입에는 lateinit var 구조를 사용해야 한다.
>
> 선택적인 빈은 널 허용 타입으로 선언한다.

클래스에서 생성자가 하나뿐이라면 스프링이 자동으로 클래스의 유일한 생성자에 모든 인자를 자동으로 오토와이어링하기 때문에 @Autowired 애노테이션을 사용할 필요가 없다.

👉🏻 **스프링으로 의존성 오토와이어링하기**

```kotlin
/** 단일 생성자를 갖는 클래스 */
@RestController
class GreetingController(val service: GreetingService) { /* ... */ }

/** 명시적으로 오토와이어링 */
@RestController
class GreetingController(@Autowired val service: GreetingService) { /* ... */ }

/** 오토와이어링 생성자 호출. 주로 다수의 의존성을 갖는 클래스 */
@RestController
class GreetingController @Autowired constructor(val service: GreetingService) { 
    // ...
}

/** 필드 주입(비추천하지만 유용할 수 있다) */
@RestController
class GreetingController {
    @Autowired 
    lateinit var service: GreetingService

    // ...
}
```

클래스의 속성이 필수가 아니라면 해당 속성을 널 허용 타입으로 선언할 수 있다.

👉🏻 **선택 가능한 파라미터를 갖는 컨트롤러 함수**

```kotlin
@GetMapping("/hello")
fun greetUser(@RequestParam name: String?) = 
    Greeting(service.sayHello(name ?: "World"))

...

@DataJpaTest
class RepositoriesTests @Autowired constructor (
    val entityManager: TestEntityManager,
    val userRepository: UserRepository,
    val articleRepository: ArticleRepository) {
    // ...
}

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTests(@Autowired val restTemplate: TestRestTemplate) {
    // ...
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jihunparkme.gitbook.io/docs/book/kotlin-cookbook/kotlin-etc.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
