Android、JUnit(一)JUnit初步解析
Android SDK 1.5已经将JUnit包含进来了,但是一直没有去深入了解,以前在使用一些C++的开源库中学习过与CPPUnit,简要分析过其主要框架,如下:
这次在学习Android SDK 1.6中的例子程序APIDemoes中的过程中,看到了一个test文件夹,似乎就是使用了JUnit,于是就开始学习Android中如何使用JUnit。APIDemoes\test文件夹下的测试代码相对比较复杂,我们会循序渐进的学习,逐步深入的理解,在后面为大家详细解析如何阅读这些测试代码,本篇幅就初步解析JUnit。
什么是 JUnit ?
JUnit是采用测试驱动开发的方式,也就是说在开发前先写好测试代码,主要用来说明被测试的代码会被如何使用,错误处理等;然后开始写代码,并在测试代码中逐步测试这些代码,直到最后在测试代码中完全通过。
现简要说JUnit的4大功能
1.????????????????? 管理测试用例。修改了哪些代码,这些代码的修改会对哪些部分有影响,通过JUnit将这次的修改做个完整测试。这也就JUnit中所谓的TestSuite。
2.????????????????? 定义测试代码。这也就是JUnit中所谓的TestCase,根据源代码的测试需要定义每个TestCase,并将TestCase添加到相应的TestSuite方便管理。
3.????????????????? 定义测试环境。在TestCase测试前会先调用“环境”配置,在测试中使用,当然也可以在测试用例中直接定义测试环境。
4.????????????????? 检测测试结果。对于每种正常、异常情况下的测试,运行结果是什么、结果是否是我们预期的等都需要有个明确的定义,JUnit在这方面提供了强大的功能。
以上部分与我们平常使用IDE调试的过程是完全一样的,只不过是增加了测试用例管理、测试结果检测等功能,提高了单元的效率,保证了单元测试的完整性,明确了单元测试的目标。
以上4大功能,在JUnit的框架中是如何实现的了,在下一篇幅JUnit例子分析中,通过一个简要的例子,详细说明4大功能是如何实现的。
?
?
Android、JUnit(二)JUnit例子分析
1.????????????????????????????? 管理测试用例;
2.????????????????????????????? 定义测试代码;
3.????????????????????????????? 定义测试环境;
4.????????????????????????????? 检测测试结果;
结合主要功能,举个简单的例子分析如下:
源代码:
public class SampleCalculator
{
public int add(int augend , int addend)
{return augend + addend ;}
public int subtration(int minuend , int subtrahend)
{ return minuend – subtrahend ;}
}
测试用例(TestCase):
import junit.framework.TestCase;
public class TestSample extends TestCase
{
private int a;
private int b;
private int r1,r2;
void setUp() /*开始测试当前用例–初始化测试环境*/
{
a = 50;
b = 20;
r1 = 70;
r2 = 30;
}
void tearDown()/*当期用例测试结束*/
{}
public void testAdd()/*测试SampleCalculator 类的Add函数*/
{
SampleCalculator calculator = new SampleCalculator();
int result = calculator.add(a , b);
assertEquals(r1 , result);/*检测测试结果*/
}
public void testSubtration()/*测试SampleCalculator 类的Subtration函数*/
{
SampleCalculator calculator = new SampleCalculator();
int result = calculator.subtration(a , b);
assertEquals(r2 , result);/*检测测试结果*/
}
}
以上TestSample测试用例中就对SampleCalculator进行了完整的单元测试,并对测试结果做了预期说明。
TestCase的管理
import junit.framework.Test;
import junit.framework.TestSuite;
public class TestAll
{
public static Test suite()
{
TestSuite suite = new TestSuite(“TestSuite Test”);
suite.addTestSuite(TestSample.class);
return suite;
}
}
以上就将TestSample增加到“TestSuite Test”中,将来在选择测试用例的过程中只要选择了TestSuite Test,TestSample就将加入当前测试中。如果将来SampleCalculator增加了其他功能,只需要在TestSample增加相应的测试代码。
最后需要说明的:对TestCase的管理,是完全界面化的,JUnit会自动产生UI界面,运行以上测试的例子,JUnit的界面如下:
可能还需要下载JUint packeage,最后送给大家一句话:大胆尝试下,你会发现编程真的可以如此“美好”。
?
?
?
Android、JUnit(三)JUnit深入解析(上)
通过前面2篇文章的学习,我们对JUnit有了初步的认识,下面我们将深入的解析JUnit数据包。整个JUnit的数据包应该是很强大的,但是一般来说,不一定每个工程都需要这些数据包,而是在JUnit部分数据包的基础上扩展出自己的数据包,Android SDK中也不例外。至于JUnit完整的包,这里我们就不详细分析了,我们这里只解析Android SDK中包含的那些JUnit数据包,以及Android SDK在JUnit的基础上扩展的一些数据包,如下:
SDK | 功能说明 |
junit.framework | JUnit测试框架 |
junit.runner | 实用工具类支持JUnit测试框架 |
android.test | Android 对JUnit测试框架的扩展包 |
android.test.mock | Android的一些辅助类 |
android.test.suitebuilder | 实用工具类,支持类的测试运行 |
在这些包中最为重要的是:junit.framework、android.test,其中前者是JUnit的核心包,后者是Andoid SDK在Junit.framework的基础上扩展出来的包,我们将重点解析这2个包。
首先解析junit.framework包,结构如下:
通过这张图,大家就可以比较清晰的看到JUnit的主要框架,再回去看下上篇文章的例子,对前面的例子感觉明白多了。做个简要的总结,如下:
1.????????????????????????????? TestSuit:TestSuite是测试用例的集合;
2.????????????????????????????? TestCase:定义运行多个测试用例;
3.????????????????????????????? TestResult:收集一个测试案例的结果,测试结果分为失败和错误,如果未能预计的断言就是失
4.????????????????????????????? 败,错误就像一个ArrayIndexOutOfBoundsException异常而导致的无法预料的问题;
5.????????????????????????????? TestFailure:测试失败时捕获的异常;
6.????????????????????????????? Assert:断言的方法集,当断言失败时显示信息;
TestCase与TestSuite之间的关系,有些类似于图元对象与容器对象之间的关系,在面向对象的语言C++、JAVA中较常见,在这里就不多说了。
举个简单的例子,并简要说明过程
第一步:实现TestCase
1.????????????????????????????? 继承父类TestCase;
2.????????????????????????????? 定义一下变量在测试中使用;
3.????????????????????????????? 在setUp()中初始化这些变量;
4.????????????????????????????? 在tearDown()中清理这些变量;
public class MathTest extends TestCase{
protected double fValue1;
protected double fValue2;
protected void setUp(){
fValue1= 2.0;
fValue2= 3.0;
}
}
5.????????????????????????????? 编写测试单元代码;
public void testAdd() {
double result= fValue1 + fValue2;
assertTrue(result == 5.0);
}
6.????????????????????????????? 运行测试用例,这里有2种方法可以使用:
·???????????????????????????????? 静态类型:覆盖runTes()和定义测试函数。最常用的就是采用java的匿名类,如下:
TestCase test= new MathTest(“add”){
public void runTest() { testAdd();}
};
test.run();
·???????????????????????????????? 动态类型:使用反射来实现runTest,它动态地发现并调用的方法,在这种情况下,测试案例的名字对应的测试方法来运行,如下:TestCase= new MathTest(“testAdd”);
test.run();
相比之下,第2种更符合面向对象的思维。
第二步:将TestCase添加到TestSuilt
?????????? TestSuite suite= new TestSuite();
?????????? suite.addTest(new MathTest("testAdd"));
由于TestSuite可以自动从TestCase中提取测试单元并运行,也可以用如下方法:
?????????? TestSuite suite= new TestSuite(MathTest.class);
一个测试用例就完成了,想要更加详细的了解junit.framework,还是到Android SDK中仔细阅读。
?
?
?
Android、JUnit(四)AndroidTestRunner
随着学习的深入,发现包在前面的篇幅中,我们忽略了android.test包中一个重要的类AndroidTestRunner,这个类是android.test包的核心类,下面为大家详细说明,并补充说明一些相关的内容。
junit.framework包中的TestListener接口
这个接口的函数,列举如下:
与这个接口,相关的类就只用TestResult,相关接口如下:
看到这里就应该知道如何使用了,具体的使用在下一篇幅例子中说明。
junit.runner包,结构如下:
这是一个对junit.framework的辅助包,包主要就是BaseTestRunner类,其实现了TestListener接口,主要功能是:对测试过程中Error、Failure的检查。
有了这些补充说明,下面学习android.test包中一个重要的类AndroidTestRunner。
AndroidTestRunner类结构,如下图所示:
:
其主要接口函数,列举如下:
看到setContext(Context context)这个函数的这个参数Context context,总算让我看到junit与Android的结合点了,在看下其他几个函数,我们会发现,这个类是android.test的核心控制类,大家心中的疑惑顿时就没有了。列举一个简要的例子,如下:
AndroidTestRunner testRunner = new AndroidTestRunner();
testRunner.setTest( new ExampleSuite() );
testRunner.addTestListener( this );
testRunner.setContext( parentActivity );
testRunner.runTest();
通过AndroidTestRunner控制整个测试,并与我们的Activity向结合,具体的使用在下一篇幅中详细说明。
?
?
Android、JUnit(五)AndroidTest例子分析
前面我们学习了android.test包中的大部分类,是该通过学习具体的例子将前面的知识融会贯通,让我们的理解更加深刻,例子程序代码下载地址,下载后添加Eclipes的工程中,边看这篇文章边阅读例子程序的代码。
首先分析整个工程的结构图,如下:
AndroidTestCase,Testsuite在前面的篇幅中已经学习过了,ContestTest、MathTest、SomeTest、ExampleSuite在前面的例子中已经为大家介绍了,这里我们主要说明整个程序是如何运行的?
核心类代码简要列举,如下:
public class JUnit extends Activity {
static final String LOG_TAG = “junit”;
Thread testRunnerThread = null;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button launcherButton = (Button)findViewById( R.id.launch_button );
launcherButton.setOnClickListener( new View.OnClickListener() {
public void onClick( View view ) {startTest();}
} );
}
private synchronized void startTest()
{
if( ( testRunnerThread != null ) &&!testRunnerThread.isAlive() )
testRunnerThread = null;
if( testRunnerThread == null )
{
testRunnerThread = new Thread( new TestRunner( this ) );
testRunnerThread.start();
}
else
{
Toast.makeText(this, “Test is still running”, Toast.LENGTH_SHORT).show();
}
class TestRunner implements Runnable,TestListener
{
static final String LOG_TAG = “TestRunner”;
int testCounter;
int errorCounter;
int failureCounter;
……;
Activity parentActivity;
public TestRunner( Activity parentActivity )
{this.parentActivity = parentActivity;}
public void run()
{
testCounter = 0;
errorCounter = 0;
failureCounter = 0;
………….;
Log.d( LOG_TAG, “Test started” );
/*整个代码的核心*/
AndroidTestRunner testRunner = new AndroidTestRunner();
testRunner.setTest( new ExampleSuite() );
testRunner.addTestListener( this );
testRunner.setContext( parentActivity );
testRunner.runTest();
Log.d( LOG_TAG, “Test ended” );
}
// TestListener
public void addError(Test test, Throwable t)
{
Log.d( LOG_TAG, “addError: “+test.getClass().getName() );
Log.d( LOG_TAG, t.getMessage(), t );
++errorCounter;
…….;
}
public void addFailure(Test test, AssertionFailedError t)
{
Log.d( LOG_TAG, “addFailure: “+test.getClass().getName() );
Log.d( LOG_TAG, t.getMessage(), t );
++failureCounter;
…….;
}
public void endTest(Test test)
{
Log.d( LOG_TAG, “endTest: “+test.getClass().getName() );
…..;
}
public void startTest(Test test)
{
Log.d( LOG_TAG, “startTest: “+test.getClass().getName() );
++testCounter;
…….;
}
}
通过将源工程中的代码简单整理后,就可以看到TestRunner这个工作者线程(window中的术语,没有界面的线程)的作用,这让我们对TestListener有了更加深入的了解。
整个程序的核心代码,如下:
public void run()
{
……;
/*整个代码的核心*/
AndroidTestRunner testRunner = new AndroidTestRunner();
testRunner.setTest( new ExampleSuite() );
testRunner.addTestListener( this );
testRunner.setContext( parentActivity );
testRunner.runTest();
…….;
}
AndroidTestRunner这个核心类,在前面的篇幅中我们已经学习过,再次回忆下这张图(在大脑中留下深刻的记忆,后面会经常使用):
红色划线部分代表例子程序代码中使用的AndroidTestRunner类的函数。这里使用单独线程的主要作用就是:testRunner.runTest();会占用大量的时间,如果直接在UI线程中运行会阻滞UI线程,导致界面停止反应,这对用户的操作会有很大的影响。
如何将TestRunner 中的测试信息显示在界面上?
在前面的Snake例子程序中介绍过:Android SDK为我们提供了Handler,通过Handler与一个线程的消息队列相关联,发送和处理信息。在这个例子中使用了Activity类的runOnUiThread (Runnable action)函数,这个函数的主要功能:在UI线程中运行指定的操作,如果当前线程是UI线程,然后采取行动立即执行;如果当前线程不是UI线程,发送消息到UI线程的事件队列。
整个程序就介绍完了,运行程序后的界面如下:
在这里需要特殊说明的是:打开AndroidManifest.xml文件,发现<application>有个以前没有见过的标记,如下:
<application android:[email protected]/app_name”>
<uses-library android:name=”android.test.runner”/>
<activity android:name=”.JUnit” android:[email protected]/app_name”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
</application>
user-library元素说明:指定一个共享库,应用程序需要连接的。默认情况下会连接所有的Android库,然而一些软件包(如地图和AWT)是不会自动连接独立的库,以确定哪些库需要包含这些特定的包代码文件。
总结说明
这个例子已经学习完了,虽然它比较简单,但是让我们清晰的了解如何使用AndroidTestRunner,后面我们将继续介绍一些复杂的例子,更加深入的学习。
?
?
?
Android、JUnit(六)如何运行单元测试?
前面我们学习了很多JUnit的程序,在Android ADT插件中已经为我们提供了很多这方面的功能,方便我们进行单元测试。但是如何进行单元测试,例如在ApiDemos\test提供的测试例子程序如何运行,对于我们这些初学者来说有些茫然,我也是在网上查找了不少这方面的的资料学习,才知道如何运行测试单元,因此在这里总结说明与大家分享。总结起来,大概有4种不同的方法:
使用ADT 运行测试单元
在Eclipes中选择工程,单击右键,在Run as/Debug as子菜单选项中选择Android JUnit Test,如下:
单击运行后,应用程序将启动,在Eclipes中会出现个新的面板JUnit,如下:
这个界面上就显示了测试的结果,这种方法操作比较简单,但是要想自己写单元测试就的必须深入的去了解后面2中运行的方法。
通过AVD 运行测试单元
运行AVD,选择Dev Tool ,当前界面如下:
双击Instrumentation后,界面如下:
测试例子开始运行,在LogCat中查看运行过程输出的信息,界面如下:
这些信息就是测试例子输出的信息。
通过adb shell 命令运行测试单元
这种方法应该是为linux程序员设置的,完全的命令行,使用起来相对比较麻烦,命令格式如下:
按照上面的命令行格式,输入:adb shell am instrument -w com.xmobileapp.hello/android.test.Instrumentatio