Espresso中IdlingResource详解

在ui测试中经常会遇到异步线程的操作(例如网络请求,后台计算等操作后验证界面元素),在uiautomator中一般会使用sleep方法来进行等待,在espresso中已经不推荐使用sleep这种形式来等待异步线程操作完成了。

IdlingResource

在espresso中,基于AsynTask的异步任务都会自动在测试中转化为同步线程,例如基于rxjava,rxandroid的异步网络请求,在等网络请求返回结果后Espresso才会执行下一步的操作,除了AsynTask外还有其他的异步操作,例如IntentService等,在这些场景下就需要用到IdlingResource了.

先贴一下idlingresource源码:

public interface IdlingResource
    {
    // 返回idlingresource名字,必须返回
    public String getName();
    // 主线程执行的方法,主要实现该方法
    public boolean isIdleNow();
    public void registerIdleTransitionCallback(ResourceCallback callback);
    public interface ResourceCallback {
    //回调方法,用于通知主线程是否从busy-idle
         public void onTransitionToIdle();
    }
}

要使IdlingResource生效,必须在测试时先进行注册,通过espresso.registerIdlingResources(yourIdlingResource);注册成功后,IdlingResource在运行时就会检查主线程的运行状态,espresso的IdlingResourceRegistry实现了onTransitionToIdle()的回调方法

private void registerToIdleCallback(final IdlingResource resource, final int position) {
     resource.registerIdleTransitionCallback(new ResourceCallback() {
         @Override
         public void onTransitionToIdle() {
              Message m = handler.obtainMessage(DYNAMIC_RESOURCE_HAS_IDLED);
              m.arg1 = position;
              m.obj = resource;
              handler.sendMessage(m);
         }
     });
}

IdlingResource在主线程中通过isIdleNow方法来判断主线程是否idle,若idle则回调onTransitionToIdle()方法告知espresso我已经处于idle状态,若false继续通过isIdleNow状态检测,直到超时或者回调onTransitionToidle()方法为止

附上自己写的一个小例子,场景是发送图片动态,界面出现发送成功后通知espresso执行下一步操作,其中textview由外部传入,通过activityTestRule的getActivity()方法获取当前activity并通过findViewById获取:

public class SendingIdlingResources implements IdlingResource{
     private TextView textView;
     private ResourceCallback mResourceCallback;
     public SendingIdlingResources(TextView textView){ this.textView=textView; }
     @Override
     public String getName() { return "sendingidlingresources"; }
     @Override public boolean isIdleNow() {
          String text = (String) textView.getText();
          String expectedString = "发送成功";
          boolean idle = (text.equals(expectedString));
          if (idle){
               System.out.println("发送成功:"+text);
               mResourceCallback.onTransitionToIdle();
          } else{
                 System.out.println("发送成功没有出现:"+text);
                }
          return idle;
      }
      @Override public void registerIdleTransitionCallback(ResourceCallback callback) {
                      this.mResourceCallback = callback;
       }
}

Espresso中RecyclerView操作

RecyclerViewActions
espresso中如果我们要对AdapterView进行操作,我们会使用onData()方法,然而在对RecyclerView使用onData()方法时会报错,因为RecyclerView是继承自ViewGroup而非AdapterView,如何对RecyclerView进行操作,google官方提供了对应的支持:

androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2')

代码如下,点击recyclerview中的第五个item:

onView(withId(R.id.swipe_target)).perform(RecyclerViewActions.actionOnItemAtPosition(4,click()));

官方仅仅只对recyclerview提供了操作的api,如何check RecyclerView中的元素并没有提供。

RecyclerViewMatcher
官方没有提供,但是官方给出了建议,由自己实现自定义viewMathcer来匹配定位元素,github已有大神实现:
https://github.com/dannyroa/espresso-samples/tree/master/RecyclerView

public static RecyclerViewMatcher withRecyclerView(final int recyclerViewId) {
    return new RecyclerViewMatcher(recyclerViewId);
}
//检查recyclerview中第4个item是否含有内容为“hello”
onView(withRecyclerView(R.id.scroll_view).atPosition(3))
    .check(matches(hasDescendant(withText("hello"))));
// 点击该item
onView(withRecyclerView(R.id.scroll_view).atPosition(3)).perform(click());

对于屏幕外的元素
以上方法在对屏幕外的item操作时会报错,如何解决:

onView(withId(recyclerViewId)).perform(scrollToPosition(position));
onView(withRecyclerView(recyclerViewId).atPosition(position)).perform(click());

通过scroll方法首先滚动到屏幕外的元素,再对元素进行check或者操作即可。

impress.js制作ppt

      impress.js 是国外一位开发者受 Prezi 启发,采用 CSS3 与 JavaScript 语言完成的一个可供开发者使用的表现层框架(演示工具)。现在普通开发者可以利用 impress.js 自己开发出类似效果的演示工具,但性能比基于 FLASH 的 Prezi 更优。其功能包括画布的无限旋转与缩放,任意角度放置任意大小的文字,CSS3 3D 效果支持等。同时,也支持传统 PowerPoint 形式的幻灯演示。

      目前 impress.js 是基于 webkit 浏览器(Chrome、Safari)开发,而在其它基于非 webkit 引擎,但支持 CSS3 3D 的浏览器也能正常运行。

impress GitHub地址:https://github.com/bartaz/impress.js

impress 官方demo: http://impress.github.io/impress.js/#/bored

impress 教程完全不需要,官方demo中的代码已经注释的非常清楚了

自己模仿写了一个demo: http://www.caiqianyu.com/impress/impress.html,效果不错,空间感很强

最后附上作者的鸡汤:

        if you want to build great presentation take a pencil and piece of paper. And turn off the computer.

sqlite增删改查的两种方式

/**
 * Created by cc05451 on 17/1/9.
 * sqlite增删改查的两种方式
 */


public class InfoDao {
    private MySqliteOpenHelper mySqliteOpenHelper;
    public InfoDao(Context context)
    {
        mySqliteOpenHelper = new MySqliteOpenHelper(context);
    }

    /***
     * 写入数据库方法一
     * @param bean
     */

    public void add(InfoBean bean){
        SQLiteDatabase db = mySqliteOpenHelper.getReadableDatabase();
        String sql = "insert into info(name,phone) values(?,?);";
        /***
         * 第一个参数sql:sql语句
         * 第二个参数bindArgs:sql语句中的占位符(?)
         */

        db.execSQL(sql,new Object[]{bean.name,bean.phone});
        db.close();
    }

    /***
     * 写入数据库方法二
     * @param bean
     * @return boolean
     */

    public boolean add2(InfoBean bean){
        SQLiteDatabase db = mySqliteOpenHelper.getReadableDatabase();
        ContentValues values = new ContentValues();
        values.put("name",bean.name);
        values.put("phone",bean.phone);
        /***
         * 第一个参数table:表名
         * 第二个参数nullcolumnhack:当contentvalues为空时插入的数据
         * 第三个参数:插入数据库的值
         * 返回-1表示出错,成功返回添加新行的id
         */

        long result = db.insert("info",null,values);
        db.close();
        if (result!=-1){
            return true;
        }else{
            return false;
        }
    }

    /***
     * 删除数据方法一
     * @param name
     */

    public void del(String name){
        SQLiteDatabase db = mySqliteOpenHelper.getReadableDatabase();
        String sql = "delete from info where name=?;";
        /***
         * 第一个参数sql:sql语句
         * 第二个参数bindArgs:sql语句中的占位符(?)
         */

        db.execSQL(sql,new Object[]{name});
        db.close();

    }

    /***
     * 删除数据方法二
     * @param name
     * @return
     */

    public int del2(String name)
    {
        SQLiteDatabase db = mySqliteOpenHelper.getReadableDatabase();
        /***
         * 第一个参数table:表名
         * 第二个参数删除条件
         * 第三个参数sql语句占位符
         * 返回删除的行数
         */

        int result = db.delete("info","name = ?",new String[]{name});
        db.close();
        return result;
    }

    /***
     * 更新数据方法一
     * @param bean
     */

    public void update(InfoBean bean){
        SQLiteDatabase db = mySqliteOpenHelper.getReadableDatabase();
        String sql = "update info set phone=? where name=?;";
        /***
         * 第一个参数sql:sql语句
         * 第二个参数bindArgs:sql语句中的占位符(?)
         */

        db.execSQL(sql,new Object[]{bean.phone,bean.name});
        db.close();

    }
    /***
     * 更新数据方法二
     * @param bean
     * @return
     */

    public int update2(InfoBean bean){
        SQLiteDatabase db = mySqliteOpenHelper.getReadableDatabase();
        ContentValues values = new ContentValues();
        values.put("phone",bean.phone);
        /***
         * 第一个参数table:表名
         * 第二个参数更新条件
         * 第三个参数sql语句占位符
         * 返回更新的行数
         */

        int update = db.update("info",values,"name = ?",new String[]{bean.name});
        db.close();
        return update;
    }

    /***
     * 查询数据方法一
     * @param name
     */

    public void select(String name){
        System.out.println("selectxxxx");
        SQLiteDatabase db = mySqliteOpenHelper.getReadableDatabase();
        String sql = "select id,name,phone from info where name=?;";
        Cursor cursor = db.rawQuery(sql,new String[]{name});
        if(cursor!=null && cursor.getCount()>0) {
            while (cursor.moveToNext()) {
                int id = cursor.getInt(0);
                String name_str = cursor.getString(1);
                String phone = cursor.getString(2);
                System.out.println("_id:" + id + "name:" + name_str + "phone:" + phone);

            }
            cursor.close();
        }
        db.close();

    }

    /***
     * 查询数据方法二
     * @param name
     */


    public void select2(String name){
        SQLiteDatabase db = mySqliteOpenHelper.getReadableDatabase();
        /***
         * 第一个参数table:表名
         * 第二个参数columns:查询的列名,为空查询所有列
         * 第三个参数selection:查询条件
         * 第四个参数selectionArgs:查询条件占位符
         * 第五个参数groupby:分组
         * 第六个参数having:分组条件
         * 第七个参数orderby:排序
         */

        Cursor cursor = db.query("info",new String[]{"id","name","phone"},"name = ?",new String[]{name},null,null,"id desc");
        if(cursor!=null && cursor.getCount()>0) {
            while (cursor.moveToNext()) {
                int id = cursor.getInt(0);
                String name_str = cursor.getString(1);
                String phone = cursor.getString(2);
                System.out.println("_id:" + id + "name:" + name_str + "phone:" + phone);

            }
            cursor.close();
        }
        db.close();

    }

}

Charles Https抓包–windows篇

今天帮同事配置charles https手机抓包,windows端相比mac端还是有不少坑。 

1.安装3.10之后的charles,3.10之前的不适用本方法

2.打开charles -> Help -> SSL Proxying -> Install Charles Root Certificate安装证书

windows下这里有个坑,安装证书的时候不能选择自动,必须选择受信任的根证书颁发机构,这点在官方文档中也有相应的说明:

Windows / Internet Explorer

In Charles go to the Help menu and choose “SSL Proxying > Install Charles Root Certificate”. A window will appear warning you that the CA Root certificate is not trusted.

Click the “Install Certificate” button to launch the Certificate Import Wizard. The certificate must be imported into the “Trusted Root Certification Authorities” certificate store, so override the automatic certificate store selection.

Complete the wizard and your Charles Root Certificate is now installed. You may need to restart IE before the installation takes affect.

3.手机连接charles代理

4.手机打开浏览器输入www.charlesproxy.com/getssl安装证书到手机

5.打开proxy->ssl proxy->enable ssl proxy勾选,location中添加你要抓包的https网站,端口为443

p.s:3.10之前的charles ssl代理证书似乎有问题一直不成功

     电脑端安装好证书后,手机一定要先连代理再安装证书,不然不成功

SharedPreferences使用

SharedPreferences 类提供了一个通用框架,以便您能够保存和检索原始数据类型的永久性键值对。 您可以使用 SharedPreferences 来保存任何原始数据:布尔值、浮点值、整型值、长整型和字符串。 此数据将跨多个用户会话永久保留(即使您的应用已终止亦如此)。

要获取应用的 SharedPreferences 对象,请使用以下两个方法之一:

  • getSharedPreferences() – 如果您需要多个按名称(使用第一个参数指定)识别的首选项文件,请使用此方法。
  • getPreferences() – 如果您只需要一个用于 Activity 的首选项文件,请使用此方法。 由于这将是用于 Activity 的唯一首选项文件,因此无需提供名称。

要写入值:

  1. 调用 edit() 以获取 SharedPreferences.Editor
  2. 使用 putBoolean() 和 putString() 等方法添加值。
  3. 使用 commit() 提交新值

要读取值,请使用 getBoolean() 和 getString() 等 SharedPreferences 方法。

——节选自官方文档

实际使用案例

存储SharedPreferences

public static boolean saveUserInfo(Context context, String username, String password) {
//      1.通过context对象创建sharedpreference对象
//        name:sharedpreference文件名 mode:文件操作模式
        try {
            SharedPreferences sharedPreference = context.getSharedPreferences("userinfo.txt", Context.MODE_PRIVATE);
//      2.通过sharedpreference对象获取一个editor对象
            SharedPreferences.Editor editor = sharedPreference.edit();
//      3.往editor中添加数据
            editor.putString("username", username);
            editor.putString("password", password);
            editor.commit();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

读取SharedPreferences

public static Map<String, String> getUserInfo(Context context) {
//        1.创建sharedpreference对象
        SharedPreferences sharedpreferences = context.getSharedPreferences("userinfo.txt", Context.MODE_PRIVATE);
//        2.通过sharedpreference获取存放数据
        String username = sharedpreferences.getString("username", "");
        String password = sharedpreferences.getString("password", "");
//
        HashMap<String, String> hashMap = new HashMap<String, String>();
        hashMap.put("username", username);
        hashMap.put("password", password);
        return hashMap;
    }

wordpress codecolorer属性说明

属性说明
属性列表如下,括号内是参数类型.string是字符串;integer是整数;boolean是布尔型(开关),可接受“true” “false”, “on” “off”, 整数 1 or 0.

  • lang (string) – 代码使用的语言。
  • tab_size (integer) – 用以替换制表符的空格数,可在设置界面更改。
  • line_numbers (boolean) – 是否显示行号,可在设置界面更改。
  • first_line (integer) – 指定代码块第一行的行号
  • highlight (string) – 用于指定整行高亮的代码行行数,参数是用半角逗号分隔的数字串(如 1,5,8,9)。
  • no_links (boolean) – 当值为false时,关键字将会添加一个到官方文档的链接,可在设置界面更改。
  • lines (integer) –指定代码块显示的行数,当值设置为-1时,不出现纵向滚动条,可在设置界面更改。
  • width (integer or string) – 代码块宽度,可在设置界面更改。
  • height (integer or string) – 代码块高度,当这个高度可显示的行数比lines指定的值大才会生效,可在设置界面更改。
  • rss_width (integer or string) – 代码块在RSS输出时的宽度,可在设置界面更改。
  • theme (string) – 代码块颜色风格 (default, blackboard, dawn, mac-classic, twitlight, vibrant),可在设置界面更改。
  • inline (boolean) – 内嵌模式开关,用于将一行代码插入到文本中。
  • strict (boolean) – 严格模式的开关。
  • nowrap (boolean) – 当值指定为false时,过长的行将会被自动换行,以避免出现横向滚动条。
  • noborder (boolean) – 是否显示边框的开关。
  • no_cc (boolean) – 当值为true时,code标签将会被解析,但代码块不会有格式。
  • class (string) – 添加一个新的CSS。
  • escaped (string) – 当值为false,代码块里的html转义字符不会被转义,如<不会转义为<,默认为false。

使用方法:

<code lang=”python” nowrap=”false”>print ‘hello'</code>

效果如下:

print 'hello'