Android-UnitTest

本文介绍了Android单元测试的重要性,详细讲解了单元测试的概念、目标函数类型、测试种类,并推荐了Junit、Robolectric和Mockito等测试类库。通过实例展示了如何对网络请求进行单元测试,包括模拟网络请求和验证跳转及数据加载结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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.youkuaiyun.com/detail/lx_yoyo/9491046

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值