2016年9月24日 星期六

Espresso

lesson 1


WHY

How to setup

Download Espresso

  • Make sure you have installed the latest Android Support Repository under Extras (see instructions).
  • Open your app’s build.gradle file. This is usually not the top-level build.gradle file but app/build.gradle.
  • Add the following lines inside dependencies:
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
androidTestCompile 'com.android.support.test:runner:0.5'
  • See the downloads section for more artifacts (espresso-contrib, espresso-web, etc.)

Set the instrumentation runner

  • Add to the same build.gradle file the following line in android.defaultConfig:
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

Example build.gradle file

apply plugin: 'com.android.application'

android {
   compileSdkVersion 22
   buildToolsVersion "22"

   defaultConfig {
       applicationId "com.my.awesome.app"
       minSdkVersion 10
       targetSdkVersion 22.0.1
       versionCode 1
       versionName "1.0"

       testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
   }
}

dependencies {
   // App's dependencies, including test
   compile 'com.android.support:support-annotations:22.2.0'

   // Testing-only dependencies
   androidTestCompile 'com.android.support.test:runner:0.5'
   androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
}

Analytics

In order to make sure we are on the right track with each new release, the test runner collects analytics. More specifically, it uploads a hash of the package name of the application under test for each invocation. This allows us to measure both the count of unique packages using Espresso as well as the volume of usage.
If you do not wish to upload this data, you can opt out by passing the following argument to the test runner: disableAnalytics "true" (see how to pass custom arguments).

Add the first test

Android Studio creates tests by default in src/androidTest/java/com.example.package/
Example JUnit4 test using Rules:
@RunWith(AndroidJUnit4.class)
@LargeTest
public class HelloWorldEspressoTest {

   @Rule
   public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class);

   @Test
   public void listGoesOverTheFold() {
       onView(withText("Hello world!")).check(matches(isDisplayed()));
   }
}


ViewMatchers,
ViewActions
ViewAssertions
TDD

Lesson 2
What View Matchers are and how to use them
What View Assertions are and how to use them
What View Actions are and how to use them
How to click on buttons with Espresso
How to enter text into EditText widgets with Espresso
How to write a test that validates different screens are loaded.
How to debug tests when view are not found with view matchers
How to use the UIAutomatorViewer to speed up test development
Best practice for finding views (use the ID not string literals!)

How databases are cleaned before each test run (using Realm).



@Test
   public void ahouldBeAbleToAddTaskAndTheTaskShow(){
//        click the new button
       onView(withId(R.id.menu_main_new_task)).perform(click());
//        click task name
//        input Task1
       onView(withId(R.id.new_task_task_name)).perform(click()).perform(typeText("Task1"));
//        Click task Description
//        Task1 Desc
       onView(withId(R.id.new_task_task_desc)).perform(click()).perform(typeText("Task1 Desc"));
//        CLick Add
       onView(withId(R.id.new_task_add)).perform(click());
//        Task1 appear
       onView(withText("Task1")).check(matches(isDisplayed()));
   }



Lesson 3 UI Testing with Espresso and RecyclerView (Part 2)

Espresso  interect with RecyclerView
need to add this first

androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2.1') {
       exclude module: 'support-annotations'
       exclude module: 'support-v4'
       exclude module: 'support-v13'
       exclude module: 'recyclerview-v7'
   }


if we want to scroll the recycle view

onView(withId(R.id.main_task_list)).perform(RealmRecyclerViewActions.scrollToPosition(12));
       onView(withText("Task10")).check(matches(isDisplayed()));

@Test
   public void ahouldBeAbleToAddTaskAndTheTaskShow(){

       for(int i=0;i<15;i++){
//        click the new button
           onView(withId(R.id.menu_main_new_task)).perform(click());
//        click task name
//        input Task1
           onView(withId(R.id.new_task_task_name)).perform(click()).perform(typeText("Task"+i));
//        Click task Description
//        Task1 Desc
           onView(withId(R.id.new_task_task_desc)).perform(click()).perform(typeText("Task1"+i+" Desc"));
//        CLick Add
           onView(withId(R.id.new_task_add)).perform(click());

       }
//        Task1 appear
       onView(withText("Task1")).check(matches(isDisplayed()));

//        scroll to Tasl 10
//        Task 10 appear
       onView(withId(R.id.main_task_list)).perform(RealmRecyclerViewActions.scrollToPosition(12));
       onView(withText("Task10")).check(matches(isDisplayed()));
   }


Lesson4 Creating Custom ViewMatchers1
foe below

onView(withId(R.id.main_task_list)).perform(RealmRecyclerViewActions.scrollToPosition(12));


if i dont want to  use the matcher scroll to a position,i want to  scroll in to  a specific Text, like “Task10”,

onView(withId(R.id.main_task_list)).perform(RealmRecyclerViewActions.scrollTo(withText("Task10")));
something like that,bu it is not success,because the Structure of the recycle view is define bu ourself,the program don’t know where to find the text

so we need to create a Custom Mathcer

create a class,tham create a static method

we need to check is the parent view(item) is null,
if not null; find the R.id.task_item_task_name(the Text View hold the “Task10”)
if can find ,and it’s string is not empty,
is “Task10”? return true

public static Matcher<View> withTaskName(final String expected){
       return new TypeSafeMatcher<View>() {
           @Override
           protected boolean matchesSafely(View item) {
               if(item==null)
                   return false;
               else {
                   TextView taskName = (TextView) item.findViewById(R.id.task_item_task_name);
                   if(taskName!=null&& !TextUtils.isEmpty(taskName.getText())&&taskName.getText().toString().equals(expected)){
                       return true;
                   }else
                       return false;
               }

           }

           @Override
           public void describeTo(Description description) {
               description.appendText("look for "+expected+" in the recycle view");
           }
       };
   }


Lesson5 Creating Custom ViewMatchers2
TypeSafeMatcher and BoundedMatcher

BoundedMatcher为我们指定了一个针对目标类型的子类型进行匹配的匹配规则。比如,我们现在需要一个Matcher<Object>对象,但实际上我们需要考察的目标类型是SearchItem,而SearchItem又是Object的子类,因此,我们可以通过BoundedMatcher来构造这个Matcher<Object>对象,只不过我们实际上进行检查的转变成了SearchItem类型,只要采用如下写法:

we can specific what type we want.


for example,we want Drawable

public static Matcher<View> withImageDrawable(final int resourceId) {
       return new BoundedMatcher<View, ImageView>(ImageView.class) {
           @Override
           public void describeTo(Description description) {
               description.appendText("has image drawable resource " + resourceId);
           }

           @Override
           public boolean matchesSafely(ImageView imageView) {
               return sameBitmap(imageView.getContext(), imageView.getDrawable(), resourceId);
           }
       };
   }

   private static boolean sameBitmap(Context context, Drawable drawable, int resourceId) {
       Drawable otherDrawable = context.getResources().getDrawable(resourceId);
       if (drawable == null || otherDrawable == null) {
           return false;
       }
       if (drawable instanceof StateListDrawable && otherDrawable instanceof StateListDrawable) {
           drawable = drawable.getCurrent();
           otherDrawable = otherDrawable.getCurrent();
       }
       if (drawable instanceof BitmapDrawable) {
           Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
           Bitmap otherBitmap = ((BitmapDrawable) otherDrawable).getBitmap();
           return bitmap.sameAs(otherBitmap);
       }
       return false;
   }



waht is StateListDrawable


need get current,

The current drawable that will be used by this drawable. For simple drawables, this is just the drawable itself. For drawables that change state like StateListDrawable and LevelListDrawable this will be the child drawable currently in use.


becuase the StateListDrawable  have different state