测试驱动开发的实践经验

测试驱动开发的意义

传统开发方法

传统的开发方法的流程是:

需求分析 -> 设计 -> 编写代码 ->测试  ->回归测试 -> 修改BUG->回顾测试....... ->上线 ->修改BUG。

从整个流程可以看出来传统开发方法的开发存在这些问题:

1.不能满足客户需求。

需求分析阶段缺乏测试和验证,导致产品需求不正确。

开发阶段开发出来的东西与需求不符,导致返工。

......

2.糟糕的代码质量。

在缺陷中苦苦挣扎,代码维护困难,开发进度缓慢...........

3.开发周期长。

由于项目流程的前面质量不过关,后面必须用十倍百倍的工作量来弥补。不断的返工,不断的修改、回归测试将我们的项目开发周期拉长了。

4.上线后的质量问题降低了用户的信任。

软件上线后,用户在使用过程中遇到的众多BUG让用户很郁闷。。。。。。。。用户对软件的信任度大大降低。

测试驱动开发方法

我们使用了测试驱动开发之后,整个软件开发的流程编程了这样:


需求分析 -> 设计 ->设计测试用例 ->编写测试用例 -> 编写代码 ->执行测试 ->重构代码->执行测试 ->测试 -> 修改BUG->上线

测试驱动的好处

1.不用长时间调试代码。

2.对自己写的代码更有信心。

3.回归测试更快了。

4.单元测试代码是最好的文档。

5.代码质量更高了。

在java web项目中的实践

本案例中是以我们自己的实际开发框架为例实现的单元测试。我们的开发框架是集成了Spring + SpringMVC + MyBatis框架而成的。所以本实例的单元测试代码是基于这些框架实现的。

测试用例

本案例以一个登录的用例为实例,这是经典的案例了,相当于“hello,world”了。

访问路径是:/user/login.json

案例1    

输入空的用户名和密码

参数

username:""

password:""

返回值    json格式的数据

{

    "result":0,

    "error":"用户名和密码都不允许空"

}        



案例2

输入错误的用户名和密码

参数

username:"user"

password:"error"


返回值    json格式的数据

{

    "result":0,

    "error":"用户名和密码错误"

}        



案例3

输入正确的用户名和密码

参数

username:"user"

password:"password"

返回值    json格式的数据

{

    "result":1

}        


当然一个完整的登录功能,测试用例远不止这些,我们这里只选择三个测试用例来演示一下。


Action单元测试

1.编写Action层的单元测试

package cn.bidlink.yuecai.plan.action;

import java.util.Map;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class UserActionTest {
	
	private UserAction userAction;
	
	@Before
	public void setup(){
		userAction = new UserAction();
	}
	
	
	/**
	 * 测试登录失败的情况。
	 */
	@Test
	public void testLoginFaild(){
		Map<String, Object> result = userAction.login("", "");
		Assert.assertNotNull(result);
		Assert.assertEquals(Integer.valueOf(0), result.get("result"));
		Assert.assertEquals("用户名和密码都不允许空", result.get("error"));
		
		result = userAction.login("user", "error");
		Assert.assertNotNull(result);
		Assert.assertEquals(Integer.valueOf(0), result.get("result"));
		Assert.assertEquals("用户名和密码错误", result.get("error"));
	}
	
	/**
	 * 测试登录失败的情况。
	 */
	@Test
	public void testLoginSucess(){
		Map<String, Object> result = userAction.login("user", "password");
		Assert.assertNotNull(result);
		Assert.assertEquals(Integer.valueOf(1), result.get("result"));
	}

}

当我们代码还没有实现的时候,测试用例肯定会执行失败。

2.实现Action方法代码

我们为了让单元测试通过,我们要继续实现代码。编写的实现代码如下:


package cn.bidlink.yuecai.plan.action;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping(value = "/user")
public class UserAction {
	
	@RequestMapping(value = "login")
	@ResponseBody
	public Map<String, Object> login(String username, String password) {
		Map<String, Object> result = new HashMap<String, Object>();
		if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)){
			result.put("result", Integer.valueOf(0));
			result.put("error", "用户名和密码都不允许空");
			return result;
		}
		else if(username.equals("user")){
			if (password.equals("error")){
				result.put("result", Integer.valueOf(0));
				result.put("error", "用户名和密码错误");
				return result;
			}
			else if (password.equals("password")){
				result.put("result", Integer.valueOf(1));
				return result;
			}
		}
		
		return result;
	}

}



上述代码也不是一蹴而就,也是反复经过多次修改最终才让测试用例通过的。


单元测试结果是:

如果安装了覆盖率检查工具,还可以查看单元测试的覆盖率情况。


这样我们Action层的代码编写完毕。但是大家可以看出来,我们真实的实现肯定不是这样的,我们的用户密码校验肯定是要与服务端存储的数据进行验证的,接下来还要调用Service和dao层的代码实现最终的业务逻辑。

Service单元测试

1.修改Action层代码

修改UserAction的实现方式,改为代用Servcie方法实现登录。


package cn.bidlink.yuecai.plan.action;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import cn.bidlink.yuecai.plan.service.UserService;

@Controller
@RequestMapping(value = "/user")
public class UserAction {
	
	@Autowired
	private UserService userService;
	
	public void setUserService(UserService userService) {
		this.userService = userService;
	}

	@RequestMapping(value = "login")
	@ResponseBody
	public Map<String, Object> login(String username, String password) {
		Map<String, Object> result = new HashMap<String, Object>();
		if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)){
			result.put("result", Integer.valueOf(0));
			result.put("error", "用户名和密码都不允许空");
			return result;
		}
		
		if(userService.login(username, password)){
			result.put("result", Integer.valueOf(1));
			return result;
		}
		else{
			result.put("result", Integer.valueOf(0));
			result.put("error", "用户名和密码错误");
			return result;
		}
	}

}


2 .修改Action层单元测试代码


这个时候执行单元测试发现报空指针异常了,需要调整单元测试。

package cn.bidlink.yuecai.plan.action;

import java.util.Map;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;

import cn.bidlink.framework.test.action.AbstractActionTests;
import cn.bidlink.yuecai.plan.service.UserService;
public class UserActionTest extends AbstractActionTests{
	
	@InjectMocks
	private static UserAction userAction;
	
	@Mock
	private UserService userService;
	
	@BeforeClass
	public static void setup(){
		userAction = new UserAction();
	}
	
	
	/**
	 * 测试登录失败的情况。
	 */
	@Test
	public void testLoginFaild(){
		Map<String, Object> result = userAction.login("", "");
		Assert.assertNotNull(result);
		Assert.assertEquals(Integer.valueOf(0), result.get("result"));
		Assert.assertEquals("用户名和密码都不允许空", result.get("error"));
		
		
		/**
		 * 设置预期结果,返回false。
		 */
		String username = "user";
		String password = "error";
		Mockito.when(userService.login(username, password)).thenReturn(false);
		result = userAction.login(username, password);
		Assert.assertNotNull(result);
		Assert.assertEquals(Integer.valueOf(0), result.get("result"));
		Assert.assertEquals("用户名和密码错误", result.get("error"));
	}
	
	/**
	 * 测试登录失败的情况。
	 */
	@Test
	public void testLoginSucess(){
		/**
		 * 设置预期结果,返回false。
		 */
		String username = "user";
		String password = "password";
		Mockito.when(userService.login(username, password)).thenReturn(true);
		Map<String, Object> result = userAction.login(username, password);
		Assert.assertNotNull(result);
		Assert.assertEquals(Integer.valueOf(1), result.get("result"));
	}

}

这个时候在测试的时候为了避免service的实现影响Action代码的测试,则需要使用模拟对象替换Service方法。

调整为这样的代码之后则执行通过了。

3 .编写Service单元测试

service层单元测试的方法与Action层类似,若此层代码中出现了对Dao或者其它远程服务的调用,则测试的时候也应该用模拟对象去模拟结果。


转载于:https://my.oschina.net/ywbrj042/blog/265493

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值