Dream Maker

因上努力,果上随缘

热爱自然,喜欢动物,渴望旅行,积攒幸福的宅男


Android测试之旅(二)

开始


通过Android测试之旅之JUnit(一)的学习,我们对JUnit的知识有了初步的认识。聪明的你是不是发现其实并没有你想象的那么难呢?这章我们继续来瞅瞅JUnit还有什么好玩的。今天我们用一个简单的例子给大家进行展示,方便更好的理解。

Parameterized


我们先来看下面一个待测试类PrettyTest:

1
2
3
4
5
6
7
8
9
10
11
public class PrettyTest {
/**
* 根据输入值的大小返回字符串
* @param a 输入值
* @return 返回的字符串结果
*/

public String print(int a){
System.out.println("==========current input number is: " + a + "==========");
return a > 0? "大":"小";
}
}

这个类相当的纯洁。如果你看过上一章的内容,机智的你一定可以很快的写出测试用例PrettyTest1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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这个测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@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注解,还可以通过构造函数来设置。代码如下:

1
2
3
4
private int testNum;
public PrettyTest2(int num){
this.testNum = num;
}

测试结果和之前的测试一模一样。

Rules


JUnit

稍微回味一下,我们继续探索。Rules注解是个非常有意思注解,我们可以通过该注解在测试用例中模拟我们需要的行为,听这话有点昏昏的,别急,继续往下看。因为测试的场景有很多,JUnit为我们定义了很多非常有用的Rules。比如说TemporaryFolder这个类,这个类就是JUnit为我们提供在测试过程中创建文件夹的类,当测试结束之后,文件夹会自动删除。上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
事实证明确实在测试方法的过程中创建过一个文件,并且在测试用例结束的时候文件被删除了。是不是很神奇?很溜?当然,如果你有兴趣一步步的跟进去看源码,你就会发现这其实就是对一个文件创建,使用以及删除的一个过程。TemporaryFolder类继承了ExternalResource类,而ExternalResource类实现了接口TestRule中的apply方法。这里不贴代码是不是有点绕,别急,我们先继续往下走,走完再来回头看。

自定义

上面提到了TestRule这个神奇的接口,我打算写一个自带日志打印的Rule,里面包含一个打印消息的方法。在控制台可以看到Rule被调用的过程。好,开始行动!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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方法,在这个方法前后我们加了日志的打印。下面来看下在测试用例中的调用和输出结果。

1
2
3
4
5
6
7
8
9
10
11
public class LogRuleTest {

@Rule
public LogRule mLogRule= new LogRule();

@Test
public void test(){
String message = "method test";
mLogRule.print(message);
}
}

自定义Rule
调用是不是很方便,现在在回头看看之前我们讲到的文件创建,使用和删除的过程,是不是一下子就恍然大悟了。那句俗话怎么说的,车到山前必有路,船到桥头自然直。

Categories


看到这里有点累了吧,别急,快结束了。在上篇我们介绍过了SuiteClasses来选择需要测试的测试用例。而Categories这个注解则可以帮你更上一层楼。为什么这么说呢,因为这个注解可以给你测试用例中的测试方法进行归类。闲话不多说,上代码!

1
2
3
4
5
6
7
8
public class ICategories {

public interface First {
}

public interface Second {
}
}

1
2
3
4
5
6
7
8
9
10
11
12
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------");
}
}
1
2
3
4
5
6
7
@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对不同测试用例中的不同方法进行标注,最后选择自己需要测试的内容。其中测试类CategoriesAa方法没有任何标志,b方法注明了First接口。CategoriesB测试用例上注明了FirstSecond两个接口。我们接着往下看:

1
2
3
4
5
6
7
@RunWith(Categories.class)
@Categories.IncludeCategory(ICategories.First.class)
@Categories.ExcludeCategory(ICategories.Second.class)
@Suite.SuiteClasses({ CategoriesA.class, CategoriesB.class })
public class CategoriesTest {

}

我们运行CategoriesTest这个测试用例。结果如下:
CategoriesTest
仅仅运行了CategoriesAb方法,想必聪明的你已经知道了,@IncludeCategory注解表示我们需要包含哪个接口,@ExcludeCategory注解则表示我们剔除哪些接口。所以包含First接口而不包含Second接口的方法只有CategoriesAb方法。好啦!你可以边回味今天的知识边休息啦。

结尾


到这里,我们Android测试之旅之JUnit的全部内容就已经结束啦。下面菜主要好好研究Android相关方面的测试啦。