diff --git a/README.md b/README.md index b4b1377..c8fcbd4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,22 @@ # ๋ฏธ์…˜ - ์›Œ๋“ค +## ๐Ÿ“ ๊ธฐ๋Šฅ ๋ชฉ๋ก + +### ์ฝ”ํ‹€๋ฆฐ ์›Œ๋“ค ๊ธฐ๋Šฅ๋ชฉ๋ก / ํŽ˜ํผ, ์กฐ์กฐ๊ทธ๋ฆฐ + +- [x] ์‹œ์ž‘๋ฉ”์‹œ์ง€ ์ถœ๋ ฅํ•˜๋Š” ๊ธฐ๋Šฅ +- [x] ๋‹ต์•ˆ์„ ์ž…๋ ฅ๋ฐ›๋Š” ๊ธฐ๋Šฅ + - [x] ์ž…๋ ฅ๊ฐ’์€ 5์ž๋ฆฌ ๋ฌธ์ž์—ด๋กœ ์ œํ•œํ•˜๋Š” ๊ธฐ๋Šฅ + - [x] ์ž…๋ ฅ๊ฐ’์€ `words.txt` ์— ์กด์žฌํ•˜๋Š” ๋‹จ์–ด๋งŒ ๊ฐ€๋Šฅ + - [x] ๋‹ต์•ˆ์€ 6๋ฒˆ ๋งŒ ์ž…๋ ฅ ๊ฐ€๋Šฅ +- [x] ์˜ค๋Š˜์˜ ์ •๋‹ต์„ ์„ ํƒํ•˜๋Š” ๊ธฐ๋Šฅ + - [x] ์ •๋‹ต์€ `words.txt` ์˜ ((ํ˜„์žฌ ๋‚ ์งœ - 2021๋…„ 6์›” 19์ผ) % ๋ฐฐ์—ด์˜ ํฌ๊ธฐ) ๋ฒˆ์งธ์˜ ๋‹จ์–ด +- [x] ๋‹ต์•ˆ์ด ๊ฐ ๋ฌธ์ž๊ฐ€ ์ •๋‹ต์˜ ๋ฌธ์ž์™€ ์–ด๋–ป๊ฒŒ ๋งค์นญ๋˜๋Š”์ง€ ์ฐพ๋Š” ๊ธฐ๋Šฅ + - [x] ๋‘ ๊ฐœ์˜ ๋™์ผํ•œ ๋ฌธ์ž๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๊ทธ์ค‘ ํ•˜๋‚˜๊ฐ€ ํšŒ์ƒ‰์œผ๋กœ ํ‘œ์‹œ๋˜๋ฉด ํ•ด๋‹น ๋ฌธ์ž ์ค‘ ํ•˜๋‚˜๋งŒ ์ตœ์ข… ๋‹จ์–ด์— ํ‘œ์‹œ +- [x] ํ™•์ธ๋œ ์ •๋ณด์— ๋งž๊ฒŒ ์ถœ๋ ฅํ•˜๋Š” ๊ธฐ๋Šฅ + - [x] ๋งž๋Š” ๊ธ€์ž๋Š” ์ดˆ๋ก์ƒ‰, ์œ„์น˜๊ฐ€ ํ‹€๋ฆฌ๋ฉด ๋…ธ๋ž€์ƒ‰, ์—†์œผ๋ฉด ํšŒ์ƒ‰์œผ๋กœ ํ‘œ์‹œ +- [x] ๊ฒŒ์ž„ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ๊ธฐ๋Šฅ + ## ๐Ÿ” ์ง„ํ–‰ ๋ฐฉ์‹ - ๋ฏธ์…˜์€ **๊ธฐ๋Šฅ ์š”๊ตฌ ์‚ฌํ•ญ, ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ, ๊ณผ์ œ ์ง„ํ–‰ ์š”๊ตฌ ์‚ฌํ•ญ** ์„ธ ๊ฐ€์ง€๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค. diff --git a/src/main/kotlin/.gitkeep b/src/main/kotlin/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/kotlin/Application.kt b/src/main/kotlin/Application.kt new file mode 100644 index 0000000..bd12ffe --- /dev/null +++ b/src/main/kotlin/Application.kt @@ -0,0 +1,5 @@ +import wordle.controller.WordleController + +fun main() { + WordleController().run() +} diff --git a/src/main/kotlin/wordle/controller/WordleController.kt b/src/main/kotlin/wordle/controller/WordleController.kt new file mode 100644 index 0000000..a7c090c --- /dev/null +++ b/src/main/kotlin/wordle/controller/WordleController.kt @@ -0,0 +1,33 @@ +package wordle.controller + +import wordle.domain.Answer +import wordle.domain.Game +import wordle.domain.Words +import wordle.view.printErrorMessage +import wordle.view.printResults +import wordle.view.printStartMessage +import wordle.view.requestInput +import java.time.LocalDate + +class WordleController { + + fun run() { + val game = Game(Words.pick(LocalDate.now())) + printStartMessage(game.fixedCount) + + while (game.isPlaying) { + val answer = requestAnswer() + game.playRound(answer) + printResults(game.results, game.isPlaying, game.findTryCount(), game.fixedCount) + } + } + + private fun requestAnswer(): Answer { + return try { + Answer(requestInput()) + } catch (e: IllegalArgumentException) { + printErrorMessage(e.message) + requestAnswer() + } + } +} diff --git a/src/main/kotlin/wordle/domain/Answer.kt b/src/main/kotlin/wordle/domain/Answer.kt new file mode 100644 index 0000000..e935fc8 --- /dev/null +++ b/src/main/kotlin/wordle/domain/Answer.kt @@ -0,0 +1,46 @@ +package wordle.domain + +class Answer(private val answer: String) { + + init { + require(answer.length == WORD_SIZE) { "[ERROR] ๋ถ€์ ์ ˆํ•œ ๊ธ€์ž ๊ธธ์ด์ž…๋‹ˆ๋‹ค." } + require(Words.contains(answer)) { "[ERROR] ๋ชฉ๋ก์— ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋‹จ์–ด์ž…๋‹ˆ๋‹ค." } + } + + fun compareToWord(word: String): MutableList { + val result = MutableList(WORD_SIZE) { Mark.NONE } + val wordTable = createWordTable(word) + (0 until WORD_SIZE).map { markExact(it, word, result, wordTable) } + (0 until WORD_SIZE).map { markExist(it, result, wordTable) } + return result + } + + private fun createWordTable(word: String): HashMap { + val wordTable = HashMap() + for (char in word) { + wordTable[char] = wordTable.getOrDefault(char, 0) + 1 + } + return wordTable + } + + private fun markExact(i: Int, word: String, result: MutableList, wordTable: HashMap) { + if (word[i] == answer[i]) { + result[i] = Mark.EXACT + wordTable.computeIfPresent(word[i]) { _, v -> v - 1 } + } + } + + private fun markExist(i: Int, result: MutableList, wordTable: HashMap) { + if (isExist(i, result, wordTable, answer[i])) { + result[i] = Mark.EXIST + wordTable.computeIfPresent(answer[i]) { _, v -> v - 1 } + } + } + + private fun isExist( + i: Int, + result: MutableList, + wordTable: HashMap, + charOfAnswer: Char, + ) = result[i] == Mark.NONE && wordTable.containsKey(charOfAnswer) && wordTable[charOfAnswer] != 0 +} diff --git a/src/main/kotlin/wordle/domain/Game.kt b/src/main/kotlin/wordle/domain/Game.kt new file mode 100644 index 0000000..3e3c3b6 --- /dev/null +++ b/src/main/kotlin/wordle/domain/Game.kt @@ -0,0 +1,21 @@ +package wordle.domain + +class Game(private val word: String) { + val fixedCount = 6 + val results: Results = Results() + var isPlaying: Boolean = true + private set + + fun playRound(answer: Answer) { + val result = answer.compareToWord(word) + results.add(result) + isPlaying = !isOver(result) + } + + private fun isOver(result: MutableList) = + results.isLimit() || result.all { it == Mark.EXACT } + + fun findTryCount(): Int { + return results.value.size + } +} diff --git a/src/main/kotlin/wordle/domain/Mark.kt b/src/main/kotlin/wordle/domain/Mark.kt new file mode 100644 index 0000000..9922112 --- /dev/null +++ b/src/main/kotlin/wordle/domain/Mark.kt @@ -0,0 +1,8 @@ +package wordle.domain + +enum class Mark { + + NONE, + EXIST, + EXACT +} diff --git a/src/main/kotlin/wordle/domain/Results.kt b/src/main/kotlin/wordle/domain/Results.kt new file mode 100644 index 0000000..211a3e9 --- /dev/null +++ b/src/main/kotlin/wordle/domain/Results.kt @@ -0,0 +1,16 @@ +package wordle.domain + +private const val LIMIT_SIZE = 6 + +class Results { + + val value: MutableList> = mutableListOf() + + fun add(result: List) { + value.add(result) + } + + fun isLimit(): Boolean { + return value.size >= LIMIT_SIZE + } +} diff --git a/src/main/kotlin/wordle/domain/Words.kt b/src/main/kotlin/wordle/domain/Words.kt new file mode 100644 index 0000000..f30e24a --- /dev/null +++ b/src/main/kotlin/wordle/domain/Words.kt @@ -0,0 +1,23 @@ +package wordle.domain + +import java.io.File +import java.time.LocalDate +import java.time.temporal.ChronoUnit + +const val WORD_SIZE = 5 + +object Words { + + private val VALUE: List = File("src/main/resources/words.txt").readLines() + + private val BASIC_DATE = LocalDate.of(2021, 6, 19) + + fun contains(word: String): Boolean { + return VALUE.contains(word) + } + + fun pick(date: LocalDate): String { + val index = ChronoUnit.DAYS.between(BASIC_DATE, date) % VALUE.size + return VALUE[index.toInt()] + } +} diff --git a/src/main/kotlin/wordle/view/View.kt b/src/main/kotlin/wordle/view/View.kt new file mode 100644 index 0000000..8e2f60c --- /dev/null +++ b/src/main/kotlin/wordle/view/View.kt @@ -0,0 +1,44 @@ +package wordle.view + +import wordle.domain.Mark +import wordle.domain.Results + +fun printStartMessage(fixedCount: Int) { + println("WORDLE ์„ $fixedCount ๋ฒˆ ๋งŒ์— ๋งž์ถฐ ๋ณด์„ธ์š”.\n์‹œ๋„์˜ ๊ฒฐ๊ณผ๋Š” ํƒ€์ผ์˜ ์ƒ‰ ๋ณ€ํ™”๋กœ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.") +} + +fun requestInput(): String { + println("์ •๋‹ต์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.") + return readln() +} + +fun printResults(results: Results, isPlaying: Boolean, tryCount: Int, fixedCount: Int) { + println() + if (!isPlaying) { + printTryCount(tryCount, fixedCount) + } + results.value.forEach { + printResult(it) + } + println() +} + +fun printTryCount(tryCount: Int, fixedCount: Int) { + println("$tryCount/$fixedCount\n") +} + +private fun printResult(result: List) { + val stringBuilder = StringBuilder() + result.forEach { + when (it) { + Mark.NONE -> stringBuilder.append("โฌœ") + Mark.EXIST -> stringBuilder.append("๐ŸŸจ") + Mark.EXACT -> stringBuilder.append("๐ŸŸฉ") + } + } + println(stringBuilder.toString()) +} + +fun printErrorMessage(message: String?) { + println(message) +} diff --git a/src/test/kotlin/.gitkeep b/src/test/kotlin/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/kotlin/practice/DslTest.kt b/src/test/kotlin/practice/DslTest.kt new file mode 100644 index 0000000..a377eba --- /dev/null +++ b/src/test/kotlin/practice/DslTest.kt @@ -0,0 +1,51 @@ +package practice + +import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat +import org.junit.jupiter.api.Test + +class DslTest { + + @Test + fun `์ž๊ธฐ์†Œ๊ฐœ ํ”„๋Ÿฌํผํ‹ฐ ๋ฒ„์ „`() { + val person = introduce { + name = "์ตœ์ˆ˜์—ฐ" + age = 26 + } + assertThat(person.name).isEqualTo("์ตœ์ˆ˜์—ฐ") + assertThat(person.age).isEqualTo(26) + } + + @Test + fun `์ž๊ธฐ์†Œ๊ฐœ ํ•จ์ˆ˜ ๋ฒ„์ „`() { + val person = introduce { + name("์ตœ์ˆ˜์—ฐ") + age(26) + } + assertThat(person.name).isEqualTo("์ตœ์ˆ˜์—ฐ") + assertThat(person.age).isEqualTo(26) + } +} + +private fun introduce(builder: PersonBuilder.() -> Unit): Person { +// ๋žŒ๋‹ค์—์„œ ํ•จ์ˆ˜ ํ˜•์‹์„ ์ง€์ •๊ฐ€๋Šฅ + return PersonBuilder().apply(builder).build() +} + +class PersonBuilder { + lateinit var name: String + var age: Int = 0 + + fun name(value: String) { + name = value + } + + fun age(value: Int) { + age = value + } + + fun build(): Person { + return Person(name, age) + } +} + +data class Person(val name: String, val age: Int) diff --git a/src/test/kotlin/study/DslTest.kt b/src/test/kotlin/study/DslTest.kt new file mode 100644 index 0000000..2a9b3b3 --- /dev/null +++ b/src/test/kotlin/study/DslTest.kt @@ -0,0 +1,82 @@ +package study + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.groups.Tuple +import org.junit.jupiter.api.Test + +class DslTest { + @Test + fun `ํŽ˜ํผ ์ž๊ธฐ์†Œ๊ฐœ ํ”„๋Ÿฌํผํ‹ฐ ๋ฒ„์ „`() { + val profile = introduce { + name("์ตœ์ˆ˜์—ฐ") + company("์šฐ์•„ํ•œํ…Œํฌ์ฝ”์Šค") + skills { + soft("A passion for problem solving") + soft("Good communication skills") + hard("Kotlin") + } + languages { + "Korean" level 5 + "English" level 3 + } + } + assertThat(profile.name).isEqualTo("์ตœ์ˆ˜์—ฐ") + assertThat(profile.company).isEqualTo("์šฐ์•„ํ•œํ…Œํฌ์ฝ”์Šค") + assertThat(profile.skills.soft).contains("A passion for problem solving", "Good communication skills") + assertThat(profile.skills.hard).contains("Kotlin") + assertThat(profile.languages.languages).extracting("type", "level") + .contains( + Tuple.tuple("Korean", 5), + Tuple.tuple("English", 3) + ) + } +} + +fun introduce(builder: ProfileBuilder.() -> Unit): Profile { + return ProfileBuilder().apply(builder).build() +} + +class ProfileBuilder { + private lateinit var name: String + private lateinit var company: String + private lateinit var skills: Skills + private lateinit var languages: Languages + + fun name(value: String) { + name = value + } + + fun company(value: String) { + company = value + } + + fun skills(skillValue: Skills.() -> Unit) { + skills = Skills(mutableListOf(), mutableListOf()).apply(skillValue) + } + + fun languages(languageSkill: Languages.() -> Unit) { + languages = Languages(mutableListOf()).apply(languageSkill) + } + + fun build(): Profile { + return Profile(name, company, skills, languages) + } +} + +data class Profile(val name: String, val company: String, val skills: Skills, val languages: Languages) + +data class Skills(val hard: MutableList, val soft: MutableList) { + fun hard(value: String) { + hard.add(value) + } + + fun soft(value: String) { + soft.add(value) + } +} + +data class Languages(val languages: MutableList) { + infix fun String.level(value: Int) = languages.add(Language(this, value)) +} + +data class Language(val type: String, val level: Int) diff --git a/src/test/kotlin/wordle/domain/AnswerTest.kt b/src/test/kotlin/wordle/domain/AnswerTest.kt new file mode 100644 index 0000000..1acc62b --- /dev/null +++ b/src/test/kotlin/wordle/domain/AnswerTest.kt @@ -0,0 +1,92 @@ +package wordle.domain + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.data.forAll +import io.kotest.data.headers +import io.kotest.data.row +import io.kotest.data.table +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.throwable.shouldHaveMessage +import org.junit.jupiter.api.Test +import wordle.domain.Mark.EXACT +import wordle.domain.Mark.EXIST +import wordle.domain.Mark.NONE + +internal class AnswerTest { + + @Test + fun `๊ธ€์ž๊ธธ์ด๊ฐ€ 5๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฐœ์ƒ`() { + forAll( + table( + headers("answer"), + row("abcd"), + row("abcdef") + ) + ) { + shouldThrow { Answer(it) } + .shouldHaveMessage("[ERROR] ๋ถ€์ ์ ˆํ•œ ๊ธ€์ž ๊ธธ์ด์ž…๋‹ˆ๋‹ค.") + } + } + + @Test + fun `์ฃผ์–ด์ง„ ๋‹จ์–ด๋ชฉ๋ก์— ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฐœ์ƒ`() { + shouldThrow { Answer("abcde") } + .shouldHaveMessage("[ERROR] ๋ชฉ๋ก์— ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋‹จ์–ด์ž…๋‹ˆ๋‹ค.") + } + + @Test + fun `๋‹ต์•ˆ๊ณผ ์ •๋‹ต์ด ์ผ์น˜`() { + val answer = Answer("apple") + + answer.compareToWord("apple") shouldContainExactly listOf(EXACT, EXACT, EXACT, EXACT, EXACT) + } + + @Test + fun `์ •๋‹ต๊ณผ ๋‹ต์•ˆ์˜ ๋ฌธ์ž๊ฐ€ ๋ชจ๋‘ ๋ถˆ์ผ์น˜`() { + val answer = Answer("madam") + + answer.compareToWord("rerun") shouldContainExactly listOf(NONE, NONE, NONE, NONE, NONE) + } + + @Test + fun `์ •๋‹ต์˜ ๋ฌธ์ž๊ฐ€ ๋‹ต์•ˆ์— ๋ชจ๋‘ ์กด์žฌํ•˜์ง€๋งŒ ์œ„์น˜๊ฐ€ ๋ชจ๋‘ ๋ถˆ์ผ์น˜`() { + val answer = Answer("rebut") + + answer.compareToWord("brute") shouldContainExactly listOf(EXIST, EXIST, EXIST, EXIST, EXIST) + } + + @Test + fun `์ •๋‹ต์ด rebut์ด๊ณ  ๋‹ต์•ˆ์ด speed์ผ ๋•Œ ์ฒซ๋ฒˆ์งธ e๋งŒ EXIST, ๋‚˜๋จธ์ง€๋Š” NONE`() { + val answer = Answer("speed") + + answer.compareToWord("rebut") shouldContainExactly listOf(NONE, NONE, EXIST, NONE, NONE) + } + + @Test + fun `์ •๋‹ต์ด erase์ด๊ณ  ๋‹ต์•ˆ์ด speed์ผ ๋•Œ e์™€ s๋Š” ๋ชจ๋‘ EXIST, ๋‚˜๋จธ์ง€๋Š” NONE`() { + val answer = Answer("speed") + + answer.compareToWord("erase") shouldContainExactly listOf(EXIST, NONE, EXIST, EXIST, NONE) + } + + @Test + fun `์ •๋‹ต์ด spill์ด๊ณ  ๋‹ต์•ˆ์ด label์ผ ๋•Œ ์ฒซ๋ฒˆ์งธ l์€ EXIST, ๋‘๋ฒˆ์งธ l์€ EXACT, ๋‚˜๋จธ์ง€๋Š” NONE`() { + val answer = Answer("label") + + answer.compareToWord("spill") shouldContainExactly listOf(EXIST, NONE, NONE, NONE, EXACT) + } + + @Test + fun `์ •๋‹ต์ด spill์ด๊ณ  ๋‹ต์•ˆ์ด hello์ผ ๋•Œ ์ฒซ๋ฒˆ์งธ l์€ EXIST, ๋‘๋ฒˆ์งธ l์€ EXACT, ๋‚˜๋จธ์ง€๋Š” NONE`() { + val answer = Answer("hello") + + answer.compareToWord("spill") shouldContainExactly listOf(NONE, NONE, EXIST, EXACT, NONE) + } + + @Test + fun `์ •๋‹ต์ด mourn์ด๊ณ  ๋‹ต์•ˆ์ด sorry์ผ ๋•Œ o์™€ ๋‘๋ฒˆ์งธ r์€ EXACT, ๋‚˜๋จธ์ง€๋Š” NONE`() { + val answer = Answer("sorry") + + answer.compareToWord("mourn") shouldContainExactly listOf(NONE, EXACT, NONE, EXACT, NONE) + } +} diff --git a/src/test/kotlin/wordle/domain/GameTest.kt b/src/test/kotlin/wordle/domain/GameTest.kt new file mode 100644 index 0000000..f5602ce --- /dev/null +++ b/src/test/kotlin/wordle/domain/GameTest.kt @@ -0,0 +1,50 @@ +package wordle.domain + +import io.kotest.assertions.assertSoftly +import io.kotest.matchers.ints.shouldBeExactly +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +internal class GameTest { + + @Test + fun `์ •๋‹ต์„ 6๋ฒˆ ๋ฏธ๋งŒ์œผ๋กœ ์ž…๋ ฅํ•œ ๊ฒฝ์šฐ ๊ฒŒ์ž„ ์ง„ํ–‰ ๊ฐ€๋Šฅ`() { + val game = Game("fetus") + repeat(5) { game.playRound(Answer("apple")) } + + assertSoftly(game) { + findTryCount() shouldBeExactly 5 + isPlaying shouldBe true + } + } + + @Test + fun `๊ฒŒ์ž„์„ ํ•œ ๋ผ์šด๋“œ์”ฉ ์ง„ํ–‰ํ•˜๋‹ค 6๋ผ์šด๋“œ์— ๋„๋‹ฌํ•˜๋ฉด ๊ฒŒ์ž„์ข…๋ฃŒ`() { + val game = Game("fetus") + repeat(6) { game.playRound(Answer("apple")) } + + assertSoftly(game) { + findTryCount() shouldBeExactly 6 + isPlaying shouldBe false + } + } + + @Test + fun `๊ฒŒ์ž„ 6๋ผ์šด๋“œ ์ด์ „์— ์ •๋‹ต์„ ๋งž์ถ”๋ฉด ๊ฒŒ์ž„์ข…๋ฃŒ`() { + val game = Game("fetus") + repeat(3) { game.playRound(Answer("fetus")) } + + assertSoftly(game) { + findTryCount() shouldBeExactly 3 + isPlaying shouldBe false + } + } + + @Test + fun `๋ช‡๋ฒˆ์งธ ์‹œ๋„์ธ์ง€ ๊ณ„์‚ฐ`() { + val game = Game("fetus") + repeat(6) { game.playRound(Answer("apple")) } + + game.findTryCount() shouldBeExactly 6 + } +} diff --git a/src/test/kotlin/wordle/domain/WordsTest.kt b/src/test/kotlin/wordle/domain/WordsTest.kt new file mode 100644 index 0000000..174adc2 --- /dev/null +++ b/src/test/kotlin/wordle/domain/WordsTest.kt @@ -0,0 +1,15 @@ +package wordle.domain + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import java.time.LocalDate + +internal class WordsTest { + + @Test + fun `์˜ค๋Š˜์˜ ๋‹จ์–ด๋ฅผ ์„ ํƒ`() { + val date = LocalDate.of(2022, 5, 12) + + Words.pick(date) shouldBe "fetus" + } +}