{"id":45762,"date":"2021-11-23T17:44:06","date_gmt":"2021-11-23T08:44:06","guid":{"rendered":"https:\/\/www.charlezz.com\/?p=45762"},"modified":"2021-11-23T17:44:09","modified_gmt":"2021-11-23T08:44:09","slug":"testing-in-jetpack-compose-%eb%b3%91%ed%95%a9-%eb%b0%8f-%eb%b3%91%ed%95%a9%ed%95%b4%ec%a0%9c-%eb%90%9c-semantics-tree%eb%93%a4","status":"publish","type":"post","link":"https:\/\/charlezz.com\/?p=45762","title":{"rendered":"Testing in Jetpack Compose &#8211; \ubcd1\ud569 \ubc0f \ubcd1\ud569\ud574\uc81c \ub41c semantics tree\ub4e4"},"content":{"rendered":"\n<p>\uc2dc\ub9e8\ud2f1 \ud2b8\ub9ac(Semantics tree)\ub294 \ud56d\uc0c1 \uad00\ub828 \uc815\ubcf4\ub9cc \ud45c\uc2dc\ud558\uc5ec \ucd5c\ub300\ud55c \uac04\uacb0\ud558\uac8c \ub9cc\ub4e4\ub824\uace0 \ud55c\ub2e4.<\/p>\n\n\n\n<p>\uc608\ub97c \ub4e4\uc5b4, TopAppBar\uc5d0\uc11c\ub294 \uc544\uc774\ucf58\uacfc \ub808\uc774\ube14\uc774 \ub2e4\ub978 \ub178\ub4dc\uc77c \ud544\uc694\uac00 \uc5c6\ub2e4. &#8220;Overview&#8221; \ub178\ub4dc\ub97c \uc0b4\ud3b4\ubcf4\uc790.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/developer.android.com\/codelabs\/jetpack-compose-testing\/img\/d20c96207c30e44a.png?hl=ko\" alt=\"d20c96207c30e44a.png\"\/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>|-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'<\/code><\/pre>\n\n\n\n<p>\uc774 \ub178\ub4dc\uc5d0\ub294 <em>selectable<\/em> \ucef4\ud3ec\ub10c\ud2b8\uc5d0 \ub300\ud574 \ud2b9\ubcc4\ud788 \uc815\uc758\ub41c \uc18d\uc131(\uc608: <em>Selected<\/em> \ubc0f <em>Role<\/em>)\uacfc \uc804\uccb4 \ud0ed\uc5d0 \ub300\ud55c \ucf58\ud150\uce20 \uc124\uba85(content description)\uc774 \uc788\ub2e4. \uc774\ub294 \uac04\ub2e8\ud55c \ud14c\uc2a4\ud2b8\uc5d0 \ub9e4\uc6b0 \uc720\uc6a9\ud55c \uace0\uc218\uc900(high-level) \uc18d\uc131\uc774\ub2e4. \uc544\uc774\ucf58\uc774\ub098 \ud14d\uc2a4\ud2b8\uc5d0 \ub300\ud55c \uc138\ubd80\uc815\ubcf4\ub294 \uc911\ubcf5\ub418\uc5b4 \ud45c\uc2dc\ub418\uc9c0 \uc54a\ub294\ub2e4.<\/p>\n\n\n\n<p>\ucef4\ud3ec\uc988\ub294 Text\uc640 \uac19\uc740 \uc77c\ubd80 \ucef4\ud3ec\uc800\ube14\uc5d0\uc11c \uc774\ub7ec\ud55c Semantics \uc18d\uc131\uc744 \uc790\ub3d9\uc73c\ub85c \ub178\ucd9c\ud55c\ub2e4. \ud558\ub098 \ub610\ub294 \uc5ec\ub7ec \ud558\uc704 \ud56d\ubaa9\uc73c\ub85c \uad6c\uc131\ub41c \ub2e8\uc77c \ucef4\ud3ec\ub10c\ud2b8\ub97c \ub098\ud0c0\ub0b4\ub3c4\ub85d \ucee4\uc2a4\ud130\ub9c8\uc774\uc9d5\ud558\uace0 \ubcd1\ud569\ud560 \uc218\ub3c4 \uc788\ub2e4. \uc608\ub97c \ub4e4\uc5b4, Text \ucef4\ud3ec\uc800\ube14\uc744 \ud3ec\ud568\ud558\ub294 Button\uc744 \ub098\ud0c0\ub0bc \uc218 \uc788\ub2e4. MergeDescendants = &#8216;true&#8217; \uc18d\uc131\uc740 <strong>\uc774 \ub178\ub4dc\uc5d0 \ud558\uc704 \ud56d\ubaa9\uc774 \uc788\uc9c0\ub9cc \ubcd1\ud569\ub418\uc5c8\uc74c<\/strong>\uc744 \uc54c\ub824\uc900\ub2e4. \ud14c\uc2a4\ud2b8\uc5d0\uc11c \uc6b0\ub9ac\ub294 \uc885\uc885 \ubaa8\ub4e0 \ub178\ub4dc\uc5d0 \uc561\uc138\uc2a4\ud574\uc57c \ud55c\ub2e4.<\/p>\n\n\n\n<p>\ud0ed \uc548\uc758 Text\uac00 \ud45c\uc2dc\ub418\ub294\uc9c0 \uc5ec\ubd80\ub97c \ud655\uc778\ud558\uae30 \uc704\ud574, \ubcd1\ud569\ub418\uc9c0 \uc54a\uc740 \uc2dc\ub9e8\ud2f1 \ud2b8\ub9ac\ub97c \ucffc\ub9ac\ud558\uc5ec useUnmergedTree = true\ub97c onRoot\ub77c\ub294 finder\uc5d0 \uc804\ub2ec\ud560 \uc218 \uc788\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<strong>\u00a0 \u00a0 composeTestRule.onRoot(useUnmergedTree = true).printToLog(\"currentLabelExists\")<\/strong>\n\n}<\/code><\/pre>\n\n\n\n<p>\uc9c0\uae08 \ub85c\uadf8\ucea3\uc5d0 \ucd9c\ub825\ub41c \ub0b4\uc6a9\uc740 \uc57d\uac04 \ub354 \uae38\ub2e4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Printing with useUnmergedTree = 'true'<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; | &nbsp;|-Node #9 at (l=284.0, t=105.0, r=468.0, b=154.0)px<br>&nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp;Text = 'ACCOUNTS'<br>&nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp;Actions = &#91;GetTextLayoutResult]<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<p>\ub178\ub4dc #3\uc5d0\ub294 \uc5ec\uc804\ud788 \ud558\uc704 \ud56d\ubaa9\uc774 \uc5c6\ub2e4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&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'<\/code><\/pre>\n\n\n\n<p>\uadf8\ub7ec\ub098 \uc120\ud0dd\ud55c \ud0ed\uc778 \ub178\ub4dc 6\uc5d0\ub294 \ud558\ub098\uac00 \uc788\uc73c\uba70, \uc774\uc81c &#8216;Text&#8217; \uc18d\uc131\uc744 \ubcfc \uc218 \uc788\ub2e4.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&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; | &nbsp;|-Node #9 at (l=284.0, t=105.0, r=468.0, b=154.0)px<br>&nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp;Text = 'ACCOUNTS'<br>&nbsp; &nbsp; &nbsp; &nbsp; | &nbsp; &nbsp;Actions = &#91;GetTextLayoutResult]<\/code><\/pre>\n\n\n\n<p>\uc6b0\ub9ac\uac00 \uc6d0\ud558\ub294 \ub300\ub85c \uc62c\ubc14\ub978 \ub3d9\uc791\uc744 \ud655\uc778\ud558\uae30 \uc704\ud574 &#8220;ACCOUNTS&#8221;\ub77c\ub294 \ud14d\uc2a4\ud2b8\uac00 \uc788\ub294 \ub178\ub4dc \ud558\ub098\ub97c \ucc3e\ub294 matcher\ub97c \uc791\uc131\ud558\uc790. \uc774 \ub178\ub4dc\ub294 \ucf58\ud150\uce20 \uc124\uba85(content description) &#8220;Accounts&#8221;\ub97c \uac00\uc9c4 \uc0c1\uc704 \ub178\ub4dc\ub2e4.<\/p>\n\n\n\n<p><a href=\"https:\/\/developer.android.com\/jetpack\/compose\/testing-cheatsheet?hl=ko\">Compose Testing Cheat Sheet<\/a>\ub97c \ub2e4\uc2dc \ud655\uc778\ud558\uace0, \ud574\ub2f9 matcher\ub97c \uc791\uc131\ud558\ub294 \ubc29\ubc95\uc744 \ucc3e\uc544\ubcf4\uc790. <em>and<\/em> \ubc0f <em>or<\/em>\uc640 \uac19\uc740 Boolean \uc5f0\uc0b0\uc790\ub97c matcher\uc640 \ud568\uaed8 \uc0ac\uc6a9\ud560 \uc218 \uc788\ub2e4.<\/p>\n\n\n\n<p>\ubaa8\ub4e0 finder\uc5d0\ub294 useUnmergedTree\ub77c\ub294 \ub9e4\uac1c\ubcc0\uc218\uac00 \uc788\ub2e4. \ubcd1\ud569\ub418\uc9c0 \uc54a\uc740 \ud2b8\ub9ac\ub97c \uc0ac\uc6a9\ud558\ub824\uba74 true\ub85c \uc124\uc815\ud558\uc790.<\/p>\n\n\n\n<p>\ucd5c\uc885 \ucf54\ub4dc(Solution)\uc744 \ubcf4\uc9c0 \uc54a\uace0 \ud14c\uc2a4\ud2b8\ub97c \ud55c\ubc88 \uc791\uc131\ud574\ubcf4\uc790.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\ucd5c\uc885 \ucf54\ub4dc<\/h2>\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 .onNode(\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 hasText(RallyScreen.Accounts.name.uppercase(Locale.getDefault())) and\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 hasParent(\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 hasContentDescription(RallyScreen.Accounts.name)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 ),\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 useUnmergedTree = true\n\u00a0 \u00a0 \u00a0 \u00a0 )\n\u00a0 \u00a0 \u00a0 \u00a0 .assertExists()\n}<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote\"><p><strong>Note<\/strong>: \uc774 \uacbd\uc6b0 \ub9e4\uc6b0 \uaca9\ub9ac\ub41c \ud14c\uc2a4\ud2b8\uc774\uae30 \ub54c\ubb38\uc5d0, \uc5c4\uaca9\ud558\uac8c matcher\uc5d0 \uc0c1\uc704\uc694\uc18c\ub97c \ucd94\uac00\ud560 \ud544\uc694\uac00 \uc5c6\ub2e4. \uadf8\ub7ec\ub098 \ub354 \ud070 \ud14c\uc2a4\ud2b8\uc5d0\uc11c \uc2e4\ud328\ud560 \uc218 \uc788\ub294 \uad11\ubc94\uc704\ud55c finder(\uc608: hasText)\ub97c \ub2e8\ub3c5\uc73c\ub85c \uc0ac\uc6a9\ud558\ub294 \uac83\uc740 \ud53c\ud558\ub294 \uac83\uc774 \uc88b\ub2e4(\ud14d\uc2a4\ud2b8\uc758 \ub2e4\ub978 \uc778\uc2a4\ud134\uc2a4\ub97c \ucc3e\uc744 \uc218 \uc788\ub294 \uacbd\uc6b0).<\/p><\/blockquote>\n\n\n\n<p>\uc774\uc81c \ud14c\uc2a4\ud2b8\ub97c \uc2e4\ud589\ud574\ubcf4\ub3c4\ub85d \ud558\uc790.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/developer.android.com\/codelabs\/jetpack-compose-testing\/img\/377261a7b0db6354.png?hl=ko\" alt=\"377261a7b0db6354.png\"\/><\/figure>\n\n\n\n<p>\ucd95\ud558! \uc774 \ub2e8\uacc4\uc5d0\uc11c\ub294 \uc18d\uc131 \ubcd1\ud569\uacfc \ubcd1\ud569 \ubc0f \ubcd1\ud569 \ud574\uc81c \uc2dc\ub9e8\ud2f1 \ud2b8\ub9ac\uc5d0 \ub300\ud574 \ubc30\uc6e0\ub2e4.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\uc2dc\ub9e8\ud2f1 \ud2b8\ub9ac(Semantics tree)\ub294 \ud56d\uc0c1 \uad00\ub828 \uc815\ubcf4\ub9cc \ud45c\uc2dc\ud558\uc5ec \ucd5c\ub300\ud55c \uac04\uacb0\ud558\uac8c \ub9cc\ub4e4\ub824\uace0 \ud55c\ub2e4. \uc608\ub97c \ub4e4\uc5b4, TopAppBar\uc5d0\uc11c\ub294 \uc544\uc774\ucf58\uacfc \ub808\uc774\ube14\uc774 \ub2e4\ub978 \ub178\ub4dc\uc77c \ud544\uc694\uac00 \uc5c6\ub2e4. &#8220;Overview&#8221; \ub178\ub4dc\ub97c \uc0b4\ud3b4\ubcf4\uc790. \uc774 \ub178\ub4dc\uc5d0\ub294 selectable \ucef4\ud3ec\ub10c\ud2b8\uc5d0 \ub300\ud574 \ud2b9\ubcc4\ud788 \uc815\uc758\ub41c \uc18d\uc131(\uc608: Selected \ubc0f Role)\uacfc \uc804\uccb4 \ud0ed\uc5d0 \ub300\ud55c \ucf58\ud150\uce20 \uc124\uba85(content description)\uc774 \uc788\ub2e4. \uc774\ub294 \uac04\ub2e8\ud55c \ud14c\uc2a4\ud2b8\uc5d0 \ub9e4\uc6b0 \uc720\uc6a9\ud55c \uace0\uc218\uc900(high-level) \uc18d\uc131\uc774\ub2e4. \uc544\uc774\ucf58\uc774\ub098 \ud14d\uc2a4\ud2b8\uc5d0 [&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\/45762"}],"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=45762"}],"version-history":[{"count":1,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/45762\/revisions"}],"predecessor-version":[{"id":45763,"href":"https:\/\/charlezz.com\/index.php?rest_route=\/wp\/v2\/posts\/45762\/revisions\/45763"}],"wp:attachment":[{"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=45762"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=45762"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/charlezz.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=45762"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}