ETC

  • ํ…Œ์ŠคํŠธ

  • ์ž…๋ ฅ/์ถœ๋ ฅ

  • ๊ทธ ๋ฐ–์˜ ์ฝ”ํ‹€๋ฆฐ ๊ธฐ๋Šฅ

  • ์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ

  • ์ฝ”๋ฃจํ‹ด๊ณผ ๊ตฌ์กฐ์  ๋™์‹œ์„ฑ

ํ…Œ์ŠคํŠธ

ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค ์ˆ˜๋ช…์ฃผ๊ธฐ ์„ค์ •

Junit 5์˜ ํ…Œ์ŠคํŠธ ์ˆ˜๋ช…์ฃผ๊ธฐ๋ฅผ ๊ธฐ๋ณธ๊ฐ’์ธ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋‹น ํ•œ ๋ฒˆ ๋Œ€์‹ 

ํด๋ž˜์Šค ์ธ์Šคํ„ด์Šค๋‹น ํ•œ ๋ฒˆ์”ฉ ์ธ์Šคํ„ด์Šคํ™” ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด,

@TestInstance ์• ๋…ธํ…Œ์ด์…˜์ด๋‚˜ junit-platform.properties ํŒŒ์ผ์˜ lifecycle.default ์†์„ฑ์„ ์„ค์ •ํ•˜์ž.

Junit 5๋Š” ํด๋ž˜์Šค์—์„œ @TestInstance ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด ์ˆ˜๋ช…์ฃผ๊ธฐ๋ฅผ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ํ…Œ์ŠคํŠธ ์ธ์Šคํ„ด์Šค์˜ ์ˆ˜๋ช…์ฃผ๊ธฐ๋ฅผ PER_CLASS๋กœ ์„ค์ •ํ•˜๋ฉด ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜์˜ ์–‘๊ณผ ์ƒ๊ด€ ์—†์ด ํ…Œ์ŠคํŠธ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋”ฑ ํ•˜๋‚˜๋งŒ ์ƒ์„ฑ

@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 ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์ˆ˜๋ช…์ฃผ๊ธฐ ์„ค์ • ๊ฐ€๋Šฅ

junit.jupiter.testinstance.lifecycle.default = per_class

ํ…Œ์ŠคํŠธ์— ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค ์‚ฌ์šฉํ•˜๊ธฐ

์›ํ•˜๋Š” ๋ชจ๋“  ์†์„ฑ์„ ์บก์Šํ™”ํ•˜๋Š” ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค ์ƒ์„ฑํ•˜๊ธฐ

assertAll ํ•จ์ˆ˜์˜ ์žฅ์ ์€ Executable ์ธ์Šคํ„ด์Šค์˜ ๋‹จ์–ธ์ด 1๊ฐœ ์ด์ƒ ์‹คํŒจํ•˜๋”๋ผ๋„ Executable ์ธ์Šคํ„ด์Šค๋ฅผ ๋ชจ๋‘ ์‹คํ–‰

์ฝ”ํ‹€๋ฆฐ data ํด๋ž˜์Šค์—๋Š” ์ด๋ฏธ equals ๋ฉ”์†Œ๋“œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌํ˜„๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ํ…Œ์ŠคํŠธ ํ”„๋กœ์„ธ์Šค ๊ฐ„์†Œํ™”

  • ๋‹จ ํ•˜๋‚˜์˜ assertion์ด ๋ชจ๋“  ์†์„ฑ์„ ํ…Œ์ŠคํŠธ

assertj์—์„œ ์ œ๊ณตํ•˜๋Š” contains ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•ด ์ปฌ๋ ‰์…˜์˜ ๋ชจ๋“  ์›์†Œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

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)
}

๊ธฐ๋ณธ ์ธ์ž์™€ ํ•จ๊ป˜ ๋„์›€ ํ•จ์ˆ˜ ์‚ฌ์šฉํ•˜๊ธฐ

๊ฐ ์ธ์ž์— ๊ธฐ๋ณธ๊ฐ’์„ ์ œ๊ณตํ•˜๋„๋ก ํด๋ž˜์Šค๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ๋ง๊ณ 

๊ธฐ๋ณธ๊ฐ’์„ ์ƒ์„ฑํ•˜๋Š” ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜์ž.

์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค์— ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜๋ฅผ ์œ„์น˜์‹œํ‚ค๋ฉด ํ…Œ์ŠคํŠธ์—์„œ ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜๋ฅผ ์žฌํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

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 ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด ํŒŒ๋ฆฌ๋ฏธํ„ฐํ™”๋œ ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰

@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์—์„œ๋Š” ํŒฉํ† ๋ฆฌ ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ‘‰๐Ÿป ํŒŒ๋ผ๋ฏธํ„ฐ ์†Œ์Šค๋กœ์„œ ์ธ์Šคํ„ด์Šค ํ•จ์ˆ˜์— ์ ‘๊ทผ

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 ๋ผ๋ฉด ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์†Œ์Šค ํ•จ์ˆ˜๋ฅผ ๋™๋ฐ˜ ๊ฐ์ฒด ์•ˆ์— ์œ„์น˜์‹œ์ผœ์•ผ ํ•œ๋‹ค.

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 ํด๋ž˜์Šค ๊ธฐ๋ฐ˜์˜ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ํ…Œ์ŠคํŠธ ๋ฉ”์†Œ๋“œ ์†Œ์Šค๋กœ์„œ ์‚ฌ์šฉํ•˜์ž.

@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 ํด๋ž˜์Šค๋ฅผ ์ธ์Šคํ„ด์Šคํ™”ํ•˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐํ™”๋œ ํ…Œ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ…Œ์ŠคํŠธ ๋ฉ”์†Œ๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

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๋กœ ๋งคํ•‘ํ•˜๋Š” ๋žŒ๋‹ค

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 ํ•จ์ˆ˜์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜

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

ํŒŒ์ผ์— ๊ธฐ๋กํ•˜๊ธฐ

File ํด๋ž˜์Šค์˜ ํ™•์žฅ ํ•จ์ˆ˜์—๋Š” ์ผ๋ฐ˜์ ์ธ ์ž๋ฐ” ์ž…์ถœ๋ ฅ ๋ฉ”์†Œ๋“œ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ ์ŠคํŠธ๋ฆผ๊ณผ Writer๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ํ™•์žฅ ํ•จ์ˆ˜๊ฐ€ ์žˆ๋‹ค.

forEachLine ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํŒŒ์ผ์„ ์ˆœํšŒํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ํŒŒ์ผ์ด ๋งค์šฐ ํฌ์ง€ ์•Š๋‹ค๋ฉด File์˜ readLines๋ฅผ ํ˜ธ์ถœํ•ด ๋ชจ๋“  ์ค„์ด ๋‹ด๊ธด ์ปฌ๋ ‰์…˜์„ ํš๋“ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

  • useLines ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ํŒŒ์ผ์˜ ์ค„๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ํŒŒ์ผ์˜ ํฌ๊ธฐ๊ฐ€ ์ž‘๋‹ค๋ฉด readText ๋˜๋Š” readBytes๋ฅผ ์‚ฌ์šฉํ•ด ์ „์ฒด ๋‚ด์šฉ์„ ๊ฐ๊ฐ ๋ฌธ์ž์—ด์ด๋‚˜ ๋ฐ”์ดํŠธ ๋ฐฐ์—ด๋กœ ์ฝ์–ด์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

ํŒŒ์ผ์— ์กด์žฌํ•˜๋Š” ๋‚ด์šฉ์„ ๋ชจ๋‘ ๊ต์ฒดํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด writeText ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉ

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

File ํด๋ž˜์Šค์—๋Š” ํŒŒ์ผ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” appendText๋ผ๋Š” ํ™•์žฅ ํ•จ์ˆ˜๊ฐ€ ์žˆ๋‹ค.

  • writeText์™€ appendText ํ•จ์ˆ˜๋Š” writeBytes์™€ appendBytes์— ๊ธฐ๋ก ์ž‘์—…์„ ์œ„์ž„ํžŒ๋‹ค.

  • writeBytes์™€ appendBytes๋Š” ๊ธฐ๋ก์ด ๋๋‚˜๋ฉด use ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ํŒŒ์ผ์„ ํ™•์‹คํžˆ ๋‹ซ๋Š”๋‹ค.

๊ฐœ๋ฐœ์ž๋Š” OutputStreamWriter์™€ BufferedWriter๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” writer(printWriter)์™€ bufferedWriter ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

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

๊ทธ ๋ฐ–์˜ ์ฝ”ํ‹€๋ฆฐ ๊ธฐ๋Šฅ

์ฝ”ํ‹€๋ฆฐ ๋ฒ„์ „ ์•Œ์•„๋‚ด๊ธฐ

์ฝ”ํŠธ๋ฅผ ์ž‘์„ฑํ•ด ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ ์ฝ”ํ‹€๋ฆฐ ๋ฒ„์ „์„ ์•Œ๋ ค๋ฉด,

KotlinVersion ํด๋ž˜์Šค ๋™๋ฐ˜ ๊ฐ์ฒด์˜ CURRENT ์†์„ฑ์„ ์‚ฌ์šฉํ•˜์ž.

๐Ÿ‘‰๐Ÿป ์ฝ”ํ‹€๋ฆฐ ๋ฒ„์ „ ๋น„๊ตํ•˜๊ธฐ

@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 ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์ž.

/**
 * 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 ์ ˆ์„ ์š”๊ตฌํ•˜๋„๋ก ๊ฐ•์š”

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 ์—ฐ์‚ฐ์ž ํ•จ์ˆ˜๋ฅผ ์žฌ์ •์˜ํ•˜์ž.

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 ํ•จ์ˆ˜์˜ ๊ตฌํ˜„

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

๐Ÿ‘‰๐Ÿป ์ฝ”๋“œ ๋ธ”๋ก์˜ ๊ฒฝ๊ณผ ์‹œ๊ฐ„ ์ธก์ •ํ•˜๊ธฐ

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")

}

๋” ์ •ํ™•ํ•œ ์„ฑ๋Šฅ ์ธก์ •์„ ์›ํ•œ๋‹ค๋ฉด ์˜คํ”ˆ JDK์˜ ์ž๋ฐ” ๋งˆ์ดํฌ๋กœ๋ฒค์น˜๋งˆํฌ ๋„๊ตฌJMH, Java Microbenchmark Harnessํ”„๋กœ์ ํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์ž.


์Šค๋ ˆ๋“œ ์‹œ์ž‘ํ•˜๊ธฐ

์Šค๋ ˆ๋“œ ํ™•์žฅ ํ•จ์ˆ˜์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜

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

๐Ÿ‘‰๐Ÿป ๋‹ค์ˆ˜์˜ ์Šค๋ ˆ๋“œ๋ฅผ ์ž„์˜์˜ ๊ฐ„๊ฒฉ์œผ๋กœ ์‹œ์ž‘ํ•˜๊ธฐ

/**
 * 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 ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ชจ๋“  ์Šค๋ ˆ๋“œ๋ฅผ ์ˆœ์ฐจ์ ์œผ๋กœ ํ˜ธ์ถœํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

(0..5).forEach { n ->
    thread {
    // ...
    }.join() // ๋ฆฌํ„ด๋œ ์Šค๋ ˆ๋“œ์˜ invoke ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
}

TODO๋กœ ์™„์„ฑ ๊ฐ•์ œํ•˜๊ธฐ

๐Ÿ‘‰๐Ÿป TODO ํ•จ์ˆ˜์˜ ๊ตฌํ˜„

  • ํšจ์œจ์„ฑ์„ ์ด์œ ๋กœ ์†Œ์Šค๋Š” ์ธ๋ผ์ธ๋˜์–ด ์žˆ๊ณ , ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ NotImplementedError ๋ฐœ์ƒ

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

...

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

Random์˜ ๋ฌด์ž‘์œ„ ๋™์ž‘ ์ดํ•ดํ•˜๊ธฐ

๐Ÿ‘‰๐Ÿป nextInt ํ•จ์ˆ˜

@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)
}

๐Ÿ‘‰๐Ÿป ์‹œ๋“œ ๊ฐ’๊ณผ ํ•จ๊ป˜ ๋‚œ์ˆ˜ ์ƒ์„ฑ๊ธฐ ์‚ฌ์šฉํ•˜๊ธฐ

@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

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

kotlin-spring์ด ์ œ๊ณตํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋” ๋งŽ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด all-open ํ”Œ๋Ÿฌ๊ทธ์ธ๋„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๊ฑฐ์˜ ํ•„์š” ์—†๋‹ค.


์ฝ”ํ‹€๋ฆฐ data ํด๋ž˜์Šค๋กœ ํผ์‹œ์Šคํ„ด์Šค ๊ตฌํ˜„ํ•˜๊ธฐ

data ํด๋ž˜์Šค๋กœ JPA๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด kotlin-jpa ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ถ”๊ฐ€ํ•˜์ž.

JPA ๊ด€์ ์—์„œ data ํด๋ž˜์Šค๋Š” ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค.

1๏ธโƒฃ JPA๋Š” ๋ชจ๋“  ์†์„ฑ์— ๊ธฐ๋ณธ๊ฐ’์„ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š” ์ด์ƒ ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ํ•„์ˆ˜์ง€๋งŒ data ํด๋ž˜์Šค๋Š” ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ์—†๋‹ค.

  • no-arg ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ์ธ์ž๊ฐ€ ์—†๋Š” ์ƒ์„ฑ์ž๋ฅผ ์ถ”๊ฐ€ํ•  ํด๋ž˜์Šค๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๊ณ 

  • ๊ธฐ๋ณธ ์ƒ์„ฑ์ž ์ถ”๊ฐ€๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์• ๋„ˆํ…Œ์ด์…˜์„ ์ •์˜ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

kotlin-jpa ํ”Œ๋Ÿฌ๊ทธ์ธ์€ no-arg ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋งŒ๋“ค์–ด ์กŒ๊ณ , ์•„๋ž˜ ์• ๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ž๋™ ํ‘œ์‹œ๋œ ํด๋ž˜์Šค์— ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

  • @Entity

  • @Embeddable

  • @MappedSuperclass

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

2๏ธโƒฃ val ์†์„ฑ๊ณผ ํ•จ๊ป˜ data ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ๋ถˆ๋ณ€ ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜๋Š”๋ฐ, JPA๋Š” ๋ถˆ๋ณ€ ๊ฐ์ฒด์™€ ๋”๋ถˆ์–ด ์ž˜ ๋™์ž‘ํ•˜๋„๋ก ์„ค๊ณ„๋˜์ง€ ์•Š์•˜๋‹ค.

  • ์—”ํ‹ฐํ‹ฐ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ ์ฝ”ํ‹€๋ฆฐ ํด๋ž˜์Šค์— (data ํด๋ž˜์Šค ๋Œ€์‹ ) ํ•„๋“œ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๊ฒŒ ์†์„ฑ์— var ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋Š” ๋‹จ์ˆœ ํด๋ž˜์Šค ์‚ฌ์šฉ์„ ์ถ”์ฒœ

๐Ÿ‘‰๐Ÿป ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”๋กœ ๋งคํ•‘๋˜๋Š” ์ฝ”ํ‹€๋ฆฐ ํด๋ž˜์Šค

@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


์˜์กด์„ฑ ์ฃผ์ž…ํ•˜๊ธฐ

์ฝ”ํ‹€๋ฆฐ ์Šคํ”„๋ง์€ ์ƒ์„ฑ์ž ์ฃผ์ž…์„ ์ œ๊ณตํ•˜์ง€๋งŒ ํ•„๋“œ ์ฃผ์ž…์—๋Š” lateinit var ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

์„ ํƒ์ ์ธ ๋นˆ์€ ๋„ ํ—ˆ์šฉ ํƒ€์ž…์œผ๋กœ ์„ ์–ธํ•œ๋‹ค.

ํด๋ž˜์Šค์—์„œ ์ƒ์„ฑ์ž๊ฐ€ ํ•˜๋‚˜๋ฟ์ด๋ผ๋ฉด ์Šคํ”„๋ง์ด ์ž๋™์œผ๋กœ ํด๋ž˜์Šค์˜ ์œ ์ผํ•œ ์ƒ์„ฑ์ž์— ๋ชจ๋“  ์ธ์ž๋ฅผ ์ž๋™์œผ๋กœ ์˜คํ† ์™€์ด์–ด๋งํ•˜๊ธฐ ๋•Œ๋ฌธ์— @Autowired ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

๐Ÿ‘‰๐Ÿป ์Šคํ”„๋ง์œผ๋กœ ์˜์กด์„ฑ ์˜คํ† ์™€์ด์–ด๋งํ•˜๊ธฐ

/** ๋‹จ์ผ ์ƒ์„ฑ์ž๋ฅผ ๊ฐ–๋Š” ํด๋ž˜์Šค */
@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

    // ...
}

ํด๋ž˜์Šค์˜ ์†์„ฑ์ด ํ•„์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ํ•ด๋‹น ์†์„ฑ์„ ๋„ ํ—ˆ์šฉ ํƒ€์ž…์œผ๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ‘‰๐Ÿป ์„ ํƒ ๊ฐ€๋Šฅํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ฐ–๋Š” ์ปจํŠธ๋กค๋Ÿฌ ํ•จ์ˆ˜

@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) {
    // ...
}

Last updated