Android-UnitTest

来源:互联网 发布:如何评价李鸿章 知乎 编辑:程序博客网 时间:2024/05/18 18:55

Android单元测试

概述

现在国内很多开发人员都没有写单元测试的习惯,但写单元测试能减少很多不必要的麻烦。现在我们就从以下来介绍下单元测试。

一、什么是单元测试

  • 单元测试是开发者编写的一小段代码,用于检验被测代码中的一个很明确的功能是否正确,其中,单元指的是测试的最小模块。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。单元测试的代码不会被编译进入APK中。

二、单元测试目标函数

  • 单元测试的目标函数即测试目标,主要有三种:

1) 有明确的返回值,验证函数的返回值是否符合预期结果。

2) 这个函数只改变其对象内部的一些属性或者状态,函数本身没有返回值,就验证它所改变的属性和状态。

3) 一些函数没有返回值,也没有直接改变哪个值的状态,这就需要验证其行为,比如点击事件。

既没有返回值,也没有改变状态,又没有触发行为的函数是不可测试的,在项目中不应该存在。当存在同时具备上述多种特性时,建议采用多个case来针对每一种特性逐一验证,或者采用一个case,逐一执行目标函数并验证其影响。

三、单元测试的种类

  • 本地测试:只能在本地计算机上运行的单元测试,这些测试在Java虚拟机(JVM)上编译运行,减少运行时间。用这种方式来运行对Android 框架没有依赖性或者有依赖但可以通过模拟对象来填充的单元测试。

  • 仪器测试:即在Android设备或模拟器上运行的单元测试。这些测试能够获取设备的信息,使用这种方式来运行那些不能使用模拟对象容易的填充的单元测试。

四、单元测试常用类库

  • Junit:android单元测试框架

  • Robolectric:其通过一系列对底层Android元素的替换来实现对原有元素调用的模拟,从而实现脱离模拟器的测 试,在测试服务器请求时,Robolectric的数据模拟和延时发送模拟,给多线程状态下的测试提供了很好的解决方法。

  • Mockito:该框架可以模拟出对象来,而且本身提供了一些验证函数执行的功能。Mock构造的是一个虚拟的对象,用于解耦真实对象所需要的依赖。Mock得到的对象仅仅是具备测试对象的类型,并不是真实的对象,也就是并没有执行过真实对象的逻辑。

五、实例代码

  • 下面这个列子是点击按钮后想气象局发送一个网络请求,成功后跳转显示结果;及其所对应的单元测试
  • 查询天气的类QueryWeatherActivity.java
public class QueryWeatherActivity extends Activity {    private String weather;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.weather);        findViewById(R.id.button_weather).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                loadWeather("shanghai");            }        });    }    /**     * 加载数据     *     * @param city     */    private void loadWeather(String city) {        String url = String.format("http://api.openweathermap.org/data/2.5/weather?q={0}&APPID=7bbbb47522848e8b9c26ba35c226c734&units=metric", city);        RequestQueue queue = Volley.newRequestQueue(this);        queue.add(new JsonObjectRequest(url, null, new Response.Listener<JSONObject>() {            @Override            public void onResponse(JSONObject jsonObject) {                weather = jsonObject.toString();                NextToActivity(weather);            }        }, new Response.ErrorListener() {            @Override            public void onErrorResponse(VolleyError volleyError) {                Toast.makeText(QueryWeatherActivity.this, volleyError.toString(), Toast.LENGTH_LONG).show();            }        }));    }    public void NextToActivity(String weather) {        if (weather != null) {            Intent intent = new Intent(QueryWeatherActivity.this, MainActivity.class);            intent.putExtra("weather", weather);            startActivity(intent);        } else {            Toast.makeText(QueryWeatherActivity.this, "weather is null", Toast.LENGTH_LONG).show();        }    }}
  • 针对QueryWeatherActivity做单元测试:主要对是否跳转成功和加载数据是否成功进行 assert。
  • 这里会用到两个测试框架Robolectric和Mockito
dependencies {      ......    testCompile 'org.robolectric:robolectric:3.0'    testCompile "org.mockito:mockito-core:1.10.19"}

  • 下面出现的MockHttpStack 是mock出来的一个HttpStack来模拟Volley发送请求的
public class QueryWeatherTest {    private QueryWeatherActivity queryWeatherActivity;    private String mockWeather;    private String weather;    @Before    public void setUp() throws Exception {        queryWeatherActivity = Robolectric.setupActivity(QueryWeatherActivity.class);        mockWeather = "由于mock的数据很长就不贴了,具体的源码里都有";    }    /**     * 跳转页面成功     * weather有数据时才成功跳转     */    @Test    public void testNextActivitySucceed(){        weather = "有数据";        queryWeatherActivity.NextToActivity(weather);        ShadowActivity shadowActivity = Shadows.shadowOf(queryWeatherActivity);        Intent actualIntent = shadowActivity.getNextStartedActivity();        ShadowIntent shadowIntent = Shadows.shadowOf(actualIntent);        assertThat((Class < MainActivity>) shadowIntent.getIntentClass(), equalTo(MainActivity.class));    }    /**     * 跳转页面失败     */    @Test    public void testNextActivityFail(){        queryWeatherActivity.NextToActivity(weather);        ShadowActivity shadowActivity = Shadows.shadowOf(queryWeatherActivity);        Intent actualIntent = shadowActivity.getNextStartedActivity();        assertThat("weather is null", actualIntent, equalTo(null));    }    /**     * 加载数据成功     * MockHttpStack 是mock一个HttpStack来模拟Volley发送请求的     */    @Test    public void testLoadWeatherSucceed() throws IOException, AuthFailureError {        MockHttpStack mockHttpStack = new MockHttpStack(mockWeather, 200);        String vehicleModel = mockHttpStack.transferString();        assertThat(mockWeather, equalTo(vehicleModel));    }    /**     * 加载数据失败 401     */    @Test    public void testLoadWeatherFail401() throws IOException, AuthFailureError {        MockHttpStack mockHttpStack = new MockHttpStack(mockWeather, 401);        int statusCode = mockHttpStack.response.getStatusLine().getStatusCode();        assertThat("找不到服务器",401, equalTo(statusCode));    }    /**     * 加载数据失败 500     */    @Test    public void testLoadWeatherFail500() throws IOException, AuthFailureError {        MockHttpStack mockHttpStack = new MockHttpStack(mockWeather, 500);        int statusCode = mockHttpStack.response.getStatusLine().getStatusCode();        assertThat("没用内容",500, equalTo(statusCode));    }    /**     * 加载数据失败     * 还有很多失败的情况就不一一列出,这里直接非200都为失败     */    @Test    public void testLoadWeatherFail() throws IOException, AuthFailureError {        MockHttpStack mockHttpStack = new MockHttpStack(mockWeather, 500);        int statusCode = mockHttpStack.response.getStatusLine().getStatusCode();        assertThat("加载失败",200, not(statusCode));    }}

谢谢大家的支持,欢迎大家一起讨论!
源码下载处:http://download.csdn.net/detail/lx_yoyo/9491046

1 0