Android Studio下单元测试的本质其实是根据通过书写JAVA测试代码,通过模拟用户调用相应的方法,或者使用者按下相应的按键来验证我们的代码的逻辑是否能达到预期的要求,如果所有的用例都能通过,则证明我们的逻辑满足要求,否则,可以通过fail()函数(或使用Assert)进行输出错误信息。
在进行测试前我们首先需要了解一下几个基本的概念:TestCase,TestSuite:
如图1所示,TestSuite和TestCase都是继承自Test接口,同时,TestSuite的建立和使用依赖于TestCase实例,TestCase继承自Assert类,因此TestCase中可以直接使用Assert中的相关方法,Assert类提供了几个常用的判断方法,Assert的类图可以参照图2:
TestCase:
在进行单元测试的时候,在JUNIT4之前,我们需要测试的代码所在的类一般都需要直接或者间接继承自TestCase,对于我们创建的这个TestCase的子类,我们需要注意在我们这个类的测试流程,假设我们创建的TestCase子类中有两个测试用例testMethod1和testMethod2,则执行顺序可以如图3所示:
对于我们类中的两个测试用例testMethod1和testMethod2,都会分别创建一个新的TestCase子类对象,并引起TestCase中的setUp和tearDown函数分别执行一遍,因此,在进行单元测试的过程中,我们可以在setUp当中进行一些初始化操作(如类的某些属性的赋值操作),在tearDown中进行一些扫尾工作(如类中某些对象所持有资源的释放)。
一个简单的Demo:
import junit.framework.TestCase;
public class TestDemo extends TestCase{
@Override
protected void setUp() throws Exception {
// TODO Auto-generated method stub
super.setUp();
System.out.println("setUp , hashCode = "+hashCode());
}
@Override
protected void tearDown() throws Exception {
// TODO Auto-generated method stub
super.tearDown();
System.out.println("tearDown,hashCode = "+hashCode());
}
public void testMethod1(){
System.out.println("testMethod1 , hashCode = "+hashCode());
}
public void testMethod2(){
System.out.println("testMethod2 , hashCode = "+hashCode());
}
}
运行结果,如图4所示:
有两个test函数,testMethod1和testMethod2,在生命周期的开始函数setUp以及结尾函数tearDown中分别产生了两次不同的hashCode,根据Java语言中HashCode的概念我们可以知道这分别导致了我们的TestDemo类型的对象创建了两次。因此如果测试的case增多,我们的TestDemo对象也会创建和case相同的个数。
对于测试用例testMethod1和testMethod2的函数声明,在我们书写用例函数的时候需要注意他们有一个共同的特点:
1).访问权限都是public;
2).返回类型都是void;
3).没有参数;
4).方法名以“test”开头。
在使用单元测试的时候必须注意用例方法的生命格式,否则该用例将不会被执行的到。
TestSuite:
对于suite这个英文单词,从字面上可以理解为组合或者集合的意思,再加上通过图1,我们发现TestSuite和TestCase都是实现自Test接口,这很容易让我们想起JAVA设计模式中的合成模式的概念:即TestSuite可以认为合成模式中的组,是一组TestCase对象的集合;而TestCase对象时这个合成模式中的叶子对象,并且,这些TestCase对象(叶子对象)和TestSuite(组对象)拥有共同的行为(run方法);这样,可以保证当用户调用组对象TestSuite的run方法的时候,也会调用到TestCase对象的run方法。而事实上也确实是这样,在使用JUnit3执行测试的过程中,会首先创建TestSuite对象,在TestSuite对象的构造方法中,会扫描TestCase子类的所有方法,并调用addTestMethod方法,在该方法中调用isPublicTestMethod方法判断是否是待测的方法,若是会调用createTest方法,创建一个Test对象,并调用addTest方法加入到自己的集合中去,因此执行过程中的TestCase子类都会以具体的test方法个数创建自身实例的个数,并加入到TestSuite中,TestSuite的相对详细的类图如图5所示:
一个简单的例子了解一下TestSuite:
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
public class TestSuiteDemo extends TestSuite{
public static Test suite(){
//创建TestSuite对象
TestSuite suite = new TestSuite();
//为TestSuite添加一个测试用例集合,参数为:ClasstestClass
//通过参数可以知道,其实该参数就是TestCase的子类
suite.addTestSuite(TestDemo.class);
//创建具体的测试用例
Test test = TestSuite.createTest(TestDemo.class, "testMethod1");
//添加一个具体的测试用例
suite.addTest(test);
return suite;
}
}
执行结果如图6:
通过代码和运行结果,我们可以看出testMethod1执行了两次而testMethod2只执行了一次,通过分析上述代码得出testMethod1执行两次的原因是:第一次是在addTestSuite的时候将其作为一个测试用例传入到TestSuite中,第二次是通过addTest方法将用例加入到TestSuite中,因此在执行的时候将其执行了两遍,通过比较hashCode得出总共创建了三个TestCase对象的结论。
通过上述代码,我们需要强调一下,如果我们想一次执行一组TestCase实现类的测试,这个时候可以自定义TestSuite对象,将需要测试的TestCase实现类加入到TestSuite中去。我们需要了解TestSuite如何使用,其实TestSuite的使用也很简单,在TestSuite的使用的时候,我们必须在TestSuite的实现类中,自定义suite方法,由于suite方法会通过反射调用,反射调用代码如下:
public static Test testFromSuiteMethod(Classklass) throws Throwable {
Method suiteMethod= null;
Test suite= null;
try {
suiteMethod= klass.getMethod("suite");
if (! Modifier.isStatic(suiteMethod.getModifiers())) {
throw new Exception(klass.getName() + ".suite() must be static");
}
suite= (Test) suiteMethod.invoke(null); // static method
} catch (InvocationTargetException e) {
throw e.getCause();
}
return suite;
}
所以suite方法命名规则如下:
1).必须以“suite”方法命名;
2).suite方法的访问修饰权限必须为static;
3).suite方法必须为静态方法;
4).suite方法必须没有参数。
总结:
TestCase和TestSuite类是JUNIT中比较重要的两个类,TestCase和TestSuite可以认为是JAVA的合成设计模式在单元测试中的应用,其实即便我们没有自己声明和创建TestSuite的子类,而且运行的TestCase子类的过程中也会创建TestSuite类,并将要执行的TestCase子类的实例对象添加到TestSuite中去执行,其执行过程可以如图7所示:
在选择JUnit执行引擎的时候,便创建了TestSuite对象,并通过上面介绍TestSuite介绍过的addTestMethod,creatTest,addTest方法,将要测的TestCase类中的所有测试用例给扫描出来,并添加到待测列表中去。在执行JUnit测试引擎的run方法时会调用TestSuite的的run方法,TestSuite在执行自身run方法时会遍历所有TestCase对象的run方法,同一个TestCase子类的run方法会根据自身所包含的测试用例个数被执行相应的次数。