Junit实现抽象类和接口类测试

Junit实现抽象类和接口类测试

简单的目录:


Junit实现接口类测试

写接口的目的一般是,让这个接口的所有实现都具备某个共同的行为。这个行为不仅目前实现的类具备,将来要写的实现也都必须具备。因此,就需要为这个接口编写一个通用的测试程序,这个测试程序不仅能测试当前已经实现的类的通用属性,而且可以不加修改应用于将来要实现的类。
首先引入一个抽象的测试类,该测试类的方法用于测试接口的共同行为。然后使用工厂方法创建接口的对象,以完成测试程序。下面是详细步骤:

  1. 选定测试程序要测试的已经具体实现的类。
  2. 创建一个抽象的测试类,声明要验证的功能的测试方法。在具体的测试程序实现中继承这个测试类,并修改相应的实现方法。
  3. 在接口的每一个具体实现中都运行该测试程序,但在每个实现中都只验证“接口范围内”的行为。
  4. 在测试程序内,找到创建(接口)对象的代码,将该代码改成具体的、已经实现的类的创建方法,但记住将该对象声明为接口的对象,而不是具体实现的类的对象。重复这一过程,直至测试程序中没有已经实现的类的对象。
  5. 声明你要在测试中调用的抽象方法。
  6. 现在,测试只涉及接口和一些抽象的测试方法,请将测试程序移入抽象的测试类。
  7. 重复这一过程直至所有的测试都移入抽象的测试类。
  8. 重复前面的全部过程,直至除了验证具体实现的特有的方法的测试程序外,所有的测试代码都已完成。

下面通过测试java.util.Iterator接口来具体说明这种技巧。首先让我们看代码ListIteratorTest,这是测试java.util.Listiterator接口的一个具体实现。

package junit.cookbook.test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class ListIteratorTest {
       private Iterator noMoreElementsIterator;
       protected abstract Iterator makeNoMoreElementsIterator();

       @Before
       public void setUp() throws Exception {
              List empty = new ArrayList();
              noMoreElementsIterator = empty.iterator();
       }
       @Test
       public void testHasNextNoMoreElements() {
              assertFalse(noMoreElementsIterator.hasNext());
       }
       @Test
       public void testNextNoMoreElements() {
              try {
                     noMoreElementsIterator.next();
                     fail("No exception with no elements remaining!");
              } catch (NoSuchElementException expected) {
              }
       }
       @Test
       public void testRemoveNoMoreElements() {
              try {
                     noMoreElementsIterator.remove();
                     fail("No exception with no elements remaining!");
              } catch (IllegalStateException expected) {
              }
       }
}

接着让引入抽象的IteratorTest测试类,并将ListIteratorTest类的实际的实现添加到IteratorTest。结果如下所示:

package junit.cookbook.test;
import java.util.Iterator;
import java.util.NoSuchElementException;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public abstract class IteratorTest {
       private Iterator noMoreElementsIterator;
       protected abstract Iterator makeNoMoreElementsIterator();
       @Before
       public void setUp() throws Exception {
              noMoreElementsIterator = makeNoMoreElementsIterator();
       }
       @Test
       public void testHasNextNoMoreElements() {
              assertFalse(noMoreElementsIterator.hasNext());
       }
       @Test
       public void testNextNoMoreElements() {
              try {
                     noMoreElementsIterator.next();
                     fail("No exception with no elements remaining!");
              } catch (NoSuchElementException expected) {
              }
       }
       @Test
       public void testRemoveNoMoreElements() {
              try {
                     noMoreElementsIterator.remove();
                     fail("No exception with no elements remaining!");
              } catch (IllegalStateException expected) {
              }
       }
}

只要我们实现了makeNoMoreElementsIterator()方法,我们就可以将所有的测试移入IteratorTest类。我们只需要将这个方法封装到ListIteratorTest类中:

package junit.cookbook.test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import temp.IteratorTest;

public class ListIteratorTest extends IteratorTest {
       protected Iterator makeNoMoreElementsIterator() {
              List empty = new ArrayList();
              return empty.iterator();
       }
}

ListIteratorTest继承我们的抽象类IteratorTest。
我们的ListIteratorTest类中实现的创建方法返回一个iterator而不是一个空列表。类似地,如果以测试一个基于Set类的iterator,你应该创建一个继承IteratorTest的SetIteratorTest类,这个类的makeNoMorelementsterator()方法也应该返回相应的iterator而不是一个空的Set对象。
这个抽象的test case能正常工作的原因是Junit中的测试等级规定。一个TestCase类在继承其父类时将同时继承父类所有的测试。在例子中,ListIteratorTest继承IteratorTest,所以只要在test runner中运行ListIteratorTest,IteratorTest中的测试都将得到运行。
值得一提的是Eric Armstrong的观点,他是Yahoo!的JUnit社区常客:“一个接口只定义语法,而不指定语义,虽然他们经常被实现。另一方面,一个相关的test Suite可以指定语义。我们应该给每一个公用的接口配备一个test suite!”当我们在框架中发布一个接口或者抽象类的时候,最好同时写一个相关的抽象的Test Case,以验证框架在所有客户端中的最重要的期望值。
最后,你会有同时实现了多个接口的类。建议你独立地测试每个接口的功能,而不要拘泥于那个“一个test case类测试一个类”的死板规定。


Junit实现抽象类测试(一)

OOP的一个很好的机制是使用抽象类,抽象类是不能被实例化的,只能提供给派生类一个接口。设计人员通常使用抽象类来强迫实现人员从基类派生,这样可以确保新的类包含一些期待的功能。
在Junit对抽象类的测试中再次引入工厂设计模式,其测试思想是:抽象类不能被实例化,所以使用具体类测试抽象类是不可以的。因此,构造抽象类的测试类必须也是抽象的。该类需要强制声明两种类型的抽象方法。第一类抽象方法即工厂方法,返回所有被测试抽象类的具体子类实例,第二类定义抽象方法返回所有被测试抽象类的具体子类行为期望值。如下面代码,Commodity类是一个商品抽象类,该类的抽象方法描述具体如下:

getCommodityName():取商品名称
changerName():修改商品名称
getCommodityPrice():取商品价格
changerPrice():修改商品价格
Commodity类具体代码如下
package com.fastpiont;

public abstract class Commodity {
       public abstract String getCommodityName();// 取得商品名称

       public abstract void changerName(String newName);// 修改商品名称

       public abstract double getCommodityPrice();// 取得商品价格

       public abstract void changerPrice(double newPrice);// 修改商品价格
}

CommodityTestCase是Commodity抽象类的测试类,同样该类被声明为抽象的。Commodity抽象类包含了getCommodity()工厂方法返回具体类实例,因为这才是真正的被测试对象。PrepareAndGetExpectedName()、PrepareAndGetExpectedPrice()、PrepareAndChangerExpectedName()、PrepareAndChangerExpectedPrice()四组方法分别返回具体类实例行为的期望值,即该实例行为的判断基准,具体代码如下:

package com.fastpiont;

import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public abstract class CommodityTest {
       Commodity comm;

       public abstract Commodity getCommodity();// 工厂方法,返回具体商品类

       public abstract String prepareAndGetExpectedName();// 返回期望值

       public abstract double prepareAndGetExpectedPrice();

       public abstract String repareAndChangerExpectedName();// 返回更改期望值

       public abstract double prepareAndChangerExpectedPrice();

       @Before
       public void setUp() throws Exception {
              comm = getCommodity();
       }

       @After
       public void tearDown() throws Exception {
       }

       @Test
       public void testGetCommodityName() {
              String expected = prepareAndGetExpectedName();
              String received = getCommodity().getCommodityName();
              assertEquals(expected, received);
       }

       @Test
       public void testChangerName() {
              comm.changerName(repareAndChangerExpectedName());
              assertEquals(repareAndChangerExpectedName(), comm.getCommodityName());
       }

       @Test
       public void testGetCommodityPrice() {
              double expected = prepareAndGetExpectedPrice();
              double received = getCommodity().getCommodityPrice();
              assertEquals(expected, received);
       }

       @Test
       public void testChangerPrice() {
              comm.changerPrice(prepareAndChangerExpectedPrice());
              assertEquals(prepareAndChangerExpectedPrice(), comm.getCommodityPrice());

       }
}

现在根据抽象类测试思想构造一个集成了Commodity抽象类的具体商品子类Commodity_Book,Commodity_Book类的构造方法有两个传参,即根据传入的书籍名称值和书籍单价值生成一本书实例。Commodity_Book类不鼓励直接修改书籍的属性,但是可以通过连歌公共方法changerName()和changerPrice()来实现,具体代码如下:

package com.fastpiont;
public class Commodity_Book extends Commodity {
       private String book_name;
       private double book_price;

       public Commodity_Book(String bookname, double bookprice) {
              book_name = bookname;
              book_price = bookprice;
       }

       public void changerName(String newName) {
              setBook_name(newName);
       }

       public void changerPrice(double newPrice) {
              setBook_price(newPrice);
       }

       public String getBook_name() {
              return book_name;
       }

       private void setBook_name(String book_name) {
              this.book_name = book_name;
       }

       public double getBook_price() {
              return book_price;
       }

       private void setBook_price(double book_price) {
              this.book_price = book_price;
       }

       @Override
       public String getCommodityName() {
              // TODO Auto-generated method stub
              return getBook_name();
       }

       @Override
       public double getCommodityPrice() {
              // TODO Auto-generated method stub
              return getBook_price();
       }
}

Commodity_BookTestCase类继承了CommodityTestCase抽象类,整个类显得非常简单,包括工厂方法返回一个具体的Commodity实例和四个针对该实例的期望值设定.
这种针对抽象类的测试方法是Junit推导者所主张的,好处在于该抽象类的所有具体子类都不用在测试抽象类中的所有抽象发发(抽象类中的具体方法除外,因为该方法可能被具体子类覆盖),符合XP测试的接口测试定义。


Junit实现抽象类测试(二)

如果抽象类中包含了具体实现的方法,那么使用抽象类测试(一)中的抽象类测试方式就很勉强了,因为抽象类的具体方法有可能被继承该抽象类的具体子类所覆盖,导致测试偏差现象发生。对于这样的测试场景,可以引入静态内部类进行抽象类变相实例化测试,这里引入Commodity抽象类实现这种设计,该类属性,抽象方法和实例方法的具体描述如下:

  1. commodity_name属于Commodity类实例私有属性,用于记录Commodity类实例的name值
  2. commodity_price属于Commodity类实例私有属性,用于记录Commodity类实例的price值
  3. getCommodity()工厂方法,返回一个Commodity实例
  4. changerName()改变Commodity实例名称
  5. changerPrice()改变Commodity实例价格
  6. getCommodityName()取得Commodity实例名称
  7. getCommodityPrice()取得Commodity实例价格
  8. setCommodityName()设置Commodity实例名称
  9. setCommodityPrice()设置Commodity实例价格

    Commodity抽象类具体代码如下:

package com.fastpoint;

public abstract class Commodity {
       private String Commodity_name;
       private double commodity_price;

       public abstract Commodity getCommodity();// 返回一个Commodity实例

       public void changerName(String newName) {
              setCommodity_name(newName);
       }

       public void changerPrice(double newPrice) {
              setCommodity_price(newPrice);
       }

       public String getCommodity_name() {
              return Commodity_name;
       }

       protected void setCommodity_name(String commodity_name) {
              Commodity_name = commodity_name;
       }

       public double getCommodity_price() {
              return commodity_price;
       }

       protected void setCommodity_price(double commodity_price) {
              this.commodity_price = commodity_price;
       }

}

Commodity类是一个抽象的商品类,本身包含了工厂方法getCommodity()来返回任何一个继承该类的具体子类实例,其余6个方法映射为该抽象商品类的具体业务操作。使用Junit对Commodity类进行测试需要得到类的实例,但是该类属于抽象类无法实例化,这样就需要引入一个具体子类来继承Commodity类,然后使用Junit对他进行测试,CommodityTestCase测试驱动类具体代码入下:

package com.fastpoint;

import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class CommodityTest {
       Commodity comm;

       static class Inner_Commodity extends Commodity {
              public Commodity getCommodity() {
                     return new Inner_Commodity();
              }
       }

       @Before
       public void setUp() throws Exception {
              comm = new CommodityTest.Inner_Commodity().getCommodity();
       }

       @After
       public void tearDown() throws Exception {
       }

       @Test
       public void testChangerName() {
              comm.setCommodity_name("软件测试");
              assertEquals("软件测试", comm.getCommodity_name());
       }

       @Test
       public void testChangerPrice() {
              comm.setCommodity_price(30.5);
              assertEquals(30.5,comm.getCommodity_price(),0);
       }

       @Test
       public void testGetCommodity_name() {
              comm.changerName("面向对象测试");
              assertEquals("面向对象测试", comm.getCommodity_name());
       }

       @Test
       public void testGetCommodity_price() {
              comm.changerPrice(20.5);
              assertEquals(20.5,comm.getCommodity_price(),0);
       }
}

CommodityTestCase类内部声明了一个静态类Inner_Commodity,该类继承了Commodity类来实现其类具体行为,CommodityTestCase类也是Inner_Commodity类的外围类。这样做的好处在于,每一个类都有对应的TestCase类,测试类层次清晰便于管理和回归。


JUnit使用abstract类来进行测试

JUnit是一个非常强大的工具, 可以帮助我们进行单元测试.
由于JUnit本身设计的非常好, 所以可以进行任意的扩展, 有很多使用技巧, 这次, 笔者和大家讨论一下使用抽象类来进行JUnit测试. 很简单, 但是很实用.
测试环境: eclipse indigo, JUnit 4
编写一个抽象类, 这个例子来自[参考1].

package junit.abstracttest;

import static org.junit.Assert.*;

import java.util.List;

import org.junit.Before;
import org.junit.Test;

public abstract class ListAbstractTest {

    private List<String> list;

    @Before
    public void whenTestingListOfString() {
        list = createList();
    }

    protected abstract List<String> createList();

    @Test
    public void shouldAddItemsToList() {
        list.add("one");
        assertEquals(1, list.size());
        assertEquals("one", list.get(0));
    }

}

里面有一个抽象方法, 用来提供一个list, 这个方法必须在子类中实现

package junit.abstracttest;

import java.util.ArrayList;
import java.util.List;

public class ListAbstractTestImpl extends ListAbstractTest {

    @Override
    protected List<String> createList() {
        return new ArrayList<String>();
    }

}

我们简单地实现了这个类, 然后可以在ListAbstractTestImpl中运行JUnit, 你会发现, 测试可以通过这样的方式, 我们可以将测试的代码写得更加简洁, 去掉大量重复的代码和逻辑.


参考:

Contract Tests in JUnit 4,
http://www.benrady.com/2009/12/contract-tests-in-junit-4.html
作者:icejoywoo 出处:http://www.cnblogs.com/icejoywoo/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
专注于自动化、性能研究,博客为原创,转载请注明文章来源于:http://www.cnblogs.com/Automation_software/

posted @ 2018-03-22 20:49  清凌  阅读(1613)  评论(0编辑  收藏  举报