요즘 컴포즈로 넘어가고 있고 xml은 레거시 뷰 취급이지만
아직 까지는 xml 기반의 뷰가 많을 것입니다.
컴포즈로 새로운 뷰를 만든다 하더라도 기존 xml 뷰도 유지보수 해줘야겠죠?
espresso를 이용해서 아주 간단한 UI 테스트를 보여드리겠습니다.
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
사실 의존성은 기본적으로 추가 되어있을 것이기 때문에 굳이 추가 안해주셔도 됩니다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/searchBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@id/search_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/search_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/search_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="nothing.."
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
아~~주 간단한 뷰죠?
초기화면입니다.
검색창에 test를 치고서 버튼을 누르면?
결과에 test가 땋!~
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.searchButton.setOnClickListener {
val input = binding.searchBar.text.toString()
binding.searchResult.text = input
binding.searchBar.text.clear()
}
}
}
뷰 바인딩으로 작성했습니다.
onView().perform().check()
ViewMatcher.ViewAction.ViewAssertion
어떤 뷰에서 어떤 행동을 하고 특정 조건을 검증한다.
아주 간단하죠?
class SearchFeatureTest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class.java)
@Test
fun `초기화면은_nothing을_보여줘야_한다`() {
onView(withId(R.id.searchBar)).check(matches(withText("")))
onView(withId(R.id.search_button)).check(matches(withText("search")))
onView(withId(R.id.search_result)).check(matches(withText("nothing..")))
}
@Test
fun searchBar에_test_를_입력했을_때_test_를_띄워준다() {
Thread.sleep(1000)
onView(withId(R.id.searchBar)).perform(typeText("test")).check(matches(withText("test")))
Thread.sleep(1000)
onView(withId(R.id.search_button)).perform(click())
onView(withId(R.id.searchBar)).check(matches(withText("")))
Thread.sleep(1000)
onView(withId(R.id.search_result)).check(matches(withText("test")))
Thread.sleep(1000)
}
}
중간중간에 Thread.sleep 을 넣은 이유는 UI 동작을 확인하기 위해서입니다.
ActivityScenarioRule 을 통해 MainActivty를 Test하도록 설정합시다.
searchBar에test를입력했을때test를_띄워준다 이 테스트 함수를 보도록 합시다.
onView(withId(R.id.searchBar)).perform(typeText("test")).check(matches(withText("test")))
onView(withId(R.id.search_button)).perform(click())
onView(withId(R.id.searchBar)).check(matches(withText("")))
onView(withId(R.id.search_result)).check(matches(withText("test")))
한번 풀어서 봅시다.
onView(withId(R.id.search_result)).check(matches(withText("test")))
R.id.search_result 란 Id를 가진 뷰에서 "test" 란 텍스트를 가지고 있는지 check 한다.
느낌 오시나요?
Thread.sleep을 하지 않는다면 순식간에 테스트가 완료돼서 UI 테스트 과정을 확인하기 힘듭니다.
요즘 compose 밖에 안쓰지만 espresso 사용법을 익히는 것도
좋다고 생각합니다.
기존 앱도 유지보수 해야죠 ^^
https://developer.android.com/training/testing/espresso/basics?hl=ko#finding-view-considerations
espresso cheatsheet <-- 꼭 보세요!