【问题标题】:How to get Spoon to take screenshots for Espresso tests?如何让 Spoon 为 Espresso 测试截屏?
【发布时间】:2015-04-28 20:16:13
【问题描述】:

【问题讨论】:

    标签: android android-espresso spoon


    【解决方案1】:

    这是我目前的做法:

    public class MainScreenTest extends BaseStatelessBlackBoxEspressoTest<LaunchActivity> {
    
        public MainScreenTest() {
            super(LaunchActivity.class);
        }
    
        public void testMainScreen() {
            // Unfortunately this must be explicitly called in each test :-(
            setUpFailureHandler();
    
            onView(withId(R.id.main_circle)).
                    check(matches(isDisplayed()));
        }
    }
    

    我的基本 Espresso 测试类设置了自定义的 FailureHandler(我喜欢使用基类来保存许多其他常见代码):

    public abstract class BaseStatelessBlackBoxEspressoTest<T extends Activity> extends BaseBlackBoxTest<T> {
    
        public BaseStatelessBlackBoxEspressoTest(Class clazz) {
            super(clazz);
        }
    
        @Before
        public void setUp() throws Exception {
            super.setUp();
            getActivity();
        }
    
        public void setUpFailureHandler() {
            // Get the test class and method.  These have to match those of the test
            // being run, otherwise the screenshot will not be displayed in the Spoon 
            // HTML output.  We cannot call this code directly in setUp, because at 
            // that point the current test method is not yet in the stack.
            StackTraceElement[] trace = Thread.currentThread().getStackTrace();
            String testClass = trace[3].getClassName();
            String testMethod = trace[3].getMethodName();
    
            Espresso.setFailureHandler(new CustomFailureHandler(
                    getInstrumentation().getTargetContext(),
                    testClass,
                    testMethod));
        }
    
        private static class CustomFailureHandler implements FailureHandler {
            private final FailureHandler mDelegate;
            private String mClassName;
            private String mMethodName;
    
            public CustomFailureHandler(Context targetContext, String className, String methodName) {
                mDelegate = new DefaultFailureHandler(targetContext);
                mClassName = className;
                mMethodName = methodName;
            }
    
            @Override
            public void handle(Throwable error, Matcher<View> viewMatcher) {
                try {
                    mDelegate.handle(error, viewMatcher);
                } catch (Exception e) {
                    SpoonScreenshotAction.perform("espresso_assertion_failed", mClassName, mMethodName);
                    throw e;
                }
            }
        }
    }
    

    ...这里是来自the Gist posted by Square的稍作修改的截图代码:

    /**
     * Source: https://github.com/square/spoon/issues/214#issuecomment-81979248
     */
    public final class SpoonScreenshotAction implements ViewAction {
        private final String tag;
        private final String testClass;
        private final String testMethod;
    
        public SpoonScreenshotAction(String tag, String testClass, String testMethod) {
            this.tag = tag;
            this.testClass = testClass;
            this.testMethod = testMethod;
        }
    
        @Override
        public Matcher<View> getConstraints() {
            return Matchers.anything();
        }
    
        @Override
        public String getDescription() {
            return "Taking a screenshot using spoon.";
        }
    
        @Override
        public void perform(UiController uiController, View view) {
            Spoon.screenshot(getActivity(view), tag, testClass, testMethod);
        }
    
        private static Activity getActivity(View view) {
            Context context = view.getContext();
            while (!(context instanceof Activity)) {
                if (context instanceof ContextWrapper) {
                    context = ((ContextWrapper) context).getBaseContext();
                } else {
                    throw new IllegalStateException("Got a context of class "
                            + context.getClass()
                            + " and I don't know how to get the Activity from it");
                }
            }
            return (Activity) context;
        }    
    
        public static void perform(String tag, String className, String methodName) {
            onView(isRoot()).perform(new SpoonScreenshotAction(tag, className, methodName));
        }
    }
    

    我很想找到一种方法来避免在每次测试中调用 setUpFailureHandler() - 如果您对如何避免这种情况有好的想法,请告诉我!

    【讨论】:

      【解决方案2】:

      基于上面@Eric的方法,通过ActivityTestRule我们可以在调用apply()函数时从description对象中获取当前的测试方法名和测试类名。通过像这样覆盖 apply 函数

      public class MyActivityTestRule<T extends Activity> extends ActivityTestRule<T> {
      
        @Override
        public Statement apply(Statement base, Description description) {
          String testClassName = description.getClassName();
          String testMethodName = description.getMethodName();
          Context context =  InstrumentationRegistry.getTargetContext();
          Espresso.setFailureHandler(new FailureHandler() {
            @Override public void handle(Throwable throwable, Matcher<View> matcher) {
              SpoonScreenshotAction.perform("failure", testClassName, testMethodName);
              new DefaultFailureHandler(context).handle(throwable, matcher);
              }
          });
          return super.apply(base, description);
        }
      
        /* ... other useful things ... */
      }
      

      我能够使用正确的测试方法和测试类截取屏幕截图,以便将其正确集成到最终的 Spoon 测试报告中。并记住通过添加来使用 JUnit4 运行器

      @RunWith(AndroidJUnit4.class)
      

      到你的测试班。

      【讨论】:

      • SpoonScreenshotAction 的使用本身会触发 Espresso FailureHandler,例如:没有找到活动。这很糟糕,因为这会导致无限循环,从而导致 OutOfMemory 错误。最好直接使用 Spoon.screenshot():Spoon.screenshot(getActivity(), tag, testClassName, testMethodName); 而不是 SpoonScreenshotAction.perform();
      【解决方案3】:

      您可以尝试在 ActivityRule 的子类中进行设置。类似的东西

      return new Statement() {
        @Override public void evaluate() throws Throwable {
          final String testClassName = description.getTestClass().getSimpleName();
          final String testMethodName = description.getMethodName();
          Instrumentation instrumentation = fetchInstrumentation();
          Context context = instrumentation.getTargetContext();
          Espresso.setFailureHandler(new FailureHandler() {
            @Override public void handle(Throwable throwable, Matcher<View> matcher) {
              SpoonScreenshotAction.perform("failure", testClassName, testMethodName);
              new DefaultFailureHandler(context).handle(throwable, matcher);
            }
          });
          base.evaluate();
        }
      } 
      

      我不确定testClassNametestMethodName 是否总是正确。我获取这些的方法似乎非常脆弱,但我想不出更好的方法。

      【讨论】:

        【解决方案4】:

        用自定义的替换 Espresso 的默认 FailureHandler 允许额外的错误处理,例如截图:

        private static class CustomFailureHandler implements FailureHandler {
        @Override
          public void handle(Throwable error, Matcher<View> viewMatcher) {
            throw new MySpecialException(error);
        }
          }
          private static class MySpecialException extends RuntimeException {
          MySpecialException(Throwable cause) {
             super(cause);
            }
        }
        

        此外,您需要在测试设置和拆卸中抛出自定义异常:

         @Override
           public void setUp() throws Exception {
           super.setUp();
           getActivity();
           setFailureHandler(new CustomFailureHandler());
          }
        
        @Override
          public void tearDown() throws Exception {
          super.tearDown();
          Espresso.setFailureHandler(new DefaultFailureHandler(getTargetContext()));
          }
        

        您可以在 Espresso 测试中使用它,例如:

        public void testWithCustomFailureHandler() {
          try {
          onView(withText("does not exist")).perform(click());
        } catch (MySpecialException expected) {
          Log.e(TAG, "Special exception is special and expected: ", expected);
          }
        }
        

        请看Android官方的CustomFailure例子:
        Click here for the official example

        Click here for another example

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多