espresso_espresso的进度栏动画

espresso

It is not uncommon that once you run into an issue with Intrumentation Testing in Android, you’d feel like the loneliest person in the world. This might be an unforeseen effect of abundance of Android knowledge that exists on the Internet. Whatever you search for, there is always a StackOverflow answer or a Medium article that exactly describes what you need to do. However, this doesn’t hold for testing. My assumption is that this trend would hold in many other areas of Software Engineering. Although TDD is being popularized, it is still fairly rare, especially among one-developer teams which is unfortunately very common in mobile teams.

一旦遇到Android中的Intrumentation Testing问题,您就会感觉自己是世界上最孤独的人,这并不罕见。 这可能是互联网上存在的大量Android知识无法预料的结果。 无论您要搜索的是什么,总会有一个StackOverflow答案或一个中篇文章准确地描述您需要做什么。 但是,这不适用于测试。 我的假设是,这种趋势将在软件工程的许多其他领域保持下去。 尽管TDD正在普及,但它仍然相当罕见,尤其是在一个开发人员团队中,不幸的是,TDD在移动团队中非常普遍。

问题:浓咖啡中的动画 (Problem: Animations in Espresso)

Espresso has quite a bit obsession with idleness. Any action or assertion on views first require everything to settle on UI. In other words, if anything is still moving on your screen, Espresso will happily wait before executing any command. This doesn’t necessarily cause a problem if we are talking about transition or window animations that take fraction of a second. However, what happens if we want to show a ProgressBar or some sort of drawable that is animated when the screen goes into a loading state.

浓咖啡对闲置有一定的迷恋。 对视图的任何操作或断言首先都需要一切都取决于UI。 换句话说,如果屏幕上仍然有任何东西在移动,Espresso将欣然等待执行任何命令。 如果我们谈论的过渡或窗口动画只需要几分之一秒,这并不一定会引起问题。 但是,如果我们要显示一个ProgressBar或某种在屏幕进入加载状态时动画化的drawable会发生什么。

with(loginRobot) {
    typeEmail("example@example.com")
    typePassword("hunter2")
    clickLogin()
    // Following line will never execute because 
    // clickLogin is going to reveal a ProgressBar
    assertViewEnabled(R.id.login_button, false)
}
// ----------------------------------------------------
// androidx.test.espresso.AppNotIdleException: Looped for 10746 iterations over 60 SECONDS. 
// The following Idle Conditions failed.

Wow, that’s not pretty. Espresso looped around 10k times and finally gave up after a minute. What can we do to circumvent this issue and tell the Espresso that the screen is actually idle?

哇,那不漂亮。 Espresso循环播放约1万次,一分钟后终于放弃。 我们可以采取什么措施来规避此问题,并告诉Espresso屏幕实际上处于空闲状态?

拟议的解决方案 (Proposed Solutions)

Let’s do a quick Google search and see what we find

让我们快速进行Google搜索,看看我们发现了什么

  1. Replace the Progressbar right after test starts

    测试开始后立即替换进度栏

There is no accepted answer so we can start with the most upvoted one. It simply suggests that if your Progressbar is invisible at the start of the test(yes, in our case), just change its drawable with something static so it does not animate.

没有可接受的答案,因此我们可以从最被提议的答案开始。 它只是表明,如果您的进度栏在测试开始时不可见(在我们的示例中是),则只需使用静态值更改其drawable即可使其不动画。

Although this may sound very legit and works, it doesn’t feel right to me. First of all, there is a big assumption that my test will start without showing the progressbar. When this doesn’t hold, the whole approach fails. Secondly, it requires me to mess with UI during testing. Not before, not after, but exactly at the time of running the test. This hack sounds innocent but violates some of the principles of UI testing.

尽管这听起来很合法并且行得通,但对我来说感觉不合适。 首先,有一个很大的假设,即我的测试将在不显示进度条的情况下开始。 如果不成立,则整个方法将失败。 其次,它要求我在测试过程中弄乱UI。 不在测试之前,之后,而是在运行测试时。 这种骇听听起来是无辜的,但却违反了UI测试的某些原理。

2. Override Progressbar for Debug builds (or for any flavor)

2.覆盖用于调试版本(或其他任何版本)的进度条

This was the first thing that also I thought of. Create a new ProgressBar, extending from the original ProgressBar, and stop the animation in Debug builds.

这也是我想到的第一件事。 创建一个从原始ProgressBar扩展的新ProgressBar,并在Debug版本中停止动画。

I wouldn’t recommend directly doing this because debug builds are intended to be used by developers during hands on testing or development time, in which animations should not be disabled for any reason. Luckily, we have a flavor of our app that is intended for instrumentation testing. Instead of debug builds, we can disable the animations according to this flavor.

我不建议直接执行此操作,因为调试版本旨在供开发人员在动手测试或开发期间使用,在这种情况下,不应出于任何原因禁用动画。 幸运的是,我们拥有适合仪表测试的应用程序风格。 除了调试版本,我们可以根据这种风格禁用动画。

override fun setIndeterminateDrawable(Drawable d) {
  if (isRunningTest()) {
    val newD = getResources().getDrawable(R.drawable.ic_replay);
    super.setIndeterminateDrawable(newD)
  } else {
    super.setIndeterminateDrawable(d)
  }
}
fun isRunningTest(): Boolean {
  try {
      Class.forName("base.EspressoTestBase")
      return true
  } catch (ClassNotFoundException e) {
      /* no-op */
  }
  return false
}

Again like the first one, this approach works if you are OK with it. My concern is that changing production code and adding logic to circumvent a testing issue might cause unpredictable side-affects. Also, it’s again in violation of core principles of UI testing.

再次像第一个一样,如果您同意的话,这种方法也可以使用。 我担心的是,更改生产代码并添加逻辑来规避测试问题可能会导致无法预料的副作用。 而且,它再次违反了UI测试的核心原则。

3. Others

3.其他

As far as I checked, all the other solutions are derivatives of the first two that we have discussed. Either intercept during testing and alter the offending drawable, or change production code to account for testing. In some cases, people have given up completely and maybe stopped testing these screens.

据我检查,所有其他解决方案都是我们所讨论的前两个解决方案的派生产品。 在测试期间进行拦截并更改有问题的可绘制对象,或者更改生产代码以进行测试。 在某些情况下,人们已经完全放弃甚至可能停止测试这些屏幕。

In the above answer, the author suggested the use of UiAutomator. If Espresso can’t handle idleness of a progress drawable, we can simply take the control from Espresso and give it to another library with similar functionality.

在以上答案中,作者建议使用UiAutomator。 如果Espresso无法处理可绘制进度的空闲状态,我们可以简单地从Espresso中获取控件并将其提供给具有类似功能的另一个库。

另一个可能性和其他动画绘图 (Another Possibility and Other Animated Drawables)

Until now, I haven’t even mentioned my original problem. To our bad luck, the problematical view is not even a ProgressBar or derivative of it. A custom button view that shows a Progress animation when its state switches to Loading . The drawable inside it is private so the first solution is out the window. The second solution is also out-ruled because we don’t want to interfere with production code logic.

到目前为止,我什至没有提到我的原始问题。 不幸的是,有问题的视图甚至不是它的ProgressBar或派生类。 一个自定义按钮视图,当其状态切换为Loading时显示进度动画。 内部的可绘制对象是私有的,因此第一个解决方案不在窗口之内。 第二种解决方案也不适用,因为我们不想干扰生产代码逻辑。

Fortunately, there is something missing in Stackoverflow answers. Do you remember a line that 99% exists in your build.gradle file?

幸运的是,Stackoverflow答案中缺少某些内容。 您还记得build.gradle文件中存在99%的行吗?

testInstrumentationRunner "com.example.hello.HelloTestRunner"

This custom test runner has incredible capabilities when it comes to meddling with your app during testing. For example you can override every activity’s oncreate . There, you can utilize custom Layout Inflater Factories to your liking.

当涉及在测试过程中与您的应用程序打交道时,此自定义测试运行器具有令人难以置信的功能。 例如,您可以覆盖每个活动的oncreate 。 在这里,您可以根据自己的喜好使用自定义的充气机工厂

override fun callActivityOnCreate(activity: Activity?, bundle: Bundle?) {
    if (activity is AppCompatActivity) {
        LayoutInflaterCompat.setFactory2(activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater,
            NoAnimationInflaterFactory(activity.delegate))
    }


    super.callActivityOnCreate(activity, bundle)
}

Now, we can just swap any view with another stub implementation that simply disables animations during testing.

现在,我们可以将任何视图与另一个存根实现交换,该存根实现只是在测试期间禁用动画。

class HelloTestRunner : AndroidJUnitRunner() {


    override fun newApplication(cl: ClassLoader, className: String, context: Context): Application {
        return super.newApplication(cl, TestHelloApplication::class.java.name, context)
    }


    override fun callActivityOnCreate(activity: Activity?, bundle: Bundle?) {
        if (activity is AppCompatActivity) {
            LayoutInflaterCompat.setFactory2(activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater,
                NoAnimationInflaterFactory(activity.delegate))
        }


        super.callActivityOnCreate(activity, bundle)
    }


    private class NoAnimationInflaterFactory(private val appCompatDelegate: AppCompatDelegate) : LayoutInflater.Factory2 {


        override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? =
            when (name) {
                CustomProgressButton::class.java.name -> {
                    StubCustomProgressButton(context, attrs)
                }
                else -> appCompatDelegate.createView(null, name, context, attrs)
            }


        override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? =
            onCreateView(null, name, context, attrs)


        private class StubCustomProgressButton @JvmOverloads constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int = 0) : CustomProgressButton(context, attrs, defStyleAttr) {


            override fun startAnimation(animation: Animation) {
                // no-op
            }
        }
    }
}

This method doesn’t care about your actual implementation. It can be thought of as a TestRule . When your test starts, your activities’ inflater factories are swapped with this class. The view classes that you are not interested in modifying will keep inflating as always but the ones you want to swap out will be changed with the new Stub implementations. In these stub implementations you can disable animations and do whatever you want as long as you are respecting the fundamental rules of instrumentation testing.

此方法不关心您的实际实现。 可以将其视为TestRule 。 当测试开始时,您的活动的充气工厂将被替换为此类。 您不希望修改的视图类将一如既往地膨胀,但是您要换出的视图类将通过新的Stub实现进行更改。 在这些存根实现中,您可以禁用动画并做任何您想做的事情,只要您遵守仪器测试的基本规则即可。

翻译自: https://proandroiddev.com/progressbar-animations-with-espresso-57f826102187

espresso

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值