我一直在尝试关注the instructions on getting Spoon 1.1.14 to take screenshots for failing Espresso tests。
使用custom Espresso FailureHandler配置此功能的最佳方式是什么?
答案 0 :(得分:7)
以下是我现在这样做的方式:
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()
的方法 - 如果您对如何避免这种情况有好的想法,请告诉我!
答案 1 :(得分:7)
基于@ Eric的上述方法,使用ActivityTestRule,我们可以在调用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 runner@RunWith(AndroidJUnit4.class)
到你的考试班。
答案 2 :(得分:2)
您可以尝试在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();
}
}
我不确定testClassName
和testMethodName
总是是否正确。我拿这些的方式似乎超级脆弱,但我无法找到更好的方法。
答案 3 :(得分:0)
使用自定义版本替换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