开始
通过Android测试之旅之JUnit(一)的学习,我们对JUnit的知识有了初步的认识。聪明的你是不是发现其实并没有你想象的那么难呢?这章我们继续来瞅瞅JUnit还有什么好玩的。今天我们用一个简单的例子给大家进行展示,方便更好的理解。
Parameterized
我们先来看下面一个待测试类PrettyTest
:
public class PrettyTest {
/**
* 根据输入值的大小返回字符串
* @param a 输入值
* @return 返回的字符串结果
*/
public String print(int a){
System.out.println("==========current input number is: " + a + "==========");
return a > 0? "大":"小";
}
}
这个类相当的纯洁。如果你看过上一章的内容,机智的你一定可以很快的写出测试用例PrettyTest1
:
public class PrettyTest1 {
private static PrettyTest mTest;
private static String expectedStrAbove;
private static String expectedStrBelow;
@BeforeClass
public static void create(){
mTest = new PrettyTest();
expectedStrAbove = "大";
expectedStrBelow = "小";
}
@Test
public void testPrintAbove(){
int i = 1;
String resultStr = mTest.print(i);
System.out.println("==========testPrintBelow result string is:"+ resultStr + "==========");
Assert.assertEquals(expectedStrAbove,resultStr);
Assert.assertNotEquals(expectedStrBelow,resultStr);
}
@Test
public void testPrintBelow(){
int i = -1;
String resultStr = mTest.print(i);
System.out.println("==========testPrintBelow result string is:"+ resultStr + "==========");
Assert.assertEquals(expectedStrBelow,resultStr);
Assert.assertNotEquals(expectedStrAbove,resultStr);
}
}
仔细看看,很满意,很傲娇。但是,有个小问题,如果我们要用多个值来测试怎么办?这里只列举了大于0和小于0的两种情况。而在实际的开发中,一个方法中有的时候可能会有n多种情况。这个时候,Parameterized
类的作用就能体现出来了。还是针对于PrettyTest
,我么来看看PrettyTest2
这个测试用例:
@RunWith(Parameterized.class)
public class PrettyTest2 {
//-------初始化代码省略-------
@Parameterized.Parameter
public int testNum;
@Parameterized.Parameters
public static Collection<Integer> initData() {
List<Integer> data = new ArrayList<>();
data.add(-1);
data.add(0);
data.add(1);
return data;
}
@Test
public void test(){
String resultStr = mTest.print(testNum);
System.out.println("==========testPrintBelow result string is:"+ resultStr + "==========");
if (testNum > 0){
//-------判断是否与预期值相同-------
}else{
//-------判断是否与预期值相同-------
}
}
}
代码确实没有少多少,我们再来看下运行结果。
运行了三次,把我们在initData
方法中设置的数据都跑了一遍。然后test
方法中队各种情况进行相应的判断,是不是觉得这样效率提高了不少。其中@Parameter
注解可以设置需要测试的公共变量,而@Parameters
注解则是设置一个装满测试数据的集合。如果你不想使用@Parameter
注解,还可以通过构造函数来设置。代码如下:
private int testNum;
public PrettyTest2(int num){
this.testNum = num;
}
测试结果和之前的测试一模一样。
Rules
JUnit
稍微回味一下,我们继续探索。Rules
注解是个非常有意思注解,我们可以通过该注解在测试用例中模拟我们需要的行为,听这话有点昏昏的,别急,继续往下看。因为测试的场景有很多,JUnit
为我们定义了很多非常有用的Rules
。比如说TemporaryFolder
这个类,这个类就是JUnit
为我们提供在测试过程中创建文件夹的类,当测试结束之后,文件夹会自动删除。上代码:
public class RuleTester {
private static File testFile = null;
@Rule
public TemporaryFolder mFolder = new TemporaryFolder();
@Before
public void before(){
System.out.println("----------method before testFile is: "+ testFile + "----------");
Assert.assertNull(testFile);
}
@Test
public void test(){
try {
testFile = mFolder.newFile("myfile.txt");
boolean flag = testFile.exists();
System.out.println("----------method test testFile exists flag: "+ flag + "----------");
Assert.assertTrue(flag);
} catch (IOException e) {
Assert.fail("exception is:"+e.getMessage());
}
}
@AfterClass
public static void after(){
boolean flag = testFile.exists();
System.out.println("----------method after testFile exists flag: "+ flag + "----------");
Assert.assertFalse(flag);
}
}
先看下显示结果。
事实证明确实在测试方法的过程中创建过一个文件,并且在测试用例结束的时候文件被删除了。是不是很神奇?很溜?当然,如果你有兴趣一步步的跟进去看源码,你就会发现这其实就是对一个文件创建,使用以及删除的一个过程。TemporaryFolder
类继承了ExternalResource
类,而ExternalResource
类实现了接口TestRule
中的apply
方法。这里不贴代码是不是有点绕,别急,我们先继续往下走,走完再来回头看。
自定义
上面提到了TestRule
这个神奇的接口,我打算写一个自带日志打印的Rule
,里面包含一个打印消息的方法。在控制台可以看到Rule
被调用的过程。好,开始行动!
public class LogRule implements TestRule{
private Statement mBase;
@Override
public Statement apply(Statement base, Description description) {
this.mBase = base;
return new LogStatement(base);
}
public void print(String message){
System.out.println("LogRule message is:" + message);
}
public class LogStatement extends Statement{
private final Statement base;
public LogStatement(Statement base) {
this.base = base;
}
@Override
public void evaluate() throws Throwable {
System.out.println("method evaluate before");
try{
base.evaluate();
}finally {
System.out.println("method evaluate after");
}
}
}
}
在这个自定义的Rule
中,我们重写了apply
方法,添加了print
方法,并且重写了Statement
类中的evaluate
方法,在这个方法前后我们加了日志的打印。下面来看下在测试用例中的调用和输出结果。
public class LogRuleTest {
@Rule
public LogRule mLogRule= new LogRule();
@Test
public void test(){
String message = "method test";
mLogRule.print(message);
}
}
调用是不是很方便,现在在回头看看之前我们讲到的文件创建,使用和删除的过程,是不是一下子就恍然大悟了。那句俗话怎么说的,车到山前必有路,船到桥头自然直。
Categories
看到这里有点累了吧,别急,快结束了。在上篇我们介绍过了SuiteClasses
来选择需要测试的测试用例。而Categories
这个注解则可以帮你更上一层楼。为什么这么说呢,因为这个注解可以给你测试用例中的测试方法进行归类。闲话不多说,上代码!
public class ICategories {
public interface First {
}
public interface Second {
}
}
public class CategoriesA {
@Test
public void a() {
System.out.println("------class CategoriesA method a called------");
}
@Category(ICategories.First.class)
@Test
public void b() {
System.out.println("------class CategoriesA method b called------");
}
}
@Category({ ICategories.First.class, ICategories.Second.class })
public class CategoriesB {
@Test
public void c() {
System.out.println("------class CategoriesB method c called------");
}
}
有三个文件,希望大家不要看晕了。我们首先定义两种测试的类型,分别是接口First和Second。然后通过注解@Category
对不同测试用例中的不同方法进行标注,最后选择自己需要测试的内容。其中测试类CategoriesA
的a
方法没有任何标志,b
方法注明了First
接口。CategoriesB
测试用例上注明了First
和Second
两个接口。我们接着往下看:
@RunWith(Categories.class)
@Categories.IncludeCategory(ICategories.First.class)
@Categories.ExcludeCategory(ICategories.Second.class)
@Suite.SuiteClasses({ CategoriesA.class, CategoriesB.class })
public class CategoriesTest {
}
我们运行CategoriesTest
这个测试用例。结果如下:
仅仅运行了CategoriesA
的b
方法,想必聪明的你已经知道了,@IncludeCategory
注解表示我们需要包含哪个接口,@ExcludeCategory
注解则表示我们剔除哪些接口。所以包含First
接口而不包含Second
接口的方法只有CategoriesA
的b
方法。好啦!你可以边回味今天的知识边休息啦。
结尾
到这里,我们Android测试之旅之JUnit的全部内容就已经结束啦。不知道这些内容是否可以帮助您解决问题。当然,有什么问题可以随时留言,我一定会积极回答的。