{"id":45760,"date":"2021-11-23T17:07:04","date_gmt":"2021-11-23T08:07:04","guid":{"rendered":"https:\/\/www.charlezz.com\/?p=45760"},"modified":"2021-11-23T17:07:06","modified_gmt":"2021-11-23T08:07:06","slug":"testing-in-jetpack-compose-%eb%94%94%eb%b2%84%ea%b9%85-%ed%85%8c%ec%8a%a4%ed%8a%b8","status":"publish","type":"post","link":"https:\/\/charlezz.com\/?p=45760","title":{"rendered":"Testing in Jetpack Compose &#8211; \ub514\ubc84\uae45 \ud14c\uc2a4\ud2b8"},"content":{"rendered":"\n<p>\uc774 \ub2e8\uacc4\uc5d0\uc11c\ub294 \ud604\uc7ac \ud0ed\uc758 \ub808\uc774\ube14\uc774 \ub300\ubb38\uc790\ub85c \ud45c\uc2dc\ub418\ub294\uc9c0 \ud655\uc778\ud55c\ub2e4.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter is-resized\"><img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/developer.android.com\/codelabs\/jetpack-compose-testing\/img\/baca545ddc8c3fa9.png?hl=ko\" alt=\"baca545ddc8c3fa9.png\" width=\"450\" height=\"800\"\/><\/figure><\/div>\n\n\n\n<p>\uac00\ub2a5\ud55c \ud574\uacb0\ucc45\uc740 \ud14d\uc2a4\ud2b8\ub97c \ucc3e\uc544(find) \ub300\ubb38\uc790\uac00 \uc874\uc7ac\ud55c\ub2e4\uace0 \uc8fc\uc7a5(assert)\ud558\ub294 \uac83\uc774\ub2e4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@Test\nfun rallyTopAppBarTest_currentLabelExists() {\n\u00a0 \u00a0 val allScreens = RallyScreen.values().toList()\n\u00a0 \u00a0 composeTestRule.setContent {\n\u00a0 \u00a0 \u00a0 \u00a0 RallyTopAppBar(\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 allScreens = allScreens,\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 onTabSelected = { },\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 currentScreen = RallyScreen.Accounts\n\u00a0 \u00a0 \u00a0 \u00a0 )\n\u00a0 \u00a0 }\n\n\u00a0 \u00a0 composeTestRule\n\u00a0 \u00a0 \u00a0 \u00a0 .onNodeWithText(RallyScreen.Accounts.name.uppercase(Locale.getDefault()))\n\u00a0 \u00a0 \u00a0 \u00a0 .assertExists()\n}<\/code><\/pre>\n\n\n\n<p>\uadf8\ub7ec\ub098 \uc2e4\ud589\ud574\ubcf4\uba74 \ud14c\uc2a4\ud2b8\uac00 \uc2e4\ud328\ud558\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\ub2e4.\ud83d\ude31<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/developer.android.com\/codelabs\/jetpack-compose-testing\/img\/2abf8cb6fa32c03a.png?hl=ko\" alt=\"2abf8cb6fa32c03a.png\"\/><\/figure>\n\n\n\n<p>\uc774 \ub2e8\uacc4\uc5d0\uc11c\ub294 semantics tree\ub97c \uc0ac\uc6a9\ud558\uc5ec \ub514\ubc84\uae45\ud558\ub294 \ubc29\ubc95\uc744 \ubc30\uc6b4\ub2e4.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Semantics tree<\/h2>\n\n\n\n<p>\ucef4\ud3ec\uc988 \ud14c\uc2a4\ud2b8\ub294 semantics tree\ub77c\ub294 \uad6c\uc870\ub97c \uc0ac\uc6a9\ud558\uc5ec \ud654\uba74\uc5d0\uc11c \uc694\uc18c\ub97c \ucc3e\uace0 \ud574\ub2f9 \uc18d\uc131\uc744 \uc77d\ub294\ub2e4. \uc774\ub294 TalkBack\uacfc \uac19\uc740 \uc11c\ube44\uc2a4\uc5d0\uc11c \uc77d\uc744 \uc218 \uc788\ub3c4\ub85d \uc811\uadfc\uc131 \uc11c\ube44\uc2a4\uc5d0\uc11c\ub3c4 \uc0ac\uc6a9\ud558\ub294 \uad6c\uc870\ub2e4.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\"><p><strong>Warning<\/strong>: Semantics \uc18d\uc131\uc5d0 \ub300\ud55c Layout Inspector \uc9c0\uc6d0\uc740 \uc544\uc9c1 \uc0ac\uc6a9\ud560 \uc218 \uc5c6\ub2e4.<\/p><\/blockquote>\n\n\n\n<p>\ub178\ub4dc\uc5d0\uc11c printToLog \ud568\uc218\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc2dc\ub9e8\ud2f1 \ud2b8\ub9ac\ub97c \uc778\uc1c4\ud560 \uc218 \uc788\ub2e4. \ud14c\uc2a4\ud2b8\uc5d0 \uc0c8\ub85c\uc6b4 \ub77c\uc778\uc744 \ucd94\uac00\ud558\uc790.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>fun rallyTopAppBarTest_currentLabelExists() {\n\u00a0 \u00a0 val allScreens = RallyScreen.values().toList()\n\u00a0 \u00a0 composeTestRule.setContent {\n\u00a0 \u00a0 \u00a0 \u00a0 RallyTopAppBar(\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 allScreens = allScreens,\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 onTabSelected = { },\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 currentScreen = RallyScreen.Accounts\n\u00a0 \u00a0 \u00a0 \u00a0 )\n\u00a0 \u00a0 }\n\n\u00a0 \u00a0 composeTestRule.onRoot().printToLog(\"currentLabelExists\")\n\n\u00a0 \u00a0 composeTestRule\n\u00a0 \u00a0 \u00a0 \u00a0 .onNodeWithText(RallyScreen.Accounts.name.uppercase(Locale.getDefault()))\n\u00a0 \u00a0 \u00a0 \u00a0 .assertExists() \/\/ \uc5ec\uc804\ud788 \uc2e4\ud328\ud55c\ub2e4.\n}<\/code><\/pre>\n\n\n\n<p>\uc774\uc81c \ud14c\uc2a4\ud2b8\ub97c \uc2e4\ud589\ud558\uace0 Android Studio\uc5d0\uc11c Logcat\uc744 \ud655\uc778\ud558\uc790.(currentLabelExists\ub97c \ucc3e\uc744 \uc218 \uc788\ub2e4)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>...com.example.compose.rally D\/currentLabelExists: printToLog:<br>&nbsp; &nbsp; Printing with useUnmergedTree = 'false'<br>&nbsp; &nbsp; Node #1 at (l=0.0, t=63.0, r=1080.0, b=210.0)px<br>&nbsp; &nbsp; &nbsp;|-Node #2 at (l=0.0, t=63.0, r=1080.0, b=210.0)px<br>&nbsp; &nbsp; &nbsp; &nbsp;&#91;SelectableGroup]<br>&nbsp; &nbsp; &nbsp; &nbsp;MergeDescendants = 'true'<br>&nbsp; &nbsp; &nbsp; &nbsp; |-Node #3 at (l=42.0, t=105.0, r=105.0, b=168.0)px<br>&nbsp; &nbsp; &nbsp; &nbsp; | Role = 'Tab'<br>&nbsp; &nbsp; &nbsp; &nbsp; | Selected = 'false'<br>&nbsp; &nbsp; &nbsp; &nbsp; | StateDescription = 'Not selected'<br>&nbsp; &nbsp; &nbsp; &nbsp; | ContentDescription = 'Overview'<br>&nbsp; &nbsp; &nbsp; &nbsp; | Actions = &#91;OnClick]<br>&nbsp; &nbsp; &nbsp; &nbsp; | MergeDescendants = 'true'<br>&nbsp; &nbsp; &nbsp; &nbsp; | ClearAndSetSemantics = 'true'<br>&nbsp; &nbsp; &nbsp; &nbsp; |-Node #6 at (l=189.0, t=105.0, r=468.0, b=168.0)px<br>&nbsp; &nbsp; &nbsp; &nbsp; | Role = 'Tab'<br>&nbsp; &nbsp; &nbsp; &nbsp; | Selected = 'true'<br>&nbsp; &nbsp; &nbsp; &nbsp; | StateDescription = 'Selected'<br>&nbsp; &nbsp; &nbsp; &nbsp; | ContentDescription = 'Accounts'<br>&nbsp; &nbsp; &nbsp; &nbsp; | Actions = &#91;OnClick]<br>&nbsp; &nbsp; &nbsp; &nbsp; | MergeDescendants = 'true'<br>&nbsp; &nbsp; &nbsp; &nbsp; | ClearAndSetSemantics = 'true'<br>&nbsp; &nbsp; &nbsp; &nbsp; |-Node #11 at (l=552.0, t=105.0, r=615.0, b=168.0)px<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Role = 'Tab'<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Selected = 'false'<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; StateDescription = 'Not selected'<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ContentDescription = 'Bills'<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Actions = &#91;OnClick]<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; MergeDescendants = 'true'<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ClearAndSetSemantics = 'true'<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote\"><p>\uc804\uccb4 \uc571\uc758 semantics tree\ub294 \ub9e4\uc6b0 \uae38\uae30 \ub54c\ubb38\uc5d0, <em>\uc774\ub7ec\ud55c \uc811\uadfc\ubc29\uc2dd\uc73c\ub85c \uaca9\ub9ac\uc758 \ud3b8\ub9ac\ud568\uc744 \ub204\ub9b4 \uc218 \uc788\uc74c\uc744 \uc774\ud574\ud558\uae38 \ubc14<\/em>\ub780\ub2e4.<\/p><\/blockquote>\n\n\n\n<blockquote class=\"wp-block-quote\"><p>Warning: \ucef4\ud3ec\uc800\ube14\uc5d0\ub294 ID\uac00 \uc5c6\uace0, \ud2b8\ub9ac\uc5d0 \ud45c\uc2dc\ub41c \ub178\ub4dc \ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc77c\uce58\uc2dc\ud0ac \uc218 \uc5c6\ub2e4. semantics \uc18d\uc131\uacfc \ub178\ub4dc\ub97c \uc77c\uce58\uc2dc\ud0a4\ub294 \uac83\uc774 \ube44\uc2e4\uc6a9\uc801\uc774\uac70\ub098 \ubd88\uac00\ub2a5\ud55c \uacbd\uc6b0, \ub9c8\uc9c0\ub9c9 \uc218\ub2e8\uc73c\ub85c <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/ui\/test\/package-summary?hl=ko#hasTestTag(kotlin.String)\">hasTestTag<\/a> matcher\uc640 \ud568\uaed8 <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/ui\/platform\/package-summary?hl=ko#testTag(androidx.compose.ui.Modifier,kotlin.String)\">testTag<\/a> Modifier\ub97c \uc0ac\uc6a9\ud560 \uc218 \uc788\ub2e4.<\/p><\/blockquote>\n\n\n\n<p>Semantics \ud2b8\ub9ac\ub97c \uc0b4\ud3b4\ubcf4\uba74, \uc0c1\ub2e8 \uc571 \ubc14\uc758 \ud0ed\uc778 3\uac1c\uc758 \ud558\uc704 \uc694\uc18c\uac00 \uc788\ub294 SelectableGroup\uc774 \uc788\uc74c\uc744 \uc54c \uc218 \uc788\ub2e4. &#8220;ACCOUNTS&#8221; \uac12\uc744 \uac00\uc9c4 \ud14d\uc2a4\ud2b8 \uc18d\uc131\uc774 \uc5c6\ub2e4. \uc774\uac83\uc774 \ubc14\ub85c \ud14c\uc2a4\ud2b8\ub97c \ud1b5\uacfc\ud558\uc9c0 \ubabb\ud55c \uc774\uc720\ub2e4. \uadf8\ub7ec\ub098 <strong>\uac01 \ud0ed\uc5d0 \ub300\ud55c \ucee8\ud150\ud2b8 \uc124\uba85(content description)<\/strong>\uc774 \uc788\ub2e4. TopAppBar.kt \ub0b4\ubd80\uc758 RallyTab \ucef4\ud3ec\uc800\ube14\uc5d0\uc11c \uc774 \uc18d\uc131\uc774 \uc5b4\ub5bb\uac8c \uc124\uc815\ub418\uc5b4 \uc788\ub294\uc9c0 \ud655\uc778\ud560 \uc218 \uc788\ub2e4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>private fun RallyTab(text: String...)<br>...<br>&nbsp; &nbsp; Modifier<br>&nbsp; &nbsp; &nbsp; &nbsp; .clearAndSetSemantics { contentDescription = text }<\/code><\/pre>\n\n\n\n<p>\uc774 modifier\ub294 \ud558\uc704 \ud56d\ubaa9\uc5d0\uc11c \uc18d\uc131\uc744 \uc9c0\uc6b0\uace0, \uc790\uccb4 content description\uc744 \uc124\uc815\ud558\ubbc0\ub85c &#8220;ACCOUNTS&#8221;\uac00 \uc544\ub2cc &#8220;Accounts&#8221;\uac00 \ud45c\uc2dc\ub41c\ub2e4.<\/p>\n\n\n\n<p>finder\uc778 onNodeWithText\ub97c onNodeWithContentDescription\uc73c\ub85c \uad50\uccb4\ud558\uace0 \ud14c\uc2a4\ud2b8\ub97c \ub2e4\uc2dc \uc2e4\ud589\ud574\ubcf4\uc790.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>fun rallyTopAppBarTest_currentLabelExists() {\n\u00a0 \u00a0 val allScreens = RallyScreen.values().toList()\n\u00a0 \u00a0 composeTestRule.setContent {\n\u00a0 \u00a0 \u00a0 \u00a0 RallyTopAppBar(\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 allScreens = allScreens,\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 onTabSelected = { },\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 currentScreen = RallyScreen.Accounts\n\u00a0 \u00a0 \u00a0 \u00a0 )\n\u00a0 \u00a0 }\n\n<strong>\u00a0 \u00a0 composeTestRule\n\u00a0 \u00a0 \u00a0 \u00a0 .onNodeWithContentDescription(RallyScreen.Accounts.name)\n\u00a0 \u00a0 \u00a0 \u00a0 .assertExists()<\/strong>\n}<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/developer.android.com\/codelabs\/jetpack-compose-testing\/img\/3f3d9e9b90cd6cf4.png?hl=ko\" alt=\"3f3d9e9b90cd6cf4.png\"\/><\/figure>\n\n\n\n<p>\ucd95\ud558\ud569\ub2c8\ub2e4! \ud14c\uc2a4\ud2b8\ub97c \uc218\uc815\ud558\uace0 ComposeTestRule, \uaca9\ub9ac \ud14c\uc2a4\ud2b8, finder, assertion \ubc0f Semantics tree\ub97c \uc0ac\uc6a9\ud55c \ub514\ubc84\uae45\uc5d0 \ub300\ud574 \ubc30\uc6e0\ub2e4.<\/p>\n\n\n\n<p>\ud558\uc9c0\ub9cc \ub098\uc05c \uc18c\uc2dd\uc740 \uc774 \ud14c\uc2a4\ud2b8\uac00 \uadf8\ub2e4\uc9c0 \uc720\uc6a9\ud558\uc9c0 \uc54a\ub2e4\ub294 \uac83\uc774\ub2e4! Semantics tree\ub97c \uc790\uc138\ud788 \ubcf4\uba74 \ud0ed\uc774 \uc120\ud0dd\ub418\uc5c8\ub294\uc9c0 \uc5ec\ubd80\uc5d0 \uad00\uacc4\uc5c6\uc774 \uc138 \uac1c\uc758 \ud0ed \ubaa8\ub450\uc5d0 \ub300\ud55c content description\uc774 \uc788\ub2e4. \uc6b0\ub9ac\ub294 \ub354 \uae4a\uc219\ud788 \ud30c\uace0 \ub4e4\uc5b4\uac00\uc57c \ud55c\ub2e4!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\uc774 \ub2e8\uacc4\uc5d0\uc11c\ub294 \ud604\uc7ac \ud0ed\uc758 \ub808\uc774\ube14\uc774 \ub300\ubb38\uc790\ub85c \ud45c\uc2dc\ub418\ub294\uc9c0 \ud655\uc778\ud55c\ub2e4. \uac00\ub2a5\ud55c \ud574\uacb0\ucc45\uc740 \ud14d\uc2a4\ud2b8\ub97c \ucc3e\uc544(find) \ub300\ubb38\uc790\uac00 \uc874\uc7ac\ud55c\ub2e4\uace0 \uc8fc\uc7a5(assert)\ud558\ub294 \uac83\uc774\ub2e4. \uadf8\ub7ec\ub098 \uc2e4\ud589\ud574\ubcf4\uba74 \ud14c\uc2a4\ud2b8\uac00 \uc2e4\ud328\ud558\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\ub2e4.\ud83d\ude31 \uc774 \ub2e8\uacc4\uc5d0\uc11c\ub294 semantics tree\ub97c \uc0ac\uc6a9\ud558\uc5ec \ub514\ubc84\uae45\ud558\ub294 \ubc29\ubc95\uc744 \ubc30\uc6b4\ub2e4. Semantics tree \ucef4\ud3ec\uc988 \ud14c\uc2a4\ud2b8\ub294 semantics tree\ub77c\ub294 \uad6c\uc870\ub97c \uc0ac\uc6a9\ud558\uc5ec \ud654\uba74\uc5d0\uc11c \uc694\uc18c\ub97c \ucc3e\uace0 \ud574\ub2f9 \uc18d\uc131\uc744 \uc77d\ub294\ub2e4. \uc774\ub294 TalkBack\uacfc \uac19\uc740 \uc11c\ube44\uc2a4\uc5d0\uc11c [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"inline_featured_image":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[38],"tags":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/45760"}],"collection":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=45760"}],"version-history":[{"count":1,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/45760\/revisions"}],"predecessor-version":[{"id":45761,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/45760\/revisions\/45761"}],"wp:attachment":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=45760"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=45760"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=45760"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}