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