测试的时候常常要比较实际获得的对象是否相同于期望的对象,这类对象一般是JavaBean。
比较容易想到的方法是用JavaBean的各个getters获得相应的成员属性值,逐一比较:assertEquals(obj1.getXXX(),obj2.getXXX());
这种方法无疑是最繁琐的。
再则使用反射机制:
for(String prop:props){ // props为JavaBean的成员方法名字数组
    Method method = obj1.getClass().getDeclaredMethod(prop);
    assertEquals(method.invoke(obj1),method.invoke(obj2));
}
这种情况的比较常常用于JavaBean,可以利用getter命名规则,直接构造成员属性数组:
for(String prop:props){ // props为JavaBean的成员属性名字数组
    Method method = obj1.getClass().getDeclaredMethod("get"
+prop.substring(0,1).toUpperCase()+prop.substring(1));
    assertEquals(method.invoke(obj1),method.invoke(obj2));
}
Unitils里使用ongl框架实现JavaBean成员属性的比较:
ReflectionAssert. assertPropertyLenientEquals(java.lang.String propertyName,
 java.lang.Object expectedPropertyValue, java.lang.Object actualObject);
这种方法依赖构造的成员方法或成员属性的名字,且是硬编码,不容易重构,即使是使用属性文件,将硬编码抽出,属性文件要维护起来也比较麻烦。有多少个成员属性就需要写多少条assertPropertyLenientEquals(...)再则参照上面的方法,将propertyName构造成数组,然后遍历比较。
事实上可以不用反射机制而又能避免第一种繁琐比较的方法:简单的利用equals()方法重写!
Java里面的相等,至少有两种情况:(两个对象obj1和obj2)
  • l 第一种相等(==):两个句柄(或叫引用)指向了同一个对象;即obj1==obj2为true
  • l 第二种相等(equal):两个对象里包含的成员属性对应相等;
Object类中equals()方法定义:

public boolean equals(Object obj) {

    return (this == obj);

}
一般自定义的类,为使equals()方法名副其实,都需要对它进行重写。
在举例子先介绍下例子中几个JavaBean的关系:
e69caae591bde5908d
其中User、POJO、Item重写了equals()方法。
POJO中重写equals()可以方便的利用eclipse工具自动生成代码的功能:
    @Override

    public boolean equals(Object obj) {

        if (this == obj)

            return true;

        if (obj == null)

            return false;

        if (getClass() != obj.getClass())

            return false;

        final POJO other = (POJO) obj;

        if (id == null) {

            if (other.id != null)

                return false;

        } else if (!id.equals(other.id))

            return false;

        if (owner == null) {

            if (other.owner != null)

                return false;

        } else if (!owner.equals(other.owner))

            return false;

        if (pic == null) {

            if (other.pic != null)

                return false;

        } else if (!pic.equals(other.pic))

            return false;

        if (title == null) {

            if (other.title != null)

                return false;

        } else if (!title.equals(other.title))

            return false;

        if (url == null) {

            if (other.url != null)

                return false;

        } else if (!url.equals(other.url))

            return false;

        return true;

    }

当然POJO中的owner为User对象,User对象的equals()方法也要重写。

 
Item继承自POJO,其equals()重写可以如下:

    @Override

    public boolean equals(Object obj) {

        if(!super.equals(obj))

            return false;

        if(!(obj instanceof Item))

            return false;

        Item that = (Item)obj;

        if(this.currPrice == null && that.currPrice == null)

// 注意这里一定是this.XX.equals(that.XX)[因为使用了短路与&&],以免出现

// NullPointerException

            if(this.remainTime == null && that.remainTime == null

||this.remainTime.equals(that.remainTime))

                return true;

        if(this.remainTime == null && that.remainTime == null

&& this.currPrice.equals(that.currPrice))

            return true;

        if(this.currPrice.equals(that.currPrice)

&& this.remainTime.equals(that.remainTime))

            return true;

        return false;

    }
当然也可以方便的使用代码自动生成:

@Override

    public boolean equals(Object obj) {

        if (this == obj)

            return true;

        if (!super.equals(obj))

            return false;

        if (getClass() != obj.getClass())

            return false;

        final Item other = (Item) obj;

        if (currPrice == null) {

            if (other.currPrice != null)

                return false;

        } else if (!currPrice.equals(other.currPrice))

            return false;

        if (remainTime == null) {

            if (other.remainTime != null)

                return false;

        } else if (!remainTime.equals(other.remainTime))

            return false;

        return true;

    }
 
于是:

@Test

    public void testItemEqual() {

        Item item1 = new Item(2l,new User(1l,"pwd","name"),"title","pic","url",

15.0f,new Date());

        Item item2 = new Item(2l,new User(1l,"pwd","name"),"title","pic","url",

15.0f,new Date());

        // 一句assertEquals搞定

        assertEquals(item1, item2);

    }
这样看来似乎价值还不是很大,再来看ItemList比较,顺便介绍下Comparator接口:

public void testItemListEqualV2() {

        List<Item> itemList1 = new ArrayList<Item>();

        List<Item> itemList2 = new ArrayList<Item>();

        for(int i=0;i<10;i++)

            itemList1.add(new Item((long)i,new User((long)i,"pwd","name"),

"title","pic","url",15.0f,new Date()));

        for(int i=9;i>=0;i--)

            itemList2.add(new Item((long)i,new User((long)i,"pwd","name"),

"title","pic","url",15.0f,new Date()));

        int size = itemList1.size();

        // 以上准备数据,可以略过

                   // 开始比较

        assertEquals(size, itemList2.size());

        Item[] itemArray1 = new Item[size];

        Item[] itemArray2 = new Item[size];

        Arrays.sort((Item[])itemList1.toArray(itemArray1), new ItemComparator());

        Arrays.sort((Item[])itemList2.toArray(itemArray2), new ItemComparator());

        for(int j =0 ; j<size;j++){

            assertEquals(itemArray1[j],itemArray2[j]);

        }

    }

    class ItemComparator implements Comparator<Item>{

        public int compare(Item item1, Item item2) {

            if(item1.getId() < item2.getId())

                return -1;

            else if(item1.getId() > item2.getId())

                return 1;

            else

                return 0;

        }

    }
思路就是将期望list和实际list进行排序后逐个比较,这里明显有一个好处,可以明确的知道list中哪个位置的对象assertEquals() 失败。
 
到此结束了吗?看也没有什么多大好处,重写equals()照样费事?
当然不是!第三种相等!想怎么比较就怎么比较!!
往往开发并不会给我们重写好equals(),当然测试也不能随便改开发的代码。
看我怎么比较没有重写equals()的Blog对象,又是代码:

    @Test

    public void testBlogEqual() {

        Blog blog1 = new Blog(4l,new User(1l,"pwd","name"),

"title","pic","url","description"){

            @Override

            public boolean equals(Object obj){

                if(super.equals(obj)){

                    if(obj instanceof Blog){

                        Blog that = (Blog)obj;

                        if(this.getDescription() == null

&& that.getDescription() == null)

                            return true;

                        if(this.getDescription().equals(that.getDescription()))

                            return true;

                    }

                }

                return false;

            }

        };

        Blog blog2 = new MockBlog(4l,new User(1l,"pwd","name"),

"title","pic","url","description");

        Blog blog3 = new Blog(4l,new User(1l,"pwd","name"),

"title","pic","url","description");

        Blog blog4 = new Blog(4l,new User(1l,"pwd","name"),

"title","pic","url","description4");

        assertEquals(blog1, blog3);  // success

        assertEquals(blog2, blog3);  // success

        assertEquals(blog1, blog4);  // fail

        assertEquals(blog2, blog4);  // fail

        assertEquals(blog4, blog1);  // success,原因是Blog的超类POJO重写了equals()

        assertEquals(blog4, blog2);  // success

    }

class MockBlog extends Blog{

        public MockBlog() {

            super();

        }

        public MockBlog(Long id, User user, String title, String pic,

                String url, String description) {

            super(id, user, title, pic, url, description);

        }

        public MockBlog(Long id, User user) {

            super(id, user);

        }

        @Override

        public boolean equals(Object obj){

            if(super.equals(obj)){

                                     // 注意这里一定是查看是否Blog的实例

                if(obj instanceof Blog){

                    Blog that = (Blog)obj;

                          if(this.getDescription() == null

&& that.getDescription() == null)

                        return true;

                    if(this.getDescription().equals(that.getDescription()))

                        return true;

                }

            }

            return false;

        }

    }

说明一下:测试方法testBlogEqual()中blog1、blog2、blog3里面各个成员的值都相等(equal)、blog4的description不同。blog1和blog2都是Blog子类的实例,只不过blog1是Blog匿名子类的实例。可以看到几个assertEquals()的结果如注释。其中需要说明的是:

看Assert.assertEquals()源码

static public void assertEquals(String message, Object expected, Object actual) {

        if (expected == null && actual == null)

            return;

        if (expected != null && expected.equals(actual))

            return;

        failNotEquals(message, expected, actual);

    }
这里可以看出assertEquals()实际调用的是expected对象的equals方法,所以只管对期望对象进行mock!
于是再举个例子:Shop(id,owner,title,pic,url,buyNo),只想要比较id和buyNo

@Test

    public void testShopEqual() {

        Shop shop1 = new Shop(1l,new User(1l,"pwd","name"),"title","pic1","url1",20l){

            @Override

            public boolean equals(Object obj) {

                if (this == obj)

                    return true;

                if (obj instanceof POJO) {

                    POJO other = (POJO) obj;

                    if (id == null) {

                        if (other.getId() != null)

                            return false;

                    } else if (!id.equals(other.getId()))

                        return false;

                }

                if (!(obj instanceof Shop))

                    return false;

                final Shop other = (Shop) obj;

                if (this.getBuyerNo() == null) {

                    if (other.getBuyerNo() != null)

                        return false;

                } else if (!this.getBuyerNo().equals(other.getBuyerNo()))

                    return false;

               

                return true;

            }

        };

        Shop shop2 = new Shop(1l,new User(1l,"pwd","name"),"title","pic2","url2",20l);

        assertEquals(shop1, shop2);

    }
assertEquals()通过。
以上因为Shop又继承POJO,所以equals()重写得有些复杂,一般JavaBean都直接继承Object,就没有这么复杂了。
为了重用新写的equals()方法,可以将你名字类mock为一个内部类(外部类当然也可以)。
再举一例:常常在进行数据库校验之前需要assertNotNull(),以User为例

@Test

    public void testUserNotNull() {

        User user1 = new User(){

            @Override

            public boolean equals(Object obj) {

                if((obj instanceof User)){

                    User that = (User)obj;

                    if(that.getId()!=null && that.getName()!=null && that.getPassword()!=null)

                        return true;

                }

                return false;

            }

        };

        User user2 = new User(1l,"pwd","name");

        assertEquals(user1, user2);

    }
再结合Debug工具,也很快就能定位到哪个属性值为null。
 
That all , enjoy!