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")
}
์ค๋ ๋ ์์ํ๊ธฐ
์ค๋ ๋ ํ์ฅ ํจ์์ ์๊ทธ๋์ฒ
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")
์ฝํ๋ฆฐ 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