Espresso 테스트에서 배경에 border가 있는지 확인 Matcher

틈새 용도로 범용성은 없다고 생각하지만, 잘 가서 조금 기뻤기 때문에 메모.

문제



아래 그림과 같이 RecyclerView 기반 앱이 있으며 내부 상태에 따라 ViewHolder에 테두리를 붙이거나 붙이지 않습니다. 테두리가 없는 것은 background 에 다른 shape 를 지정하는 것으로 실현되고 있습니다. ( stroke 를 구현한 shape 에는 테두리가 붙어, stroke 가 없으면 붙습니다.)



그런데 기대했던 ViewHolder 이외에도 테두리가 그려진다는 버그가 발생했습니다. 아마 ViewHolder 재사용으로 인한 버그라고 생각합니다. 곧바로 고치면 됩니다만, 이것은 UI 테스트를 쓰는 곳일 것이라고 말하는 것으로, 처음으로 제대로 Espresso 테스트를 해 보았습니다.

그러나 이렇게 말하는 외형을 비교한다 Matcher 라고 말하는 것은 코어의 라이브러리에서는 제대로 준비되어 있지 않아서, 타사에서도 발견되지 않았기 때문에 스스로 써 보기로 했습니다. (초보자이므로 간과하고 있을 가능성도 크지만)

itemView.background


ViewHolderitemView.background 상태를 조사하는 것이므로, 그것이 몇 클래스에 속하고 있는지를 먼저 조사합니다. 디버깅하여 Chimachima ViewHolder 객체 트리를 추적합니다.



있었다! mBackground 속성은 GradientDrawable의 인스턴스이며,이 녀석에는 mStrokePaint라는 속성이 있습니다. 이 프로퍼티에 실체가 있으면 경계가 있고, null 이면 경계 없음입니다.

Matcher


Matcher 구현은 다음과 같습니다.
fun backgroundHasBorder(): Matcher<View> {
   return object : TypeSafeMatcher<View>(View::class.java!!) {

       override fun describeTo(description: Description) {
           description.appendText("to have a border")
       }

       override fun matchesSafely(foundView: View): Boolean {
           val gradientDrawable = (foundView.background as? GradientDrawable)
           if (gradientDrawable == null) {
               print("Background is not a gradient")
               return false
           }

           val field = (GradientDrawable::class.java.getDeclaredField("mStrokePaint"))
           field.isAccessible = true
           val stroke = field.get(gradientDrawable)
           return stroke != null
        }
    }
}
GradientDrawable.mStrokePoint 는 프라이빗 프로퍼티로, 이것을 공개하고 있는 메소드가 없기 때문에, 리플렉션으로 액세스하고 있습니다.

사용법



국경이있을 때 (때)
onView(withId(R.id.SomeId))
    .check(matches(backgroundHasBorder()))

테두리가 없을 때
onView(withId(R.id...))
    .check(not(matches(backgroundHasBorder())))

NOTE : 이번 앱의 경우 RecyclerViewViewHolder에 대해 평가하고 싶습니다. 있습니다. 1

감상


  • RecyclerView 로 정의한 ViewHolderwithId(...) 가 아니고, shape 가 된다고 하는 것은 의외였습니다. 확실히 rect 나 ellipse 기반이 아닌 더 복잡한 background를 정의하면 ShapeDrawable 될 것입니까?
  • 이 근처는 버전에 있어서의 구현의 차이가 무섭지만, 일단 API level 28 (Pie) 와 API level 19 (Kitkat)로 테스트했는데, 기대대로 움직였습니다.

  • Java나 Kotlin에는 reflection가 있으므로, 이런 GradientDrawableshape를 쓰거나 하는 것은, 조사만 하면 원 패스는 통과할 수 있다고 하는 느낌이 있습니다. 다만 Proguard를 쓰면 확실히 빠질 것 같다.



  • 일반적으로 ShapeDrawable 액세스가 생각했던 것보다 힘들고 빠졌습니다. 그 밖에도 방법은 있을지도 모릅니다만 RecyclerViewMatcher 라고 하는 것을 카피해 와서 사용했습니다. 이것을 사용하는 경우, View 의 Matcher 에는 View로 액세스하는 것이 아니라, Matcher로 액세스합니다. 

    좋은 웹페이지 즐겨찾기