我一直在尝试按照有关让 Spoon 1.1.14 为未通过的 Espresso 测试截屏的说明进行操作。
使用自定义 Espresso FailureHandler进行配置的最佳方法是什么?
我一直在尝试按照有关让 Spoon 1.1.14 为未通过的 Espresso 测试截屏的说明进行操作。
使用自定义 Espresso FailureHandler进行配置的最佳方法是什么?
这是我目前的做法:
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;
}
}
}
}
...这是Square 发布的 Gist中稍作修改的屏幕截图代码:
/**
* 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()
每次测试都调用 - 如果您对如何避免这种情况有一个好主意,请告诉我!
基于上面@Eric的方法,通过ActivityTestRuledescription
我们可以在调用函数时从对象中获取当前的测试方法名和测试类名apply()
。通过像这样覆盖 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)
到你的测试班。
你可以尝试在你的子类中设置它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
永远是正确的。我获取这些的方式似乎非常脆弱,但我想不出更好的方法。
用自定义的替换 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示例:
点此查看官方示例