1. 什么是单元测试
单元测试(模块测试)是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。例如,你可能把一个很大的值放入一个有序list 中去,然后确认该值出现在list 的尾部。或者,你可能会从字符串中删除匹配某种模式的字符,然后确认字符串确实不再包含这些字符了。
单元测试是由程序员自己来完成,最终受益的也是程序员自己。可以这么说,程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。(引自于百度百科,其实我也不懂)
2. InstrumentationTestCase
InstrumentationTestCase是Android 中进行测试的组合类中的基础类,在junit.framework包里,继承于TestCase,可以便捷的进行一些简单的测试。
已知直接子类
ActivityTestCase,ProviderTestCase<Textends ContentProvider>, SingleLaunchActivityTestCase<T extendsActivity>, SyncBaseInstrumentation
已知间接子类
ActivityInstrumentationTestCase<Textends Activity>, ActivityInstrumentationTestCase2<T extendsActivity>, ActivityUnitTestCase<T extends Activity>
在日常编程中,测试类习惯放于androidTest包中,命名以Test结尾,以便于与开发类进行区分。如图1-1
图1-1
新建类SimpleTest进行测试如下:
public class SimpleTest extends InstrumentationTestCase { @MediumTest public void testSimple()throws Exception { try { Thread.sleep(10000);//试试会不会ANR } catch (InterruptedException e) { e.printStackTrace(); } assertEquals(2, 2);//比较功能,比较2是否等于2,若是,继续运行,如不是则抛出异常,详见附录1 } }
在测试类中,当方法已test开头,在运行中将自动调用,换言之,测试方法必须已test开头
运行:选中当前类,右键-->Run “SimpleTest”
运行成功如图1-2
图1-2
如改为assertEquals(1,2);则运行失败如图1-3
图1-3
3. ActivityUnitTestCase<Textends Activity>
ActivityUnitTestCase是ActivityTestCase的子类,添加了一些接口,是InstrumentationTestCase的孙子类,它是针对于Activity封装的类,以方便于开发人员对Activity中的代码进行测试。
开发人员是使用ActivityUnitTestCase时必须要重写构造方法,并在构造方法中调用super(T extends Activity),并将索要测试的Activity的class对象传入其中,除此之外,还要重写父类的setUp方法。
在ActivityUnitTestCase中,可以直接调用getActivity方法来获取Activity的对象,并可以获得Activity中相应权限的方法和属性,并加以测试调用。代码如下:
public class LoginActivityTest extends ActivityUnitTestCase<LoginActivity> { private Intent mLoginIntent; public LoginActivityTest() { super(LoginActivity.class); } @Override protected void setUp() throws Exception { super.setUp(); mLoginIntent = new Intent(getInstrumentation().getTargetContext(), LoginActivity.class); } /** * 验证Button上的文字是否与目标一致 */ @MediumTest public void testAa()throws Exception { startActivity(mLoginIntent, null, null);//获取Activity的对象首先要启动它 final Button loginButton = (Button) getActivity().findViewById(R.id.btn_login); assertEquals("Unexpected button ladel text", "login", loginButton.getText()); int i = ((LoginActivity) getActivity()).getCount();//getCount()为LoginActivity的一个方法,可以对其加以验证 } /** * 测试对Button进行20次点击后,显示文字是否与目标一致 */ @MediumTest public void testOnClick throws Exception () { startActivity(mLoginIntent, null, null);//在每个test方法中都要重新启动activity final Button loginButton = (Button)getActivity().findViewById(R.id.btn_login); for (int i = 0; i < 20; i++){ loginButton.performClick(); } assertEquals("Unexpected button ladel text", "login", loginButton.getText()); } } 4. ActivityInstrumentationTestCase2<T extends Activity > ActivityInstrumentationTestCase2是ActivityTestCase的子类,该类与上述两个类的最明显的区别就是该类在运行时会有界面显示,但是在整个测试流程结束时会将activity关闭,在这里模拟的点击事件将有TouchUtils来实现,通过该类可以进行点击、长按、滑动等操作。 public class MainActivityFunctionalTest extends ActivityInstrumentationTestCase2<MainActivity> { private MainActivityactivity; public MainActivityFunctionalTest(){ super(MainActivity.class); } @Override protected void setUp() throws Exception { super.setUp(); setActivityInitialTouchMode(false); activity =getActivity(); } public void testStartLoginActivity()throws Exception { Button view = (Button) activity.findViewById(R.id.btn_login); for (int i = 0; i < 1; i++){ Thread.sleep(1000); TouchUtils.clickView(this, view);//模拟点击事件 } // 添加要启动的Activity到Monitor Instrumentation.ActivityMonitor monitor = getInstrumentation(). addMonitor(LoginActivity.class.getName(), null, false); TouchUtils.clickView(this, activity.findViewById(R.id.btn2));//通过点击事件启动LoginActivity // 等待两秒时间启动LoginActivity LoginActivity startedActivity =(LoginActivity) monitor .waitForActivityWithTimeout(2000); //启动成功不为空,失败为空 assertNotNull(startedActivity); //LoginActivity里的TextView TextView textView = (TextView)startedActivity.findViewById(R.id.btn_login); for (int i = 0; i < 10; i++){ Thread.sleep(1000); TouchUtils.clickView(this, textView); } //检测textview是否在启动的activity中 ViewAsserts.assertOnScreen(startedActivity.getWindow().getDecorView(), textView); assertEquals("Text incorrect", "login", textView.getText().toString()); // 模拟点击back键 this.sendKeys(KeyEvent.KEYCODE_BACK); TouchUtils.clickView(this, view); } }
5. 总结
我们编写代码时,一定会反复调试保证它能够编译通过。如果是编译没有通过的代码,没有任何人会愿意交付给自己的老板。但代码通过编译,只是说明了它的语法正确;我们却无法保证它的语义也一定正确,没有任何人可以轻易承诺这段代码的行为一定是正确的。
幸运的是,单元测试会为我们的承诺做保证。编写单元测试就是用来验证这段代码的行为是否与我们期望的一致。有了单元测试,我们可以自信的交付自己的代码,而没有任何的后顾之忧。
由谁测试?单元测试与其他测试不同,单元测试可看作是编码工作的一部分,应该由程序员完成,也就是说,经过了单元测试的代码才是已完成的代码,提交产品代码时也要同时提交测试代码。测试部门可以作一定程度的审核。
由于作者水平有限,以上仅作为参考了解。
6. 附录1
在静态类junit.framework.Assert 中存在以下几个方法
1.assertEquals()方法,用来查看对象中存的值是否是期待的值,与字符串比较中使用的equals()方法类似;
2.assertFalse()和assertTrue()方法,用来查看变量是是否为false或true,如果assertFalse()查看的变量的值是false则测试成功,如果是true则失败,assertTrue()与之相反。
3.assertSame()和assertNotSame()方法,用来比较两个对象的引用是否相等和不相等,类似于通过“==”和“!=”比较两个对象;
4.assertNull()和assertNotNull()方法,用来查看对象是否为空和不为空。