Android自动化测试笔记

Android自动化测试笔记

实现目标

定义一个时间,在到达时间的时候,手机自动打开京东商城,并进入秒杀界面

准备工作

  1. 先导入需要的jar包
    足足有33个jar包需要引用
    足足33个jar包需要引用,而且这些jar包还不是从一个地儿down下来的,找了许久的资源,果断放弃了这个想法。
    改用Maven,这个是我以前写java的时候get到的,没想到android还真有。网上查了查资料,还挺简单,就是在项目跟目录的build.gradle中加一句代码。
    在这里插入图片描述
    这就省大劲儿了。我把代码贴出来,供大家复制粘贴使!
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.1.1'
        

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://jitpack.io' }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

  1. 配置权限和引用
    AndroidManifest.xml中要加三个权限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="android.example.xinxi.monkeytest">
    <!--android:sharedUserId="android.uid.system"-->
    <!--&gt;-->

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.INTERNET"/>

    <application android:name="ContextUtil"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

中间那个权限最关键,是个超级用户权限,有了它才可以让手机自己控制自己玩儿。
接下来是src目录中的build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "android.example.xinxi.monkeytest"
        minSdkVersion 24
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:2.0.4'
    testImplementation 'junit:junit:4.13.1'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
    implementation 'com.github.yhaolpz:FloatWindow:1.0.9'
    implementation 'com.contrarywind:Android-PickerView:4.1.9'
    implementation 'com.google.code.gson:gson:2.4'
}

这里需要注意几点,第一,com.android.support:appcompat-v7:28.0.0的版本号要与compileSdkVersion 的版本号对上,我的IDE能支持到29,但是com.android.support:appcompat-v7目前最大只有28.
第二,自动控制支持的最小安装版本是6.0,这里minSdkVersion 的版本最小可以写24。
第三,testInstrumentationRunner 一定要改成android.support.test.runner.AndroidJUnitRunner。

步入正题

在这里插入图片描述

代码量其实不高,拢共就四个文件需要写。
第一个,窗体界面文件activity_main.xml
一共就俩控件,一个时间框,一个按钮就够了。
第二个,逻辑主文件MainActivity.java
这个文件控制着主要逻辑,也是它使用Monkey命令来触发uiautomator工作的。
上代码:

package android.example.xinxi.monkeytest;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.BounceInterpolator;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.bigkoo.pickerview.builder.TimePickerBuilder;
import com.bigkoo.pickerview.listener.OnTimeSelectChangeListener;
import com.bigkoo.pickerview.listener.OnTimeSelectListener;
import com.bigkoo.pickerview.view.TimePickerView;
import com.google.gson.Gson;
import com.yhao.floatwindow.FloatWindow;
import com.yhao.floatwindow.PermissionListener;
import com.yhao.floatwindow.Screen;
import com.yhao.floatwindow.ViewStateListener;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MainActivity extends AppCompatActivity {

    private UiautomatorThread uiautomatorThread;
    private Button startButton;
    private Context context;
    private static String TAG = "MonkeyTest";
    private static String packageName = "android.example.xinxi.monkeytest.test";
    private static String runClassName = "android.support.test.runner.AndroidJUnitRunner";
    public volatile boolean exit = false;
    public static String runtime = "";
    private TimePickerView pvTime;
    private TextView textView;
    private TextView tb_textview;
    private Button tb_btn;
    private  TextView start_time;



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startButton = (Button) findViewById(R.id.button);
        textView = (TextView) findViewById(R.id.textView);
        tb_textview = (TextView) findViewById(R.id.tb_textview);
        tb_btn = (Button) findViewById(R.id.tb_btn);
        start_time = (TextView) findViewById(R.id.start_time);
        initTimePicker();
    }

    /**
     * 用于接收子线程中的数据
     */
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            tb_textview.setText(msg.obj.toString());
        }
    };

    /**
     * 点击RUN按钮
     */
    public void runMyUiautomator(View v) {
        String user_time = textView.getText().toString();
        if(user_time.equals("请选择时间")){
            showExitDialog("请选择时间");
            return;
        }

        String tb_time = tb_textview.getText().toString();
        if(tb_time.equals("淘宝时间差")){
            showExitDialog("请获取淘宝时间差");
            return;
        }

        long datetime  = Long.parseLong(getTimeStamp(user_time)) * 1000;
        long tb_time_stamp = Long.parseLong(tb_textview.getText().toString());
        long targettime = datetime - tb_time_stamp;
        start_time.setText(String.valueOf(targettime));
        uiautomatorThread = new UiautomatorThread(targettime);
        uiautomatorThread.start();
    }

    /**
     * 点击选择时间按钮
     * */
    public void initTime(View v) {
        pvTime.show(v);
    }

    /**
     * 点击获取淘宝时间差
     */
    public void getTBTime(View v){
        getTBData();
    }

    /**
     *格式化选择的时间
     */
    private String getTime(Date date) {//可根据需要自行截取数据显示
        Log.d("getTime()", "choice date millis: " + date.getTime());
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return format.format(date);
    }

    /**
     * 提示框
     */
    private void showExitDialog(String message){
        new AlertDialog.Builder(this)
                .setTitle("提示")
                .setMessage(message)
                .setPositiveButton("确定", null)
                .show();
    }

    /**
     * 初始化时间控件
     */
    private void initTimePicker() {
        //Dialog 模式下,在底部弹出
        pvTime = new TimePickerBuilder(this, new OnTimeSelectListener() {
            @Override
            public void onTimeSelect(Date date, View v) {
                textView.setText(getTime(date));
                Log.i("pvTime", "onTimeSelect");

            }
        }).setTimeSelectChangeListener(new OnTimeSelectChangeListener() {
                    @Override
                    public void onTimeSelectChanged(Date date) {
                        Log.i("pvTime", "onTimeSelectChanged");
                    }
                })
                .setType(new boolean[]{true, true, true, true, true, true})
                .isDialog(true) //默认设置false ,内部实现将DecorView 作为它的父控件。
                .addOnCancelClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Log.i("pvTime", "onCancelClickListener");
                    }
                })
                .setItemVisibleCount(5) //若设置偶数,实际值会加1(比如设置6,则最大可见条目为7)
                .setLineSpacingMultiplier(2.0f)
                .isAlphaGradient(true)
                .build();

        Dialog mDialog = pvTime.getDialog();
        if (mDialog != null) {

            FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    Gravity.BOTTOM);

            params.leftMargin = 0;
            params.rightMargin = 0;
            pvTime.getDialogContainerLayout().setLayoutParams(params);

            Window dialogWindow = mDialog.getWindow();
            if (dialogWindow != null) {
                dialogWindow.setWindowAnimations(com.bigkoo.pickerview.R.style.picker_view_slide_anim);//修改动画样式
                dialogWindow.setGravity(Gravity.BOTTOM);//改成Bottom,底部显示
                dialogWindow.setDimAmount(0.3f);
            }
        }
    }

    /**
     * 将时间字符串转成时间戳
     */
    public static String getTimeStamp(String user_time) {
        String re_time = null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d;
        try {
            d = sdf.parse(user_time);
            long l = d.getTime();
            String str = String.valueOf(l);
            re_time = str.substring(0, 10);
        }catch (ParseException e) {
        }
        return re_time;
    }

    /**
     * 获取淘宝时间差
     */
    public void getTBData(){
        //HttpUrlConnection
        /**
         * 1.实例化一个url对象
         * 2.获取HttpUrlConnection对象
         * 3.设置请求连接属性
         * 4.获取响应码,判断是否连接成功
         * 5.读取输入流并解析
         */
        //参数:你要访问的接口地址
        new Thread(){
            @Override
            public void run() {
                try {
                    URL url = new URL("http://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp");
                    HttpURLConnection coon = (HttpURLConnection) url.openConnection();
                    coon.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36");
                    coon.setRequestProperty("cookie","thw=cn");
                    coon.setRequestMethod("GET");
                    coon.setReadTimeout(6000);
                    //获取响应码
                    if(coon.getResponseCode() == 200){
                        //获取输入流
                        InputStream in = coon.getInputStream();
                        byte[] b = new byte[1024*512];
                        int len = 0;
                        //建立缓存流,保存所读取的字节数组
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        while ((len = in.read(b)) > -1){
                            baos.write(b,0,len);
                        }
                        String msg = baos.toString();
                        //JSON数据的解析:
                        JSONObject obj = new JSONObject(msg);
                        JSONObject data = obj.getJSONObject("data");
                        String tb_time = data.getString("t");

                        //获取当前时间戳
                        long now_time_long = new Date().getTime();
                        long tb_time_long = Long.parseLong(tb_time);

                        long time_difference = tb_time_long - now_time_long;

                        Message message = handler.obtainMessage();
                        message.obj =time_difference;

                        //调用此方法,则会触发主线程中Handle对象里覆盖了的handleMessage方法
                        handler.sendMessage(message);
                    }
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }.start();



    }


    /**
     * 运行uiautomator是个费时的操作,不应该放在主线程,因此另起一个线程运行
     */
    class UiautomatorThread extends Thread {


        private final Object lock = new Object();
        private boolean pause = false;
        private long runtime;

        public UiautomatorThread(long targettime) {
            runtime = targettime;
        }

        @Override
        public void run() {
            super.run();
            while (true){
                long now_time_long = new Date().getTime();
                if(now_time_long >=  runtime){
                    String command = generateCommand("android.example.xinxi.monkeytest", "ExampleInstrumentedTest", "demo");
                    CMDUtils.CMD_Result rs = CMDUtils.runCMD(command, true, true);
                    break;
                }
            }
        }


        /**
         * 生成命令
         *
         * @param pkgName uiautomator包名
         * @param clsName uiautomator类名
         * @param mtdName uiautomator方法名
         * @return
         */
        public String generateCommand(String pkgName, String clsName, String mtdName) {
            String command = "am instrument -w -r -e debug false -e class "
                    + pkgName + "." + clsName + "#" + mtdName + " "
                    + pkgName + ".test/android.support.test.runner.AndroidJUnitRunner";
            Log.e(TAG, command);
            return command;
        }


    }
}

这里需要注意的是,uiautomator在实际运行的时候特别耗资源,尤其里面还有个while循环,所以我给它来了个子线程,跟后台自己跑去吧,哈哈哈…
第三个,是一个辅助文件CMDUtils.java
它的左右就是把monkey命令格式化一下子。
上代码:

package android.example.xinxi.monkeytest;

import android.util.Log;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;

/**
 * 执行命令
 */
public class CMDUtils {

    private static final String TAG = "CMDUtils";

    public static class CMD_Result {
        public int resultCode;
        public String error;
        public String success;

        public CMD_Result(int resultCode, String error, String success) {
            this.resultCode = resultCode;
            this.error = error;
            this.success = success;
        }

    }

    /**
     * 执行命令
     *
     * @param command         命令
     * @param isShowCommand   是否显示执行的命令
     * @param isNeedResultMsg 是否反馈执行的结果
     * @retrun CMD_Result
     */
    public static CMD_Result runCMD(String command, boolean isShowCommand,
                                    boolean isNeedResultMsg) {
        if (isShowCommand)
            Log.i(TAG, "runCMD:" + command);
        CMD_Result cmdRsult = null;
        int result;
        Process process = null;
        PrintWriter pw = null;
        try {
            process = Runtime.getRuntime().exec("su"); //获取root权限
            pw = new PrintWriter(process.getOutputStream());
            pw.println(command);
            pw.flush();
            result = process.waitFor();
            if (isNeedResultMsg) {
                StringBuilder successMsg = new StringBuilder();
                StringBuilder errorMsg = new StringBuilder();
                BufferedReader successResult = new BufferedReader(
                        new InputStreamReader(process.getInputStream()));
                BufferedReader errorResult = new BufferedReader(
                        new InputStreamReader(process.getErrorStream()));
                String s;
                while ((s = successResult.readLine()) != null) {
                    successMsg.append(s);
                }
                while ((s = errorResult.readLine()) != null) {
                    errorMsg.append(s);
                }
                cmdRsult = new CMD_Result(result, errorMsg.toString(),
                        successMsg.toString());
            }
        } catch (Exception e) {
            Log.e(TAG, "run CMD:" + command + " failed");
            e.printStackTrace();
        } finally {
            if (pw != null) {
                pw.close();
            }
            if (process != null) {
                process.destroy();
            }
        }
        return cmdRsult;
    }

}

这里没啥注意的,直接复制粘贴就成。
第四个,也就是关键的自动测试文件,看好路径,是src目录下androidTest中的ExampleInstrumentedTest.java文件。

package android.example.xinxi.monkeytest;
import android.support.test.InstrumentationRegistry;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.UiSelector;
import org.junit.Test;



public class ExampleInstrumentedTest {
    private UiDevice mDevice;

    @Test
    public void demo() throws UiObjectNotFoundException {
        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        UiObject x= mDevice.findObject(new UiSelector().text("京东"));
        x.click();
        sleep(5000);
        UiObject y= mDevice.findObject(new UiSelector().text("京东秒杀"));
        y.click();
    }

    public void sleep(int mint){
        try{
            Thread.sleep(mint);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

代码量不高,就是打开京东,进入京东秒杀界面。做实验而已嘛,何必较真呢。
这个文件写完以后可以单独跑一下,看一下效果。
最后:需要注意的是,编译后的文件会有两个apk,一个普通的apk和一个testapk,安装顺序一定时先安装apk再安装testapk,否则不能正常运行,貌似是找不到testapk中的那个测试方法。
在这里插入图片描述
给你们看看我实验的结果吧,反正我也成功了,哈哈哈…
在这里插入图片描述
最后,我也是刚开始学习android,希望大家与我沟通,咱们互相学习,共同进步。
下载源码

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不正经的开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值