Java单元测试

  单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

  可以说,单元测试(模块测试)是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。可见,单元测试是由程序员自己来完成,最终受益的也是程序员自己。可以这么说,程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。

  对于程序员来说,如果养成了对自己写的代码进行单元测试的习惯,不但可以写出高质量的代码,而且还能提高编程水平。

  接下来主要介绍Junit3,Junit4单元测试方案,以及mock测试框架,Spring单元测试框架。我们先来看Junit3单元测试方案,代码如下:

package com.itszt.domain;

import java.util.Date;

/**
 * 单元测试用的实体类
 */
public class Order {
    private int orderId;//订单order的id
    private Date orderTime;//下订单时间

    public Order(int orderId, Date orderTime) {
        this.orderId = orderId;
        this.orderTime = orderTime;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderId=" + orderId +
                ", orderTime=" + orderTime +
                '}';
    }

    public Order() {
    }

    public int getOrderId() {
        return orderId;
    }

    public void setOrderId(int orderId) {
        this.orderId = orderId;
    }

    public Date getOrderTime() {
        return orderTime;
    }

    public void setOrderTime(Date orderTime) {
        this.orderTime = orderTime;
    }
}
-------------------------------------------------------------
package com.itszt.dao;

import com.itszt.domain.Order;

import java.util.*;

/**
 * 测试单元,操作订单的类
 */
public class OrderDao {
    private static int moneyNum=100;
    private static Map<String,List<Order>> allOrders=new HashMap<>();
    private static int id=1;
    static {
        List<Order> orders=new ArrayList<>();
        orders.add(new Order(id++,new Date()));
        orders.add(new Order(id++,new Date()));
        orders.add(new Order(id++,new Date()));
        allOrders.put("张三",orders);//属于张三的订单
    }
    public int queryOrderCount(String username){
        //用户所下订单的数量
        return allOrders.get(username).size();
    }
    public List<Order> queryOrders(String username){
        //用户下过的订单
        return allOrders.get(username);
    }
    public int queryMoney(){
        return moneyNum;
    }
    public void addMoney(int num){
        moneyNum=moneyNum+num;
    }
    public void dropMoney(int num){
        moneyNum=moneyNum-num;
    }
}
------------------------------------------------------
package test1;

import com.itszt.dao.OrderDao;
import com.itszt.domain.Order;
import junit.framework.TestCase;

import java.util.List;

/**
 * Junit3,创建单元测试用例
 * 注:Junit4用注解完成
 */
public class TestOrderDao extends TestCase{
   private OrderDao orderDao;
   private static int num=0;

   @Override
    public void setUp() throws Exception {
       System.out.println((++num)+"--------========================");
//       setUp:在每个测试方法前都会执行,做通用初始化
//        super.setUp();
       orderDao=new OrderDao();
       System.out.println("test1.TestOrderDao.setUp");
    }

    @Override
    public void tearDown() throws Exception {
//        super.tearDown();
        System.out.println("test1.TestOrderDao.tearDown");
    }

    public void testQueryMoney(){
        System.out.println("test1.TestOrderDao.testQueryMoney");
        int queryMoney = orderDao.queryMoney();
        System.out.println("queryMoney = " + queryMoney);
        //传入一个期望值,再传入一个真实值,看两者是否相等
        assertEquals(100,queryMoney);
    }

    public void testAddMoney(){
        System.out.println("test1.TestOrderDao.testAddMoney");
        orderDao.addMoney(100);
        int queryMoney = orderDao.queryMoney();
        System.out.println("queryMoney = " + queryMoney);
        assertEquals(200,queryMoney);
    }

    public void testDropMoney(){
        System.out.println("TestOrderDao.testDropMoney");
        orderDao.dropMoney(35);
        int queryMoney = orderDao.queryMoney();
        System.out.println("queryMoney = " + queryMoney);
        assertEquals(165,queryMoney);
    }

    public void testQueryOrderCount(){
        System.out.println("TestOrderDao.testQueryOrderCount");
        int orderCount = orderDao.queryOrderCount("张三");
        System.out.println("orderCount = " + orderCount);
        assertEquals(3,orderCount);
    }

    public void testQueryOrders(){
        System.out.println("TestOrderDao.testQueryOrders");
        List<Order> orderList = orderDao.queryOrders("张三");
        System.out.println("orderList = " + orderList);
        assertEquals(orderList,orderList);
    }
}
-------------------------------------------------------
package test1;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 *单元测试套件,运行测试用例
 */
public class TestAll extends TestCase{

    public static Test suite(){
        TestSuite testSuite=new TestSuite();
        testSuite.addTestSuite(TestOrderDao.class);
        return testSuite;
    }
}

  上述代码执行结果如下:

1--------========================
test1.TestOrderDao.setUp
TestOrderDao.testQueryOrders
orderList = [Order{orderId=1, orderTime=Wed Mar 21 14:43:51 CST 2018}, Order{orderId=2, orderTime=Wed Mar 21 14:43:51 CST 2018}, Order{orderId=3, orderTime=Wed Mar 21 14:43:51 CST 2018}]
test1.TestOrderDao.tearDown
2--------========================
test1.TestOrderDao.setUp
test1.TestOrderDao.testQueryMoney
queryMoney = 100
test1.TestOrderDao.tearDown
3--------========================
test1.TestOrderDao.setUp
test1.TestOrderDao.testAddMoney
queryMoney = 200
test1.TestOrderDao.tearDown
4--------========================
test1.TestOrderDao.setUp
TestOrderDao.testDropMoney
queryMoney = 165
test1.TestOrderDao.tearDown
5--------========================
test1.TestOrderDao.setUp
TestOrderDao.testQueryOrderCount
orderCount = 3
test1.TestOrderDao.tearDown

  总的来说,在使用Junit3单元测试时,步骤如下:

  先导入相关jar包;

  新建单元测试用例,如:public class TestUserDao extends TestCase;

  其中,setUp:在每个测试方法前都会执行,做通用初始化;

      tearDown:在每个测试方法后都会执行,做通用资源释放。

  写测试方案:其实就是一堆方法,这些方法通常以test开头即可;

  怎么判断功能是否正常:基于Assert 断言完成;

  测试结果通常是3种: 1.成功 2.失败 3.异常报错

  单元测试套件:一次执行多个测试类

  上面用的是Junit3测试方案,Junit4与Junit3不同的是,采用了注解方式,从而使得测试代码更为简洁。我们接下来看Junit4测试方案:

package com.itszt.dao;

/**
 * 测试单元
 */
public class UserDao {
    private static int moneyNum=100;
    public int queryMoney(){
        System.out.println(10/0);//模拟出现异常
        return moneyNum;
    }

    public void addMoney(int num){
        moneyNum+=num;
    }

    public void dropMoney(int num){
        moneyNum-=num;
    }
}
----------------------------------------------------------
package test2;

import com.itszt.dao.UserDao;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

/**
 * 测试用例,通过注解声明
 */

public class TestUserDao {
    private UserDao userDao;
    @Before
    public void init(){
        userDao=new UserDao();
        System.out.println("TestUserDao.init");
    }
    @After
    public void tearDown(){
        System.out.println("TestUserDao.tearDown");
    }

    @Test
    public void testQueryMoney(){
        System.out.println("TestUserDao.testQueryMoney "+userDao.hashCode());
        int money = userDao.queryMoney();
        Assert.assertEquals(100,money);
    }
    @Test
    public void testAddMoney(){
        System.out.println("TestUserDao.testAddMoney "+userDao.hashCode());
        userDao.addMoney(300);
        int money = userDao.queryMoney();
        Assert.assertEquals(400,money);
    }
}
----------------------------------------------------
package test2;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

/**
 * 测试套件
 * 测试用例多于一个时,中间以英文逗号分割
 */
@Suite.SuiteClasses({TestUserDao.class})
@RunWith(Suite.class)
public class TestAll {

}

  上述代码执行结果如下:

TestUserDao.init
TestUserDao.testQueryMoney 25282035
TestUserDao.tearDown

java.lang.ArithmeticException: / by zero

	at com.itszt.dao.UserDao.queryMoney(UserDao.java:9)
	at test2.TestUserDao.testQueryMoney(TestUserDao.java:28)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runners.Suite.runChild(Suite.java:128)
	at org.junit.runners.Suite.runChild(Suite.java:27)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

TestUserDao.init
TestUserDao.testAddMoney 4959864
TestUserDao.tearDown

java.lang.ArithmeticException: / by zero

	at com.itszt.dao.UserDao.queryMoney(UserDao.java:9)
	at test2.TestUserDao.testAddMoney(TestUserDao.java:35)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runners.Suite.runChild(Suite.java:128)
	at org.junit.runners.Suite.runChild(Suite.java:27)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

  可见,代码出现异常后,单元测试随之会给出相应的异常提示。

  我们接下来用mock方法执行单元测试。首先,我们要在项目中添加easymock-3.4.jar包的依赖。测试代码如下:

package com.itszt.domain;

/**
 * 测试用的实体类
 */
public class Org {
    private String orgName,orgLoaction,orgType;

    public Org(String orgName, String orgLoaction, String orgType) {
        this.orgName = orgName;
        this.orgLoaction = orgLoaction;
        this.orgType = orgType;
    }

    public Org() {
    }

    public String getOrgName() {
        return orgName;
    }

    public void setOrgName(String orgName) {
        this.orgName = orgName;
    }

    public String getOrgLoaction() {
        return orgLoaction;
    }

    public void setOrgLoaction(String orgLoaction) {
        this.orgLoaction = orgLoaction;
    }

    public String getOrgType() {
        return orgType;
    }

    public void setOrgType(String orgType) {
        this.orgType = orgType;
    }

    @Override
    public String toString() {
        return "Org{" +
                "orgName='" + orgName + '\'' +
                ", orgLoaction='" + orgLoaction + '\'' +
                ", orgType='" + orgType + '\'' +
                '}';
    }
}
----------------------------------------------------
package com.itszt.dao;

import com.itszt.domain.Org;

/**
 * 操作Org对象的接口
 */
public interface OrgDao {
    //1.查重
    public Org queryOrgByName(String orgName);
}
--------------------------------------------------------
package com.itszt.service;

/**
 * 操作Org业务的接口
 */
public interface OrgService {
    public boolean regOrg(String orgName,String orgLoaction,String orgType);
}
----------------------------------------------------------
package com.itszt.service;

import com.itszt.dao.OrgDao;
import com.itszt.domain.Org;

/**
 * OrgService的实现类
 */
public class OrgServiceImpl implements OrgService {
    private OrgDao orgDao;

    public void setOrgDao(OrgDao orgDao) {
        this.orgDao = orgDao;
    }

    @Override
    public boolean regOrg(String orgName, String orgLoaction, String orgType) {
        Org org = orgDao.queryOrgByName(orgName);
        if (org == null) {
            System.out.println(orgName+"不存在,可以执行插入动作。");
        } else {
            System.out.println(orgName+"已存在,不能再插入");
            return false;
        }
        return true;
    }
}
-------------------------------------------------------
package test4;

import com.itszt.dao.OrgDao;
import com.itszt.domain.Org;
import com.itszt.service.OrgServiceImpl;
import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Test;

/**
 * 用mock方案实现单元测试
 */
public class TestOrgService {
    @Test
    public void testRegOrg(){
        OrgServiceImpl orgService=new OrgServiceImpl();
        //用mock模拟一个UserDao的实现类
        OrgDao orgDao = EasyMock.createMock(OrgDao.class);
        orgService.setOrgDao(orgDao);
        //待测试的一个实体类
        Org org = new Org("曹操公司", "许昌", "魏国");
        //当orgDao调用queryOrgByName方法,并且传入参数为"曹操公司"时,则返回org对象,该模拟对象orgDao可使用次数为10
        //传入期望值,实际结果为Org对象或null
        EasyMock.expect(orgDao.queryOrgByName("曹操公司")).andReturn(org).times(10);
        EasyMock.expect(orgDao.queryOrgByName("刘备公司")).andReturn(null).times(10);

        //让我们模拟的特性生效
        EasyMock.replay(orgDao);

        boolean boo1 = orgService.regOrg("曹操公司", "许昌", "魏国");
        Assert.assertFalse(boo1);

        boolean boo2 = orgService.regOrg("刘备公司", "成都", "蜀汉");
        Assert.assertTrue(boo2);
    }
}

   上述代码执行如下:

曹操公司已存在,不能再插入
刘备公司不存在,可以执行插入动作。

  我们接下来再写一个基于mock的单元测试方案:

package com.itszt.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * 测试单元
 */
public class UserController {

    public void showUser(HttpServletRequest request, HttpSession session) {
        String username = request.getParameter("username");
        System.out.println("简单使用一下---username = " + username);
        String userpwd = request.getAttribute("userpwd").toString();
        System.out.println("简单使用一下---userpwd = " + userpwd);
        String userNow = session.getAttribute("userNow").toString();
        System.out.println("简单使用一下---userNow = " + userNow);
    }
}
-------------------------------------------------
package test3;

import com.itszt.controller.UserController;
import org.easymock.EasyMock;
import org.junit.Test;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * mock单元测试方案
 */
public class TestUserController {
    @Test
    public void testShow(){
        UserController userController=new UserController();

        HttpServletRequest request = EasyMock.createMock(HttpServletRequest.class);
        HttpSession session = EasyMock.createMock(HttpSession.class);

        String username="小明";
        String userpwd="123456";
        String userNow=username;

        EasyMock.expect(request.getParameter("username")).andReturn(username).times(1);
        EasyMock.expect(request.getAttribute("userpwd")).andReturn(userpwd).once();
        EasyMock.expect(session.getAttribute("userNow")).andReturn("正在登录的是:"+userNow).once();

        EasyMock.replay(request);
        EasyMock.replay(session);

        userController.showUser(request,session);
    }
}

  上述代码执行结果如下:

简单使用一下---username = 小明
简单使用一下---userpwd = 123456
简单使用一下---userNow = 正在登录的是:小明

  最后,我们在使用Spring框架时,Spring也为我们提供了单元测试方案。在使用时,我们需要添加Spring框架中相应的jar包(spring-test-4.3.11.RELEASE.jar)依赖。

  由于我们在案例中使用了注解,所以在spring-config.xml配置文件中要添加注解支持,配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <!--添加注解支持-->
    <context:annotation-config></context:annotation-config>
    <context:component-scan base-package="test4"></context:component-scan>
</beans>

  接下来是基于Spring框架的单元测试代码:

package test4;

import org.springframework.stereotype.Component;

/**
 * 测试单元
 */
@Component
public class UserService {
    public int queryScore(){
        System.out.println("100就对了");
        return 100;
    }
}
---------------------------------------------------
package test4;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * 测试用例与套件
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:config/spring-config.xml")
public class TestUserService {
    @Autowired
    private UserService userService;

    @Before
    public void before(){
        System.out.println("运行前测试....");
    }

    @After
    public void after(){
        System.out.println("运行完成后测试...");
    }

    @Test
    public void testQueryScore(){
        System.out.println("TestUserService.testQueryScore");
        int queryScore = userService.queryScore();
        org.junit.Assert.assertEquals(100,100);
    }
}

  上述代码运行如下:

运行前测试....
TestUserService.testQueryScore
100就对了
运行完成后测试...
posted @ 2018-03-21 17:20  奔跑在梦想的道路上  阅读(350)  评论(0编辑  收藏  举报