自动化测试之—Hamcrest匹配器
本来计划要写一点Espresso方面知识的,但是想到Espresso中好多控件或者断言都是使用Hamcrest匹配器,另一方面则是Hamcrest匹配器相比JUnit的Assert则显得更加优雅、轻便,更容易阅读。
简述
Hamcrest是用于编写匹配器对象的框架,允许以声明方式定义“匹配”规则。有许多情况下匹配器是不可估量的,例如UI验证或数据过滤,但是在编写灵活测试的领域中,匹配器反而是最常用的。
Hamcrest还是相对比较简单的,API也相对比较少,就从一个小例子说起吧。
假如要你要测试一个集合中是否包含三个元素中的一个,如果包含则断言真,否则为假。把集合的初始化放在@Before
中,则用JUnit的Assert断言写法如下:
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertTrue;
/** * Created by star on 2017/8/3. */
@RunWith(JUnit4.class)
public class HamcrestTest {
private List<String> hamcrestTestList;
@Before
public void setUp() {
hamcrestTestList = new ArrayList<>();
hamcrestTestList.add("first element");
hamcrestTestList.add("second element");
hamcrestTestList.add("third element");
}
@Test
public void assertWithJunitTest() {
assertTrue(hamcrestTestList.contains("first element")
|| hamcrestTestList.contains("second element")
|| hamcrestTestList.contains("third element"));
}
}
assertWithJunitTest方法本身并难以理解,但是你第一眼看到它很可能不太明白它是做什么的,而且代码也不简练,而Hamcrest则正是为了简化断言,可以构建测试表达式的匹配器库。Hamcrest的书写方法如下:
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItem;
...
@Test
public void assertWithHamcrestTest(){
assertThat(hamcrestTestList, hasItem(anyOf(equalTo("first element"), equalTo("second element"),
equalTo("third element"))));
}
常用API
hamcrest的匹配器都看起来很好理解,并且还提供了自定义匹配器的接口,可以满足编写代码的需要。主要的API接口有如下:
- 核心
-
anything
-绝对匹配,无论什么情况下都会匹配成功; -
describedAs
-添加自定义失败描述 -
is
-是否的意思,仅用来改善断言语句的可读性;
-
- 逻辑
-
allOf
-检查是否包含所有的匹配器,相当于与(&&); -
anyOf
-检查是否包含匹配器中的一个,相当于(||); -
not
- 检查是否与匹配器相反,相当于非(!);
-
- 对象
-
equalTo
-检查两个对象是否相等; -
hasToString
- 检查Object.toString; -
instanceOf
,isCompatibleType
-检查对象是否是兼容类型; -
notNullValue
,nullValue
-检查是否是null值; -
sameInstance
-检查对象是否是相同的类型;
-
- Beans
-
hasProperty
-检查对象是否有某种属性;
-
- 集合
-
array
-检查array的元素是否和匹配器描述的相同; -
hasEntry
,hasKey
,hasValue
-测试给定的Map是否有特定的实体、键或者值; -
hasItem
,hasItems
-测试集合是否有一个或者多个元素; -
hasItemInArray
-测试数组中是否有某一元素;
-
- 数字
-
closeTo
给定的数字是否接近于给定的值; -
greaterThan
,greaterThanOrEqualTo
,lessThan
,lessThanOrEqualTo
-给定的数字是否大于、大于等于、小于、小于等于给定的值;
-
- 文本
-
equalToIgnoringCase
-检查给定的字符串是否与另一字符串在忽略大小写的情况下相同; -
equalToIgnoringWhiteSpace
-检查给定的字符串是否与另一字符串在忽略空格的情况下相同; -
containsString
-检查给定的字符串是否包含某一字符串; -
endsWith
-检查给定的字符串是否以某一字符串结尾; -
startsWith
-检查给定的字符串是否以某一字符串开头;
-
这些匹配器除了单独使用外,还可以组合使用,提供更佳精确的匹配。例如
自定义匹配器
hamcrest除了可以使用上述匹配器以外,还可以自己编写合适的匹配器,更好的进行测试。自定义匹配器需要实现Mather接口和一个适当的工厂方法。自定义匹配器一方面可以达到合适的匹配,另一方面也可以简化断言语句。
要想定义自定义匹配器,则需要实现BaseMatcher<T>
或者TypeSafeMatcher<T>
,需要在build.gradle
文件中dependencies
闭包中引入对应的包:
dependencies { androidTestImplementation 'org.hamcrest:hamcrest-core:1.3' }
自定义匹配器有以下两种实现方法:
实现BaseMatcher<T>
接口
如果实现了BaseMatcher<T>
,那么需要重写matches方法,matches方法内部实现我们的逻辑,即要满足什么条件时匹配器将匹配各种条件,并确定是否匹配成功。比如如果要实现自定义匹配器,判断给定的字符串是否以ham
开头,并且以java
结尾,则自定义匹配器实现方式如下:
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
/** * Created by star on 2017/8/4. */
public class IsExceptedStringMatcher extends BaseMatcher<String> {
@Factory
public static <T> Matcher<String> isExceptedString() {
return new IsExceptedStringMatcher();
}
@Override
public boolean matches(Object item) {
String str = (String) item;
if (str != null && str.length() >= 7 && str.startsWith("ham") && str.endsWith("java")) {
return true;
}
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("a string that start with \"ham\" and end with \"java\"");
}
}
之后就可以在断言中直接使用自定义匹配器:
@Test(expected = AssertionError.class)
public void isExceptedStringTest(){
String str="hamcrestjava";
assertThat(str,isExceptedString());
str=null;
assertThat(str,isExceptedString());
}
实现TypeSafeMatcher<T>
接口
如果实现了TypeSafeMatcher <T>
,那么需要重写matchesSafely方法,
protected boolean matchesSafely(T item)
matchesSafely方法内部同样实现我们的逻辑,该方法绝不能接受一个null对象,它总会接受一个类型为T的参数,该参数已被检查是否为null且永远不能为null。如果我们要在自定义匹配器内部进行是否为null检查,则要继续实现BaseMatcher<T>
接口,如果要自定义一个密码检查的匹配器,要求密码必须包含1个特殊符号(!、”、#、$、%、&、’、(、)、*、+、-、.、/)和1个数字,并且密码长度至少为6,则自定义匹配器实现方式如下:
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
/** * Created by star on 2017/8/4. */
public class IsStrongPassword extends TypeSafeMatcher<String> {
@Factory
public static <T> Matcher<String> isStrongPassword() {
return new IsStrongPassword();
}
@Override
protected boolean matchesSafely(String item) {
if (containsSymbol(item) && containsDigit(item) && item.length() >= 6) {
return true;
}
return false;
}
private boolean containsDigit(String password) {
for (char ch : password.toCharArray()) {
if (Character.isDigit(ch)) {
return true;
}
}
return false;
}
private boolean containsSymbol(String password) {
for (char ch : password.toCharArray()) {
if ((int) ch >= 33 && (int) ch <= 47) {
return true;
}
}
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("a string which a strong password");
}
}
接下来你同样可以在测试代码中使用如下的方式使用该匹配器:
@Test(expected = AssertionError.class)
public void IsStrongPasswordTest() {
String password;
password = "!1s2dxs";
assertThat(password, isStrongPassword());
password = "123dsda";
assertThat(password, isStrongPassword());
}
Over…如果有不对的地方欢迎批评指正 :)