一、使用JUnit的一般测试语法
org.junit.Assert类里有各种断言方法,大部分情况下我们会像下面这个例子一样编写测试:
1 public class AssertThatTest { 2 private int id = 6; 3 private boolean trueValue = true; 4 private Object nullObject = null; 5 private String msg = "Hello World"; 6 7 @Test 8 public void testAssert() throws Exception { 9 assertEquals(6, id); 10 assertTrue(trueValue); 11 assertNull(nullObject); 12 assertTrue(msg != null && msg.startsWith("Hello") && msg.endsWith("World")); 13 } 14 }
但是这些基本的断言有些可读性并不是很好,例如上面最后一个断言,判断一个字符串以“Hello”开头,以“Workd”结尾,由于没有assertStartWith和assertEndWith之类的函数,我们不得不自己编写表达式并断言其结果。并且因为我们没有提供失败的信息,当这个断言失败时只会抛出java.lang.AssertionError,根本不知道是因为msg为null还是msg的内容错误。
二、使用assertThat与Matcher
在org.junit.Assert中除了常用的相等、布尔、非空等断言,还有一种assertThat,需要配合org.hamcrest.Matcher使用,这种断言的语法为:
assertThat([reason, ]T actual, Matcher<? super T> matcher),其中,reason为断言失败时的输出信息,actual为断言的值或对象,matcher为断言的匹配器,里面的逻辑决定了给定的actual对象满不满足断言。
在org.hamcrest.CoreMatchers类中组织了所有JUnit内置的Matcher,调用其任意一个方法都会创建一个与方法名字相关的Matcher。
使用assertThat重写上述方法:
1 public class AssertThatTest { 2 private int id = 6; 3 private boolean trueValue = true; 4 private Object nullObject = null; 5 private String msg = "Hello World!"; 6 7 @Test 8 public void testAssertThat() throws Exception { 9 //由于静态导入了org.haibin369.matcher.MyMatchers.*,可以调用里面的 10 //is(), nullValue(), containsString(), startsWith()方法,可读性更好 11 assertThat(id, is(6)); 12 assertThat(trueValue, is(true)); 13 assertThat(nullObject, nullValue()); 14 assertThat(msg, both(startsWith("Hello")).and(endsWith("World"))); 15 } 16 }
重写后的测试和之前的效果一模一样,但是可读性更好了,最后一个断言,能一眼看出来是要以“Hello”开头并以“World”结尾的字符串。如果把startsWith("Hello")改成startsWith("Helloo"),它的失败信息也比较直观:
java.lang.AssertionError: Expected: (a string starting with "Helloo" and a string ending with "World") but: a string starting with "Helloo" was "Hello World!"
三、自定义Matcher
现在我们有一个User对象,只包含两个变量机器setter和getter:username,password,当username和password都为“admin”时表示是管理员(Admin User)。现在我们来创建一个自己的Matcher并运用到assertThat语法中去。
首先看看org.hamcrest.Matcher接口的源码
1 /** 2 * A matcher over acceptable values. 3 * A matcher is able to describe itself to give feedback when it fails. 4 * <p/> 5 * Matcher implementations should <b>NOT directly implement this interface</b>. 6 * Instead, <b>extend</b> the {@link BaseMatcher} abstract class, 7 * which will ensure that the Matcher API can grow to support 8 * new features and remain compatible with all Matcher implementations. 9 * <p/> 10 * For easy access to common Matcher implementations, use the static factory 11 * methods in {@link CoreMatchers}. 12 * <p/> 13 * N.B. Well designed matchers should be immutable. 14 * 15 * @see CoreMatchers 16 * @see BaseMatcher 17 */ 18 public interface Matcher<T> extends SelfDescribing { 19 20 boolean matches(Object item); 21 22 void describeMismatch(Object item, Description mismatchDescription); 23 24 @Deprecated 25 void _dont_implement_Matcher___instead_extend_BaseMatcher_(); 26 }
类注释上强调,Matcher实现类不应该直接实现这个接口,而应该继承org.hamcrest.BaseMatcher抽象类
1 public abstract class BaseMatcher<T> implements Matcher<T> { 2 3 /** 4 * @see Matcher#_dont_implement_Matcher___instead_extend_BaseMatcher_() 5 */ 6 @Override 7 @Deprecated 8 public final void _dont_implement_Matcher___instead_extend_BaseMatcher_() { 9 // See Matcher interface for an explanation of this method. 10 } 11 12 @Override 13 public void describeMismatch(Object item, Description description) { 14 description.appendText("was ").appendValue(item); 15 } 16 17 @Override 18 public String toString() { 19 return StringDescription.toString(this); 20 } 21 }
编写IsAdminMatcher,需要实现两个方法,第一个是matches,判断给定的对象是否是所期待的值,第二个是describeTo,把应该得到的对象的描述添加进Description对象中。
/** * 断言一个给定的User对象是管理员 */ public class IsAdminMatcher extends BaseMatcher<User> { /** * 对给定的对象进行断言判定,返回true则断言成功,否则断言失败 */ @Override public boolean matches(Object item) { if (item == null) { return false; } User user = (User) item; return "admin".equals(user.getUsername()) && "admin".equals(user.getPassword()); } /** * 给期待断言成功的对象增加描述 */ @Override public void describeTo(Description description) { description.appendText("Administrator with 'admin' as username and password"); } }
执行测试:
1 public class AssertThatTest { 2 User user = new User("haibin369", "123456"); 3 4 @Test 5 public void testAdmin() throws Exception { 6 assertThat(user, new IsAdminMatcher()); 7 } 8 }
测试可以正常执行,但是上面的User对象并不是管理员,因此测试会失败,以下信息会输出:
- java.lang.AssertionError:
- Expected: Administrator with 'admin' as username and password
- but: was <org.haibin369.model.User@570b13e4>
查看源代码,我们发现but后面的信息是在BaseMatcher中的describeMismatch方法输出的,通过这个信息明显不清楚到底实际上得到了什么User,因此在我们的Matcher中从写这个方法:
1 /** 2 * 当断言失败时,描述实际上得到的错误的对象。 3 */ 4 @Override 5 public void describeMismatch(Object item, Description description) { 6 if (item == null) { 7 description.appendText("was null"); 8 } else { 9 User user = (User) item; 10 description.appendText("was a common user (") 11 .appendText("username: ").appendText(user.getUsername()).appendText(", ") 12 .appendText("password: ").appendText(user.getPassword()).appendText(")"); 13 } 14 }
重新执行测试,得到以下失败信息:
- java.lang.AssertionError:
- Expected: Administrator with 'admin' as username and password
- but: was a common user (username: haibin369, password: 123456)
虽然我们自定义的Matcher已经能够执行了,但是assertThat(user, new IsAdminMatcher());这段代码并没有达到之前所说的可读性更好的要求,因此,我们仿照org.hamcrest.CoreMatchers,创建一个类去创建我们自定义的Matcher:
1 public class MyMatchers { 2 public static Matcher<User> isAdmin() { 3 return new IsAdminMatcher(); 4 } 5 }
在测试方法中静态导入该类中的所有内容,则可以像下面一样使用assertThat:
1 import static org.haibin369.matcher.MyMatchers.*; 2 3 public class AssertThatTest { 4 5 User user = new User("haibin369", "123456"); 6 7 @Test 8 public void testAdmin() throws Exception { 9 assertThat(user, isAdmin()); 10 } 11 }