我一直在努力追随 获取Spoon 1.1.14获取失败的Espresso测试截图的说明。
用a配置它的最佳方法是什么? 自定义Espresso FailureHandler?
我一直在努力追随 获取Spoon 1.1.14获取失败的Espresso测试截图的说明。
用a配置它的最佳方法是什么? 自定义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发布的要点:
/**
* 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的上述方法,以及 ActivityTestRule 我们可以从中获取当前的测试方法名称和测试类名称 description
对象何时 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 runner
@RunWith(AndroidJUnit4.class)
到你的考试班。
这就是我现在这样做的方式:
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发布的要点:
/**
* 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的上述方法,以及 ActivityTestRule 我们可以从中获取当前的测试方法名称和测试类名称 description
对象何时 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 runner
@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替换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示例:
点击此处查看官方示例