Я пытаюсь выполнить инструкции по загрузке 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;
}
}
}
}
... и вот немного измененный код скриншота из Gist, отправленный 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, добавив
@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
всегда будут правильными. То, как я получаю их, кажется супер-хрупким, но я не мог найти лучшего способа.
Замена стандартного 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()));
}
Вы можете использовать это в своем тесте эспрессо, например:
public void testWithCustomFailureHandler() {
try {
onView(withText("does not exist")).perform(click());
} catch (MySpecialException expected) {
Log.e(TAG, "Special exception is special and expected: ", expected);
}
}
Пожалуйста, ознакомьтесь с официальным примером CustomFailure для Android:
Нажмите здесь для официального примера