断言判断数组、对象、容器相等

文章目录

  • 断言判断数组、对象、容器相等
    • 1 数组
      • 1.1 assertArrayEquals()
      • 1.2 分析
    • 2 对象
      • 2.1 assertEquals()
      • 2.2 equals()与hashCode()
    • 3 容器
      • 3.1 assertTrue()
      • 3.2 容器的equals()方法
    • 4 参考链接

断言判断数组、对象、容器相等

在使用Junit进行单元测试时,有时候会遇到测试方法返回数组、对象、容器的情况,如果直接使用assertEquals()方法,大多数情况并不能得到断言结果,这时候该如何判断测试的期望值与实际值是否相等呢?

本文的测试环境为JDK8+JUnit5

1 数组

1.1 assertArrayEquals()

先说结论:断言判断数组使用assertArrayEquals()方法。

被测试类的方法如下:

public class MyClass {
    //静态方法,返回值为数组
	public static int[] arryMethod() {
		return new int[] {0,1,2};
	}
}

为了测试方便,方法为静态方法,返回一个值为{0, 1, 2}的数组。

Junit测试代码如下:

class TestMain {

	@Test
	void test() {
		//错误
		//assertEquals(new int[] {0, 1, 2}, MyClass.arryMethod());
		
		assertArrayEquals(new int[] {0, 1, 2}, MyClass.arryMethod());
	}
}

运行test()方法,你会发现assertArrayEquals()通过,而==assertEquals()==方法测试不通过。

assertArrayEquals()用于判断两个数组是否相等,包括对象数组(对象数组与数组对象一定要能区分开来)。

1.2 分析

接下来将对assertEquals()断言未通过的原因进行分析,以及介绍assertEquals()与assertArrayEquals()方法的源码,发现它们的异同。

assertEquals的测试情况如下所示:
断言判断数组、对象、容器相等_第1张图片
从Failure Trace中很明显可以看到,assertEquals()方法是在比较两个数组对象(没错,数组也是对象!一个比较特殊的对象,它的父类是Object,如果不理解为什么数组是对象可以查看文末参考链接),而非数组的值,所以断言未通过。

(1) 为什么会输出I@402a079呢?

因为数组对象未重写Object的toString()方法,Object的toString方法默认返回对象的名称及引用地址:

public String toString() {       
	return getClass().getName() + "@" + Integer.toHexString(hashCode());       
}   

(2) assertEquals()方法既然没有比较数组的值,那它是如何比较两个数组的?

答案是:先比较两个数组对象的引用,不等则调用equals()方法进行比较

理解数组是对象这一点十分重要,assertEquals()方法将传入的数组视为Object对象进行处理,JUnit源码调用顺序如下:

assertEquals(new int[] {0, 1, 2}, MyClass.arryMethod());
========================================================
//第一步
static void assertEquals(Object expected, Object actual) {
    assertEquals(expected, actual, (String) null);
}

//第二步
static void assertEquals(Object expected, Object actual, String message) {
    if (!objectsAreEqual(expected, actual)) {
        failNotEqual(expected, actual, message);
    }
}

//第三步
static boolean objectsAreEqual(Object obj1, Object obj2) {
    if (obj1 == null) {
        return (obj2 == null);
    }
    return obj1.equals(obj2);
}

先判断两个对象的引用是否相等,如果不相等,调用对象的equals()方法。

而Object.equals()方法默认比较两个对象的引用是否相等。

public boolean equals(Object obj) {
    return (this == obj);
}

(3) 那么assertArrayEquals()又是如何进行比较的呢?

既然assertArrayEquals()方法比较的是数组的值,应该可以猜到,它会去遍历数组进行一一比较。那到底是不是呢?我们来看看源码:

assertArrayEquals(new int[] {0, 1, 2}, MyClass.arryMethod());
=============================================================
//第一步
static void assertArrayEquals(int[] expected, int[] actual) {
	assertArrayEquals(expected, actual, (String) null);
}

//第二步
static void assertArrayEquals(int[] expected, int[] actual, String message) {
    assertArrayEquals(expected, actual, null, message);
}

//第三步
private static void assertArrayEquals(int[] expected, int[] actual, 
                                      Deque<Integer> indexes,
									Object messageOrSupplier) {
    if (expected == actual) {
        return;
    }
    assertArraysNotNull(expected, actual, indexes, messageOrSupplier);
    assertArraysHaveSameLength(expected.length, actual.length, 
                               indexes, messageOrSupplier);

    for (int i = 0; i < expected.length; i++) {
        if (expected[i] != actual[i]) {
            failArraysNotEqual(expected[i], actual[i], 
                               nullSafeIndexes(indexes, i), 
                               messageOrSupplier);
        }
    }
}

可以看到,assertArrayEquals()方法先比较两个数组对象的引用是否相等,再判断是否为NULL,以及是否长度相等,最后再遍历数组,一一比较两个数组的值。

(4) 总结

assertEquals()方法针对的是基本数据类型、单一对象间的断言

  • 基本类型数据:直接比较值
  • 对象:先比较对象的引用,再通过equals()方法比较

assertArrayEquals()方法针对的是数组对象间的断言

  • 基本类型数组对象

    • 先比较数组对象的引用
    • 数组是否为NULL
    • 数组长度是否相等
    • 遍历数组,比较值
  • Object型数组对象(感兴趣的同学可以自己试着查看对应源码)

    • 先比较数组对象的引用
    • 数组是否为NULL
    • 数组长度是否相等
    • 遍历数组,Object对象拆箱
      • 基本类型:直接比较值
      • 对象类型:通过equals()方法比较

2 对象

通过1.2的分析,我们明确了assertEquals()方法针对的是基本数据类型、单一对象间的断言,所以断言对象相等使用assertEquals()方法

2.1 assertEquals()

这里我们写一个自定义类Student以用作测试,

public class Student {
	public String name;
	public Integer age;

	public Student(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
    
    @Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age == null) {
			if (other.age != null)
				return false;
		} else if (!age.equals(other.age))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
}

Student类包含有name、age属性,有参构造方法、覆盖了Object父类的toString()方法和equals()方法。

被测试类的方法如下:

public class MyClass {
	public static Student stuMethod() {
		return new Student("Rhine", 1);
	}
}

通过调用stuMethod()方法返回一个Student对象,以模拟我们的被测试方法。

Junit测试代码如下:

class TestMain {

	@Test
	void test() {
        //断言失败,只比较对象的引用是否相等
        //assertSame(new Student("Rhine",1), MyClass.stuMethod());
		
        assertEquals(new Student("Rhine",1), MyClass.stuMethod());
	}
}

根据1.2中源码的分析,我们重写了Student类的equals()方法(使用Eclipse自动生成的),这样assertEquals()方法就能够成功通过。
断言判断数组、对象、容器相等_第2张图片
当我们注释掉equals()方法,自然断言就不能通过了。
断言判断数组、对象、容器相等_第3张图片

2.2 equals()与hashCode()

说到equals()方法,就不得不与==、hashCode()方法一起来讨论了,这篇文章已经介绍得很清楚了。

3 容器

List、Map、Set等容器也是我们经常用来作为方法返回值,那怎样去断言容器是否相等呢?

我们可以换一个思路去实现对容器判定是否相等。

3.1 assertTrue()

被测试类的方法如下:

public class MyClass {
	public static ArrayList<Object> listMethod() {
		ArrayList<Object> list = new ArrayList<Object>();
		list.add(1);
		list.add(2);
		list.add(3);
		return list;
	}
	
	public static HashMap<String, Object> mapMethod(){
		HashMap<String, Object> map = new HashMap<String, Object>();
		map.put("one", 1);
		map.put("two", 2);
		map.put("three", 3);
		return map;
	}
	
	public static HashSet<Object> setMethod() {
		HashSet<Object> set = new HashSet<Object>();
		set.add(1);
		set.add(2);
		set.add(3);
		return set;
	}
}

分别用于返回我们常用的容器类型。

Junit测试代码如下:

class TestMain {

	@Test
	void test() {
		ArrayList<Object> list = new ArrayList<Object>();
		list.add(1);
		list.add(2);
		list.add(3);
		assertTrue(list.equals(MyClass.listMethod()));
		
		HashMap<String, Object> map = new HashMap<String, Object>();
		map.put("one", 1);
		map.put("two", 2);
		map.put("three", 3);
		assertTrue(map.equals(MyClass.mapMethod()));
		
		HashSet<Object> set = new HashSet<Object>();
		set.add(1);
		set.add(2);
		set.add(3);
		assertTrue(set.equals(set));
	}

}

对了,没错!就是使用assertTrue()断言,利用容器重写过后的equals()方法,就能直接判断两个容器是否相同了。

3.2 容器的equals()方法

或许你还对equals()方法真的能够判断两个容器相等保持怀疑,我们就看选一个ArrayList的equals()方法源码来看看(ArrayList是实现的AbstractList抽象类,所以它的equals()方法是在AbstractList抽象类中实现的)。

AbstractList.class
==================================
public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof List))
        return false;

    ListIterator<E> e1 = listIterator();
    ListIterator<?> e2 = ((List<?>) o).listIterator();
    while (e1.hasNext() && e2.hasNext()) {
        E o1 = e1.next();
        Object o2 = e2.next();
        if (!(o1==null ? o2==null : o1.equals(o2)))
            return false;
    }
    return !(e1.hasNext() || e2.hasNext());
}

equals()方法先判断两个对象的引用是否相等,再判断是否是List类型,最后通过while()循环,同时遍历两个容器,分别取得其中的对象o1、o2,注意if条件语句里是一个取反的三目运算,以保证两个容器的长度以及所有元素时相等的(整个while循环写得既简洁又优雅),如果容器是等长的那么return语句中的表达式则是true。

另外几个容器有兴趣的同学可以自行查看equals()方法的源码。

4 参考链接

理解数组对象:从JVM、反射的角度进行了解释,很具体了。

Object源码介绍:源码配上了少量的介绍。

你可能感兴趣的:(JUnit)