JUnit是java开发的一个测试框架,Android当然也能用
我个人的感觉是它比较适合测试纯Java逻辑的代码,比如工具类,算法的计算,类的数据操作等等。在app -> src目录下,test就是它的工作目录。 在类名字上右键,go-to,选择test,就可以自动创建一个对应的测试类。在左边的文件目录里面对测试类文件右键,run,就可以执行里面的测试方法。
基础
引入方法:
1
| testImplementation 'junit:junit:4.12'
|
JUnit4基础方法注解和常用的assertEquals之类的断言就不用说了,看名字基本都明白。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public class DateUtilTest {
private String time = "2017-10-15 16:00:02";
private long timeStamp = 1508054402000L;
private Date mDate;
@Before public void setUp() throws Exception { System.out.println("测试开始!"); mDate = new Date(); mDate.setTime(timeStamp); }
@After public void tearDown() throws Exception { System.out.println("测试结束!"); }
@Test public void dateToStampTest() throws Exception { assertNotEquals(4, DateUtil.dateToStamp(time)); }
@Test(expected = ParseException.class) public void dateToStampTest1() throws Exception { DateUtil.dateToStamp("2017-10-15"); }
@Test @Ignore("test方法不执行\n") public void test() { System.out.println("-----"); }
@Test(timeout = 2000) public void testTimeout() { System.out.println("timeout method called in thread " + Thread.currentThread().getName()); }
@Test(expected = IndexOutOfBoundsException.class) public void empty() { new ArrayList<Object>().get(0); } }
|
不过这里的@Test注解还有两个属性可以指定
Hamcrest与assertThat
Hamcrest是一个表达式类库,它提供了一套匹配符Matcher,JUnit4结合Hamcrest提供了一个全新的断言语法:assertThat,结合Hamcrest提供的匹配符,可以表达全部的测试思想。使用gradle引入JUnit4.12时已经包含了hamcrest-core.jar、hamcrest-library.jar、hamcrest-integration.jar这三个jar包,所以我们无需额外再单独导入hamcrest相关类库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class AssertThatTest {
@Test public void testMobilePhone() throws Exception { Assert.assertThat("13588888888", new IsMobilePhoneMatcher()); }
@Test public void testAssertThat1() throws Exception { Assert.assertThat(6, CoreMatchers.is(6)); }
@Test public void testAssertThat2() throws Exception { Assert.assertThat(null, IsNull.nullValue()); }
@Test public void testAssertThat3() throws Exception { Assert.assertThat("Hello python world",CoreMatchers.both(CoreMatchers.startsWith("Hello")).and(CoreMatchers.endsWith("World"))); } }
|
自定义匹配器
assertThat会用到匹配器,我们也可以自己定义匹配规则
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 34 35 36 37
| import org.hamcrest.BaseMatcher; import org.hamcrest.Description;
import java.util.regex.Matcher; import java.util.regex.Pattern;
public class IsMobilePhoneMatcher extends BaseMatcher<String> {
@Override public boolean matches(Object item) { if (item == null) { return false; } Pattern pattern = Pattern.compile("(1|861)(3|5|7|8)\\d{9}$*"); Matcher matcher = pattern.matcher((String) item); return matcher.find(); }
@Override public void describeTo(Description description) { description.appendText("预计此字符串是手机号码!"); }
@Override public void describeMismatch(Object item, Description description) { description.appendText(item.toString() + "不是手机号码!"); } }
|
自定义 Rule
Rule给我的感觉类似于动态代理里面的InvocationHandler,在测试类中使用 @Rule 注解标记一个 Rule 接口的实现类,那么在 Rule 的 apply方法中就可以拦截到这个测试类的所有测试方法。
自带的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 NameRuleTest { @Rule public final TestName name = new TestName();
@Test public void testA() { assertEquals("testA", name.getMethodName()); }
@Test public void testB() { assertEquals("testB", name.getMethodName()); } }
public class TimeoutRuleTest { @Rule public final Timeout globalTimeout = Timeout.millis(20);
@Test public void testInfiniteLoop1() { for (; ; ) { } }
@Test public void testInfiniteLoop2() { for (; ; ) { } } }
|
自定义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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
|
public class AssertThatTest {
@Rule public MyRule rule = new MyRule();
@Test public void testAssertThat1() throws Exception { Assert.assertThat(6, CoreMatchers.is(6)); }
@Test public void testAssertThat2() throws Exception { Assert.assertThat(null, IsNull.nullValue()); }
@Test public void testAssertThat3() throws Exception { Assert.assertThat("Hello python world",CoreMatchers.both(CoreMatchers.startsWith("Hello")).and(CoreMatchers.endsWith("World"))); }
public class MyRule implements TestRule {
@Override public Statement apply(final Statement base, final Description description) {
return new Statement() { @Override public void evaluate() throws Throwable { String methodName = description.getMethodName(); System.out.println(methodName + "测试开始!");
base.evaluate();
System.out.println(methodName + "测试结束!"); } }; } } }
public class RepeatRule implements TestRule { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Repeat { int count(); }
@Override public Statement apply(final Statement base, final Description description) { Statement repeatStatement = new Statement() { @Override public void evaluate() throws Throwable { Repeat repeat = description.getAnnotation(Repeat.class); if (repeat != null) { for (int i = 0; i < repeat.count(); i++) { base.evaluate(); } } else { base.evaluate(); } } }; return repeatStatement; } }
public class RepeatTest { @Rule public final RepeatRule repeatRule = new RepeatRule();
@RepeatRule.Repeat(count = 5) @Test public void testMethod() throws IOException { System.out.println("---test method---"); }
@Test public void testMethod2() throws IOException { System.out.println("---test method2---"); } }
|
测试方法的执行顺序
当我们运行一个测试类里的所有测试方法时,测试方法的执行顺序并不是固定的,JUnit4提供@ FixMethodOrder注解来配置执行顺序,其可选值有:MethodSorters.NAME_ASCENDING、MethodSorters.DEFAULT、MethodSorters.JVM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestExecOrder { @Test public void testD() { System.out.println("DDDDD"); }
@Test public void testA() { System.out.println("AAAAA"); }
@Test public void testB() { System.out.println("BBBBB"); }
@Test public void testC() { System.out.println("CCCCC"); } }
|
Test runners
所有的单元测试方法都是通过Runner来执行的。Runner只是一个抽象类,它是用来跑测试用例并通知结果的,JUnit提供了很多Runner的实现类,可以根据不同的情况选择不同的test runner。
通过@RunWith注解,可以为我们的测试用例选定一个特定的Runner来执行。
默认的test runner是 BlockJUnit4ClassRunner。
@RunWith(JUnit4.class),使用的依然是默认的test runner,实质上JUnit4继承自BlockJUnit4ClassRunner。
Suite
Suite 翻译过来是测试套件,意思是让我们将一批其他的测试类聚集在一起,然后一起执行,这样就达到了同时运行多个测试类的目的。
1 2 3 4 5 6 7 8 9
| @RunWith(Suite.class) @Suite.SuiteClasses({ TestLogin.class, TestLogout.class, TestUpdate.class }) public class TestSuite { }
|
执行运行TestSuite,相当于同时执行了这3个测试类。
Suite还可以进行嵌套,即一个测试Suite里包含另外一个测试Suite。
1 2 3 4
| @RunWith(Suite.class) @Suite.SuiteClasses(TestSuite.class) public class TestSuite2 { }
|
Parameterized 参数化
假如我们有一个待测试类
1 2 3 4 5 6 7 8 9 10 11
| public class Fibonacci { public static int compute(int n) { int result = 0; if (n <= 1) { result = n; } else { result = compute(n - 1) + compute(n - 2); } return result; } }
|
针对这个函数,我们需要多个输入参数来验证是否正确
- 使用构造函数来注入参数值
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 TestParams { @Parameterized.Parameters public static List getParams() { return Arrays.asList(new Integer[][]{{0, 0}, {1, 1}, {2, 1}, {3, 2}, {4, 3}, {5, 5}, {6, 8}}); }
private int input; private int expected;
public TestParams(int input, int expected) { this.input = input; this.expected = expected; }
@Test public void testFibonacci() { System.out.println(input + "," + expected); Assert.assertEquals(expected, Fibonacci.compute(input)); } }
|
- 使用注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @RunWith(Parameterized.class) public class TestParams2 { @Parameterized.Parameters public static List getParams() { return Arrays.asList(new Integer[][]{{0, 0}, {1, 1}, {2, 1}, {3, 2}, {4, 3}, {5, 5}, {6, 8}}); }
@Parameterized.Parameter public int input;
@Parameterized.Parameter(1) public int expected;
@Test public void testFibonacci() { System.out.println(input + "," + expected); Assert.assertEquals(expected, Fibonacci.compute(input)); } }
|
Categories
Categories继承自Suite,但是比Suite功能更加强大,它能对测试类中的测试方法进行分类执行。当你想把不同测试类中的测试方法分在一组,Categories就很管用。
代码 github地址
引用:
Android单元测试(一):JUnit框架的使用
Android单元测试