configuración
modificar la configuración
Primero habilite las opciones de desarrollador y luego, en las opciones de desarrollador, deshabilite las siguientes tres configuraciones:
- Escalado de animación de ventana
- escalado de animación de transición
- Escalado de duración del animador
agregar dependencias
app/build.gradle
Agregar dependencias al archivo
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
En app/build.gradle
el archivo android.defaultConfig
agregar
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Nota: Las dependencias anteriores solo pueden lograr funciones básicas. Si desea utilizar todas las funciones, configure de la siguiente manera:
todas las dependencias
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.ext:truth:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.test.espresso:espresso-idling-resource:3.2.0'
Los métodos llamados a continuación onView()
son métodos estáticos y pueden ser import static XXX
llamados directamente por Todos los métodos estáticos que deben importarse son los siguientes:
import static androidx.test.espresso.Espresso.*;
import static androidx.test.espresso.action.ViewActions.*;
import static androidx.test.espresso.assertion.ViewAssertions.*;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.Intents.intending;
import static androidx.test.espresso.intent.matcher.ComponentNameMatchers.*;
import static androidx.test.espresso.intent.matcher.IntentMatchers.*;
import static androidx.test.espresso.matcher.ViewMatchers.*;
import static androidx.test.ext.truth.content.IntentSubject.assertThat;
componente API
Los componentes de API comúnmente utilizados incluyen:
- Espresso: punto de entrada para interactuar con las vistas (a través de onView() y onData()). Además, se exponen las API como pressBack() que no están necesariamente asociadas con ninguna vista.
- ViewMatchers: una colección de objetos que implementan la interfaz Matcher<?super View>. Puede pasar uno o más de estos objetos al método onView() para encontrar una vista en la jerarquía de vistas actual.
- ViewActions: una colección de objetos ViewAction (como click() ) que se pueden pasar al método ViewInteraction.perform().
- ViewAssertions: una colección de objetos ViewAssertion que se pueden pasar al método ViewInteraction.check(). En la mayoría de los casos, usará aserciones de coincidencias, que usan comparadores de vista para afirmar el estado de la vista actualmente seleccionada.
La mayoría de las instancias de Matcher, ViewAction y ViewAssertion disponibles son las siguientes (documentos oficiales de origen):
Instancias comunes de api pdf
usar
mando común
Ejemplo: MainActivity
Contiene un Button
y un TextView
. Después de hacer clic en el botón, TextView
el contenido de cambiará a "Cambiado con éxito" .
Úselo Espresso
para probar de la siguiente manera:
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ChangeTextTest {
@Rule
public ActivityTestRule<MainActivity> activityRule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void test_change_text(){
onView(withId(R.id.change))
.perform(click());
onView(withId(R.id.content))
.check(matches(withText("改变成功")));
}
}
onView()
El método se utiliza para obtener la vista actual coincidente. Tenga en cuenta que solo puede haber una vista coincidente; de lo contrario, se informará un error.
withId()
El método se utiliza para buscar vistas coincidentes, similares withText()
, withHint()
etc.
perform()
El método se utiliza para realizar alguna acción, como hacer clic click()
, presionar prolongadamente longClick()
, hacer doble clicdoubleClick()
check()
Se utiliza para aplicar aserciones a la vista actualmente seleccionada
matches()
La aserción más utilizada, afirma el estado de la vista actualmente seleccionada. El ejemplo anterior es para afirmar si la Vista cuyo id es contenido coincide con la Vista cuyo texto es "cambio exitoso"
Controles relacionados con AdapterView
A diferencia de los controles normales, AdapterView
(comúnmente ListView
) solo se puede cargar un subconjunto de subvistas en la jerarquía de vista actual. Una búsqueda simple onView()
no encontrará vistas que no estén cargadas actualmente. Espresso
Proporciona un onData()
punto de entrada único que primero carga el elemento del adaptador relevante y lo enfoca antes de realizar operaciones en él o en cualquiera de sus elementos secundarios.
Ejemplo: abra Spinner
, seleccione una entrada específica y luego verifique TextView
que esa entrada esté incluida. Spinner
creará un que contenga su contenido ListView
, por lo que la necesidad deonData()
@RunWith(AndroidJUnit4.class)
@LargeTest
public class SpinnerTest {
@Rule
public ActivityTestRule<MainActivity> activityRule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void test_spinner(){
String content = "学校";
//点击Spnner,显示项目
onView(withId(R.id.change)).perform(click());
//点击指定的内容
onData(allOf(is(instanceOf(String.class)), is(content))).perform(click());
//判断TextView是否包含指定内容
onView(withId(R.id.content))
.check(matches(withText(containsString(content))));
}
}
La siguiente figura muestra el diagrama de relación de herencia de AdapterView:
Advertencia: si la implementación personalizada de AdapterView viola el contrato de herencia, puede haber problemas al usar el método onData() (especialmente la API getItem()). En este caso, el mejor curso de acción es refactorizar el código de la aplicación. Si no puede hacer esto, puede implementar un AdapterViewProtocol personalizado coincidente.
Comparador personalizado y ViewAction
Antes de presentar RecyclerView
las operaciones, echemos un vistazo a cómo personalizar Matcher
y ViewAction
.
personalizarMatcher
Matcher<T>
es una interfaz utilizada para hacer coincidir la vista, comúnmente se utilizan sus dos clases de implementación BoundedMatcher<T, S extends T>
yTypeSafeMatcher<T>
BoundedMatcher<T, S extends T>
: Algo de azúcar sintáctico coincidente que le permite crear una coincidencia de un tipo dado mientras empareja solo elementos de proceso de un subtipo particular.
Parámetros de tipo:<T> - 匹配器的期望类型。<S> - T的亚型
TypeSafeMatcher<T>
: implementa internamente verificaciones nulas, verifica el tipo y luego convierte
Ejemplo: Ingrese el valor EditText, si el valor comienza con , haga visible 000
el TextView con el contenido "éxito" , de lo contrario, haga visible el TextView con el contenido " fallo ".
@RunWith(AndroidJUnit4.class)
@LargeTest
public class EditTextTest {
@Rule
public ActivityTestRule<MainActivity> activityRule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void rightInput() {
onView(withId(R.id.editText))
.check(matches(EditMatcher.isRight()))
.perform(typeText("000123"), ViewActions.closeSoftKeyboard());
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.textView_success)).check(matches(isDisplayed()));
onView(withId(R.id.textView_fail)).check(matches(not(isDisplayed())));
}
@Test
public void errorInput() {
onView(withId(R.id.editText))
.check(matches(EditMatcher.isRight()))
.perform(typeText("003"), ViewActions.closeSoftKeyboard());
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.textView_success)).check(matches(not(isDisplayed())));
onView(withId(R.id.textView_fail)).check(matches(isDisplayed()));
}
static class EditMatcher{
static Matcher<View> isRight(){
//自定义Matcher
return new BoundedMatcher<View, EditText>(EditText.class) {
@Override
public void describeTo(Description description) {
description.appendText("EditText不满足要求");
}
@Override
protected boolean matchesSafely(EditText item) {
//在输入EditText之前,先判EditText是否可见以及hint是否为指定值
if (item.getVisibility() == View.VISIBLE &&
item.getText().toString().isEmpty())
return true;
else
return false;
}
};
}
}
}
personalizarViewAction
Esto no es muy familiar, aquí hay una introducción a la implementación de la interfaz ViewAction, la función del método a implementar.
/**
*符合某种限制的视图
*/
public Matcher<View> getConstraints();
/**
*返回视图操作的描述。 *说明不应该过长,应该很好地适应于一句话
*/
public String getDescription();
/**
* 执行给定的视图这个动作。
*PARAMS:uiController - 控制器使用与UI交互。
*view - 在采取行动的view。 不能为null
*/
public void perform(UiController uiController, View view);
}
RecyclerView
RecyclerView
Los objetos funcionan AdapterView
de manera diferente a los objetos, por lo que no puede onData()
interactuar con ellos utilizando los métodos.
Para interactuar Espresso
con RecyclerView
, puede usar espresso-contrib
el paquete, que tiene RecyclerViewActions
una colección que define métodos para desplazarse a una posición o realizar acciones en elementos.
agregar dependencias
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0'
Los métodos de operación RecyclerView
son:
- scrollTo(): se desplaza a la vista correspondiente.
- scrollToHolder(): se desplaza al titular de la vista coincidente.
- scrollToPosition() - desplazarse a una posición específica.
- actionOnHolderItem(): realiza una acción de vista en un titular de vista coincidente.
- actionOnItem(): realiza una acción de vista en la vista coincidente.
- actionOnItemAtPosition(): realiza una acción de vista en la vista en una posición específica.
Ejemplo: seleccione la función de eliminación: haga clic en Editar , el contenido de TextView se cambiará para eliminar y RecycleView
aparecerá una casilla de verificación para el elemento al mismo tiempo, marque el elemento que desea eliminar, haga clic en Eliminar , elimine el elemento especificado y RecycleView
el desaparecerá la casilla de verificación del elemento.
@RunWith(AndroidJUnit4.class)
@LargeTest
public class RecyclerViewTest {
@Rule
public ActivityTestRule<RecyclerActivity> activityRule =
new ActivityTestRule<>(RecyclerActivity.class);
static class ClickCheckBoxAction implements ViewAction{
@Override
public Matcher<View> getConstraints() {
return any(View.class);
}
@Override
public String getDescription() {
return null;
}
@Override
public void perform(UiController uiController, View view) {
CheckBox box = view.findViewById(R.id.checkbox);
box.performClick();//点击
}
}
static class MatcherDataAction implements ViewAction{
private String require;
public MatcherDataAction(String require) {
this.require = require;
}
@Override
public Matcher<View> getConstraints() {
return any(View.class);
}
@Override
public String getDescription() {
return null;
}
@Override
public void perform(UiController uiController, View view) {
TextView text = view.findViewById(R.id.text);
assertThat("数据值不匹配",require,equalTo(text.getText().toString()));
}
}
public void delete_require_data(){
//获取RecyclerView中显示的所有数据
List<String> l = new ArrayList<>(activityRule.getActivity().getData());
//点击 编辑 ,判断text是否变成 删除
onView(withId(R.id.edit))
.perform(click())
.check(matches(withText("删除")));
//用来记录要删除的项,
Random random = new Random();
int time = random.nextInt(COUNT);
List<String> data = new ArrayList<>(COUNT);
for (int i = 0; i < COUNT; i++) {
data.add("");
}
for (int i = 0; i < time; i++) {
//随机生成要删除的位置
int position = random.nextInt(COUNT);
//由于再次点击会取消,这里用来记录最后确定要删除的项
if (data.get(position).equals(""))
data.set(position,"测试数据"+position);
else data.set(position,"");
//调用RecyclerViewActions.actionOnItemAtPosition()方法,滑到指定位置
//在执行指定操作
onView(withId(R.id.recycler)).
perform(RecyclerViewActions.actionOnItemAtPosition(position,new ClickCheckBoxAction()));
}
//点击 删除 ,判断text是否变成 编辑
onView(withId(R.id.edit))
.perform(click(),doubleClick())
.check(matches(withText("编辑")));
//删除无用的项
data.removeIf(s -> s.equals(""));
//获取最后保存的项
l.removeAll(data);
//依次判断保留的项是否还存在
for (int i = 0; i < l.size(); i++) {
final String require = l.get(i);
onView(withId(R.id.recycler))
.perform(RecyclerViewActions.
actionOnItemAtPosition(i,new MatcherDataAction(require)));
}
}
}
Nota: MatcherDataAction
Llamado en assertThat()
, no se recomienda este método. Aquí hay una prueba que no he encontrado una mejor manera de implementar.
Intención
Espresso-Intents es una extensión de Espresso, que admite la verificación y acumulación de Intents enviados por la aplicación bajo prueba.
Agregar dependencias:
androidTestImplementation 'androidx.test.ext:truth:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
Antes de escribir Espresso-Intents
una prueba, es necesario configurarla IntentsTestRule
. Esta es ActivityTestRule
una extensión de la clase que le permite usar fácilmente la API en pruebas de interfaz funcional Espresso-Intents
. IntentsTestRule
Se inicializará @Test
antes de cada ejecución de prueba anotada Espresso-Intents
y se desasignará después de cada ejecución de prueba Espresso-Intents
.
@Rule
public IntentsTestRule<MainActivity> mActivityRule = new IntentsTestRule<>(
MainActivity.class);
Verificar intención
Ejemplo: en EditText, ingrese un número de teléfono, haga clic en el botón de marcación y realice una llamada.
@RunWith(AndroidJUnit4.class)
@LargeTest
public class IntentTest {
//设置拨打电话的权限的环境
@Rule
public GrantPermissionRule grantPermissionRule = GrantPermissionRule.grant("android.permission.CALL_PHONE");
@Rule
public IntentsTestRule<MainActivity> mActivityRule = new IntentsTestRule<>(
MainActivity.class);
@Test
public void test_start_other_app_intent(){
String phoneNumber = "123456";
//输入电话号码
onView(withId(R.id.phone))
.perform(typeText(phoneNumber), ViewActions.closeSoftKeyboard());
//点击拨打
onView(withId(R.id.button))
.perform(click());
//验证Intent是否正确
intended(allOf(
hasAction(Intent.ACTION_CALL),
hasData(Uri.parse("tel:"+phoneNumber))));
}
}
intended()
: es Espresso-Intents
el método proporcionado para verificar la intención
Además, la intención también se puede verificar mediante una afirmación.
Intent receivedIntent = Iterables.getOnlyElement(Intents.getIntents());
assertThat(receivedIntent)
.extras()
.string("phone")
.isEqualTo(phoneNumber);
apostar
El método anterior puede resolver la operación de verificación de intención general, pero cuando necesitamos llamar startActivityForResult()
al método para iniciar la cámara para obtener fotos, si usamos el método general, debemos hacer clic manualmente para tomar una foto, lo cual no se considera automático. prueba.
Espresso-Intents
Se proporciona un método intending()
para resolver este problema proporcionando una respuesta de código auxiliar para una actividad iniciada con startActivityForResult(). En pocas palabras, no iniciará la cámara, sino que devolverá su propia intención definida.
@RunWith(AndroidJUnit4.class)
@LargeTest
public class TakePictureTest {
public static BoundedMatcher<View, ImageView> hasDrawable() {
return new BoundedMatcher<View, ImageView>(ImageView.class) {
@Override
public void describeTo(Description description) {
description.appendText("has drawable");
}
@Override
public boolean matchesSafely(ImageView imageView) {
return imageView.getDrawable() != null;
}
};
}
@Rule
public IntentsTestRule<MainActivity> mIntentsRule = new IntentsTestRule<>(
MainActivity.class);
@Rule
public GrantPermissionRule grantPermissionRule = GrantPermissionRule.grant(Manifest.permission.CAMERA);
@Before
public void stubCameraIntent() {
Instrumentation.ActivityResult result = createImageCaptureActivityResultStub();
intending(hasAction(MediaStore.ACTION_IMAGE_CAPTURE)).respondWith(result);
}
@Test
public void takePhoto_drawableIsApplied() {
//先检查ImageView中是否已经设置了图片
onView(withId(R.id.image)).check(matches(not(hasDrawable())));
// 点击拍照
onView(withId(R.id.button)).perform(click());
// 判断ImageView中是否已经设置了图片
onView(withId(R.id.image)).check(matches(hasDrawable()));
}
private Instrumentation.ActivityResult createImageCaptureActivityResultStub() {
//自己定义Intent
Bundle bundle = new Bundle();
bundle.putParcelable("data", BitmapFactory.decodeResource(
mIntentsRule.getActivity().getResources(), R.drawable.ic_launcher_round));
Intent resultData = new Intent();
resultData.putExtras(bundle);
return new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData);
}
}
recursos inactivos
Un recurso libre indica una operación asíncrona cuyos resultados afectan a las operaciones posteriores en la prueba de IU. Espresso
Estas operaciones asincrónicas se pueden verificar de manera más confiable al probar su aplicación al registrar los recursos inactivos con .
agregar dependencias
implementation 'androidx.test.espresso:espresso-idling-resource:3.2.0'
Usemos el ejemplo oficial de Google para presentar cómo usar:
El primer paso: crear SimpleIdlingResource
una clase para implementarIdlingResource
public class SimpleIdlingResource implements IdlingResource {
@Nullable
private volatile ResourceCallback mCallback;
private AtomicBoolean mIsIdleNow = new AtomicBoolean(true);
@Override
public String getName() {
return this.getClass().getName();
}
/**
*false 表示这里有正在进行的任务,而true表示异步任务完成
*/
@Override
public boolean isIdleNow() {
return mIsIdleNow.get();
}
@Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
mCallback = callback;
}
public void setIdleState(boolean isIdleNow) {
mIsIdleNow.set(isIdleNow);
if (isIdleNow && mCallback != null) {
//调用这个方法后,Espresso不会再检查isIdleNow()的状态,直接判断异步任务完成
mCallback.onTransitionToIdle();
}
}
}
Paso 2: Cree una clase que realice tareas asincrónicasMessageDelayer
class MessageDelayer {
private static final int DELAY_MILLIS = 3000;
interface DelayerCallback {
void onDone(String text);
}
static void processMessage(final String message, final DelayerCallback callback,
@Nullable final SimpleIdlingResource idlingResource) {
if (idlingResource != null) {
idlingResource.setIdleState(false);
}
Handler handler = new Handler();
new Thread(()->{
try {
Thread.sleep(DELAY_MILLIS);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
@Override
public void run() {
if (callback != null) {
callback.onDone(message);
if (idlingResource != null) {
idlingResource.setIdleState(true);
}
}
}
});
}).start();
}
}
Paso 3: Inicie la tarea haciendo clic en el botón en MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener,
MessageDelayer.DelayerCallback {
private TextView mTextView;
private EditText mEditText;
@Nullable
private SimpleIdlingResource mIdlingResource;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.changeTextBt).setOnClickListener(this);
mTextView = findViewById(R.id.textToBeChanged);
mEditText = findViewById(R.id.editTextUserInput);
}
@Override
public void onClick(View view) {
final String text = mEditText.getText().toString();
if (view.getId() == R.id.changeTextBt) {
mTextView.setText("正在等待");
MessageDelayer.processMessage(text, this, mIdlingResource);
}
}
@Override
public void onDone(String text) {
mTextView.setText(text);
}
/**
* 仅测试能调用,创建并返回新的SimpleIdlingResource
*/
@VisibleForTesting
@NonNull
public IdlingResource getIdlingResource() {
if (mIdlingResource == null) {
mIdlingResource = new SimpleIdlingResource();
}
return mIdlingResource;
}
}
Paso 4: Crear casos de prueba
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ChangeTextBehaviorTest {
private static final String STRING_TO_BE_TYPED = "Espresso";
private IdlingResource mIdlingResource;
/**
*注册IdlingResource实例
*/
@Before
public void registerIdlingResource() {
ActivityScenario activityScenario = ActivityScenario.launch(MainActivity.class);
activityScenario.onActivity((ActivityScenario.ActivityAction<MainActivity>) activity -> {
mIdlingResource = activity.getIdlingResource();
IdlingRegistry.getInstance().register(mIdlingResource);
});
}
@Test
public void changeText_sameActivity() {
onView(withId(R.id.editTextUserInput))
.perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard());
onView(withId(R.id.changeTextBt)).perform(click());
//只需要注册IdlingResource实例,Espresso就会自动在这里等待,直到异步任务完成
//在执行下面的代码
onView(withId(R.id.textToBeChanged)).check(matches(withText(STRING_TO_BE_TYPED)));
}
//取消注册
@After
public void unregisterIdlingResource() {
if (mIdlingResource != null) {
IdlingRegistry.getInstance().unregister(mIdlingResource);
}
}
}
Insuficiente:
Espresso
proporciona un conjunto de funciones de sincronización avanzadas. Sin embargo, esta característica del marco solo se aplica aMessageQueue
las operaciones que publican mensajes en el , como Ver subclases que dibujan contenido en la pantalla.
otro
Espresso
También hay multiproceso, WebView, verificación de accesibilidad, multiventana, etc. No estoy familiarizado con estos. Se recomienda leer la
documentación oficial de Android o los ejemplos oficiales a continuación.
ejemplo oficial
- IntentsBasicSample : uso básico de intenting() y intenting().
- IdlingResourceSample : sincronizado con trabajos en segundo plano.
- BasicSample : una muestra básica de Espresso.
- CustomMatcherSample : muestra cómo extender Espresso para que coincida con la propiedad de sugerencia de un objeto EditText.
- DataAdapterSample : demuestra el punto de entrada onData() para los objetos List y AdapterView en Espresso.
- IntentsAdvancedSample : simula al usuario usando la cámara para adquirir un mapa de bits.
- MultiWindowSample : muestra cómo señalar Espresso a diferentes ventanas.
- RecyclerViewSample : acción RecyclerView de Espresso.
- WebBasicSample : use Espresso-Web para interactuar con un objeto WebView.