单元测试的必要性
在Android开发中,我们常常需要把程序运行起来才能看到一些算法、请求数据或者是逻辑的效果,这样是很费时间的。但是在实际开发中,我们需要不断看到写出的代码是什么效果,如果完全通过运行来检查逻辑是否正确,那么可能有3/5的时间都是花费在等待编译运行上了。
如果我们在看法过程中针对一个逻辑进行单元测试,那么可以很快的看到这段代码逻辑的效果,从而节省大量的时间。在《Clean Code》中,鲍勃大叔通过和许多编程教父的交谈,总结出了“单元测试”是必要的结论。它保证了代码能编译通过,能正确执行我们想要的逻辑。这是写好一个程序或者模块的基础。
单元测试的一般思路
- 方法有返回值时,采用判断符来进行测试。
- 没有返回值时,寻找方法执行完成后,有哪些对象属性发生了变化,然后打印/或者判断对象的属性。
- 对于耗时操作的测试,需要使用CountDownLatch来帮助阻断测试线程,以等待耗时操作完成,获取结果。
例子
下面的例子中运用到了反射机制,请移步我的另一篇文章,查看Java反射机制。
点击此处传送门Java反射机制
"需要测试的MainActivity:"
public class MainActivity extends AppCompatActivity {
@BindView(R.id.play_anim)
Button button;
@BindView(R.id.ll)
LinearLayout ll;
@BindView(R.id.stop_game_icon)
View stopGameIcon;
@BindView(R.id.display_image)
ImageView displayImage;
private ViewWrapper viewWrapper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
initViewData();
initData();
addListener();
}
private void addListener() {
displayImage.setOnClickListener(v -> initData());
}
private int i = 0;
//需要测试的第一个方法,这是一个void返回值的,包括耗时操作的方法
public void initData() {
APIClient.getArticles()
// 在调用OnNext()之前先调用这个方法,这里的OnNext()是由Retrofit去调用的。
.doOnNext(articles -> {
Log.e("MainActivity", " -> initData: itemsSize = " + articles.items.size());
articles.items.remove(0);
})
// 用上一个Observable返回的结果来创建一个新的Observable
.flatMap(articles -> Observable.from(articles.items)) // 这里使用了from方法创建Observable
// 指定接收者的工作线程
.observeOn(AndroidSchedulers.mainThread())
// 发射给订阅者
.subscribe(
// 创建订阅者
new Subscriber<Articles.ItemsTestBean>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Articles.ItemsTestBean itemsTestBean) {
Log.e("MainActivity", " -> onNext: item " + i++ + " = " + itemsTestBean.id);
}
});
}
//需要测试的第二个方法,这是一个私有的void返回值方法,并且它修改的是私有参数
private void initViewData() {
viewWrapper = new ViewWrapper(ll);
}
private class ViewWrapper {
View view;
public ViewWrapper(View view) {
this.view = view;
}
public int getWidth() {
return view.getMeasuredWidth();
}
public void setWidth(int width) {
view.getLayoutParams().width = width;
view.requestLayout();
}
}
}
"测试类:"
//这里使用的是继承TestCase
public class MainActivityTest extends TestCase {
MainActivity mainActivity;
//该方法会在每次测试前执行,主要是为了初始化测试所需的对象、数据、配置之类的
@Before
public void setUp() throws Exception {
mainActivity = new MainActivity();
}
//该方法发在每次测试结束后都会调用一次
@Override
protected void tearDown() throws Exception {
super.tearDown();
System.out.println("________");
}
//测试耗时操作。因为MainActivity中的这个方法主要执行了这个网络请求,但是我需要
//获得它的回调,在回调中把阻塞的测试线程解除阻塞,并且这里只是味了检查获取结果是否
//正确,所以我就直接测试了这个耗时操作,而不是MainActivity中的方法。
@Test //使用Junit测试必须加的注解符
public void testInitData() throws Throwable {
//创建阻塞变量,参数为多少,就需要调用多少次countDown(),每次调用,阻塞变量-1,
//这里阻塞变量值为1,所以就调用一次。
final CountDownLatch latch = new CountDownLatch(1);
APIClient.getArticles().subscribe(new Action1<Articles>() {
@Override
public void call(Articles articles) {
System.out.println(articles.toString()); //在回调中验证网络请求的结果是否正确
"可以看到,我们不需要再把软件运行起来,就可以检查所写的接口是否正确了。是不是很强大呢!!"
latch.countDown();
}
});
//在确保阻塞变量为0时,需要调用这个方法来终止阻塞。
latch.await();
System.out.println("_________________test");
}
//下面测试的方法没有返回值,而且是私有方法。因此我决定通过反射来调用这个方法,然后
//判断在方法中创建的对象ViewWrapper是否创建成功,即不为null。
@Test
public void testInitViewData() throws Exception {
ReflectUtils.invokeMethod(MainActivity.class, "initViewData"); //这是我封装的通过反射调用私有方法的方法,现在只需要知道它能调用到类中的私有方法就行了。
assertEquals(null, ReflectUtils.getVariable(MainActivity.class, "viewWrapper")); //同样通过反射来获得私有属性。
}
}