zoukankan      html  css  js  c++  java
  • TDD入门demo

    OK,前面的博客整理了一系列的junit相关内容,这里举一个例子TDD实际的编码例子,不管实际编码中是否使用TDD,个人觉得这种思想必须要有。

    我们不一定在写业务代码之前一定要说是把测试类都写出来,至少脑子里面都应该试试考虑到自己写的一段编码的代码可测性。如果每段代码都可以测试,那么我们可以高枕无忧放心的编码了。

    那我们现在开始吧。这里举一个简单的业务层代码,对一个用户实现CRUD,然后该业务层面向接口编程。

    1,定义用户数据模型

    package org.linkinpark.maven.linkinMaven;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月17日
     * @功能描述: 定义一个Javabean
     */
    public class User
    {
    	private String userName;
    	private Integer age;
    
    	public User()
    	{
    	}
    
    	public User(String userName, Integer age)
    	{
    		this.userName = userName;
    		this.age = age;
    	}
    
    	public String getUserName()
    	{
    		return userName;
    	}
    
    	public User setUserName(String userName)
    	{
    		this.userName = userName;
    		return this;
    	}
    
    	public Integer getAge()
    	{
    		return age;
    	}
    
    	public User setAge(Integer age)
    	{
    		this.age = age;
    		return this;
    	}
    
    	@Override
    	public int hashCode()
    	{
    		final int prime = 31;
    		int result = 1;
    		result = prime * result + ((age == null) ? 0 : age.hashCode());
    		result = prime * result + ((userName == null) ? 0 : userName.hashCode());
    		return result;
    	}
    
    	@Override
    	public boolean equals(Object obj)
    	{
    		if (this == obj)
    			return true;
    		if (obj == null)
    			return false;
    		if (getClass() != obj.getClass())
    			return false;
    		User other = (User) obj;
    		if (age == null)
    		{
    			if (other.age != null)
    				return false;
    		}
    		else if (!age.equals(other.age))
    			return false;
    		if (userName == null)
    		{
    			if (other.userName != null)
    				return false;
    		}
    		else if (!userName.equals(other.userName))
    			return false;
    		return true;
    	}
    
    }
    
    关于这个用户对象,我们重写equals方法,如果用户名和用户年龄相同,那么equals就为true。这里我没有定义用户主键,我用用户名来做用户的唯一标示好了。

    2,开始写可能用到的接口

    package org.linkinpark.maven.linkinMaven;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月17日
     * @功能描述: 测试未动,接口先行
     */
    public interface Service
    {
    	// 增加一个用户
    	boolean addUser(User user);
    
    	// 修改一个用户
    	boolean modifyUser(User user);
    
    	// 删除一个用户
    	boolean deleteUser(String userName);
    
    	// 用户名查找一个用户
    	User getUserByUserName(String userName);
    	
    
    }
    


    3,开始写测试代码

    package org.linkinpark.maven.linkinMaven;
    
    import static org.junit.Assert.*;
    import org.junit.Test;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月17日
     * @功能描述: 实现类未动,测试先行
     */
    public class ServiceImplTest
    {
    
    	@Test
    	public void testAddUser()
    	{
    		fail("添加用户尚未实现。。。");
    	}
    
    	@Test
    	public void testModifyUser()
    	{
    		fail("修改用户尚未实现。。。");
    	}
    
    	@Test
    	public void testDeleteUser()
    	{
    		fail("删除用户尚未实现。。。");
    	}
    
    	@Test
    	public void testGetUserByUserName()
    	{
    		fail("用户名查找用户尚未实现。。。");
    	}
    
    }
    

    关于上面的测试代码,值得注意的是,我们在写业务代码之前,先编写了测试代码,一定要在测试的方法中fail("not yet implement")一个错误,这样子表示我们相关的业务代码还没有写呢,不然测试直接通过了。


    4,开始编写接口实现

    package org.linkinpark.maven.linkinMaven;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月17日
     * @功能描述: 用户业务层实现类
     */
    public class ServiceImpl implements Service
    {
    	// 用一Map暂时封装用户表
    	private Map<String, User> users = new HashMap<>();
    
    	@Override
    	public boolean addUser(User user)
    	{
    		try
    		{
    			users.put(user.getUserName(), user);
    			return true;
    		}
    		catch (Exception e)
    		{
    			return false;
    		}
    	}
    
    	@Override
    	public boolean modifyUser(User user)
    	{
    		try
    		{
    			users.put(user.getUserName(), user);
    			return true;
    		}
    		catch (Exception e)
    		{
    			return false;
    		}
    	}
    
    	@Override
    	public boolean deleteUser(String userName)
    	{
    		try
    		{
    			users.remove(userName);
    			return true;
    		}
    		catch (Exception e)
    		{
    			return false;
    		}
    
    	}
    
    	@Override
    	public User getUserByUserName(String userName)
    	{
    		return users.get(userName);
    	}
    
    }
    

    5,编写正式的测试代码

    package org.linkinpark.maven.linkinMaven;
    
    import static org.junit.Assert.*;
    
    import org.junit.Before;
    import org.junit.Test;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月17日
     * @功能描述: 实现类未动,测试先行
     */
    public class ServiceImplTest
    {
    	private Service service;
    
    	@Before
    	public void setUp()
    	{
    		service = new ServiceImpl();
    	}
    
    	@Test
    	public void testAddUser()
    	{
    		User user = new User("关羽", 25);
    		service.addUser(user);
    		User userA = service.getUserByUserName("关羽");
    		assertEquals82User(userA, user);
    	}
    
    	@Test
    	public void testModifyUser()
    	{
    		User user = new User("关羽", 25);
    		service.addUser(user);
    		User userA = new User("关羽", 18);
    		service.modifyUser(userA);
    		User userB = service.getUserByUserName("关羽");
    		assertEquals82User(userA, userB);
    	}
    
    	@Test
    	public void testDeleteUser()
    	{
    		User user = new User("关羽", 25);
    		service.addUser(user);
    		service.deleteUser("关羽");
    		User userA = service.getUserByUserName("关羽");
    		assertNull(userA);
    	}
    
    	@Test
    	public void testGetUserByUserName()
    	{
    		User user = new User("关羽", 25);
    		service.addUser(user);
    		User userA = service.getUserByUserName("关羽");
    		assertEquals82User(userA, user);
    	}
    
    	private void assertEquals82User(User userA, User userB)
    	{
    		assertEquals(userA.getUserName(), userB.getUserName());
    		assertEquals(userA.getAge(), userB.getAge());
    	}
    
    }
    

    OK,没问题了,测试都通过了,但是真的没问题了吗?

    我前面已经说了,测试主要是测试异常的情况,所以我们好多的方法都没有测试校验,比如如果用户都存在了呢,怎么还能重复添加呢?用户都没存在,怎么能进行修改和删除操作呢?

    也可以说这么说我们业务代码写的一团糟,根本没做基本的校验等,因为我们的测试代码也都没写呢。好吧,再次编写测试代码然后来修改业务代码吧。

    6,在修改测试代码之前,我们先来自定义一个异常,userException。

    package org.linkinpark.maven.linkinMaven;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月17日
     * @功能描述: 自定义一个异常,业务层内容验证出错时抛出。
     */
    public class UserException extends RuntimeException
    {
    
    	public UserException()
    	{
    		super();
    	}
    
    	public UserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)
    	{
    		super(message, cause, enableSuppression, writableStackTrace);
    	}
    
    	public UserException(String message, Throwable cause)
    	{
    		super(message, cause);
    	}
    
    	public UserException(String message)
    	{
    		super(message);
    	}
    
    	public UserException(Throwable cause)
    	{
    		super(cause);
    	}
    
    }
    

    7,开始修改测试代码,开始修改自己的业务代码,重构自己的测试代码和业务代码,减少代码重复。最后确保自己的测试通过,OK,下面是一份最终的测试代码和业务实现类代码。看的出来,修改过后的测试代码和业务代码都增加了校验,然后这些校验也都被封装到了某个方法中,为了使得子类可以重写这些校验规则,我这里使用protected修饰符,如果不想别人修改这部分东西的话,那么就用private修饰符就OK,不过测试的时候要跑下反射,这个稍微有点折腾。

    package org.linkinpark.maven.linkinMaven;
    
    import static org.junit.Assert.*;
    
    import org.junit.Before;
    import org.junit.Test;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月17日
     * @功能描述: 实现类未动,测试先行
     */
    public class ServiceImplTest
    {
    	private ServiceImpl service;
    
    	@Before
    	public void setUp()
    	{
    		service = new ServiceImpl();
    	}
    
    	@Test
    	public void testAddUser()
    	{
    		User user = new User("关羽", 25);
    		service.addUser(user);
    		User userA = service.getUserByUserName("关羽");
    		assertEquals82User(userA, user);
    	}
    
    	@Test(expected = UserException.class)
    	public void testAddUser8HasUser()
    	{
    		User user = new User("关羽", 25);
    		service.addUser(user);
    		User userA = new User("关羽", 25);
    		service.addUser(userA);
    	}
    
    	@Test
    	public void testModifyUser()
    	{
    		User user = new User("关羽", 25);
    		service.addUser(user);
    		User userA = new User("关羽", 18);
    		service.modifyUser(userA);
    		User userB = service.getUserByUserName("关羽");
    		assertEquals82User(userA, userB);
    	}
    
    	@Test(expected = UserException.class)
    	public void testModifyUser8HasNotUser()
    	{
    		User userA = new User("关羽", 18);
    		service.modifyUser(userA);
    	}
    
    	@Test
    	public void testDeleteUser()
    	{
    		User user = new User("关羽", 25);
    		service.addUser(user);
    		service.deleteUser("关羽");
    		User userA = service.getUserByUserName("关羽");
    		assertNull(userA);
    	}
    
    	@Test(expected = UserException.class)
    	public void testDeleteUser8HasNotUser()
    	{
    		service.deleteUser("关羽");
    	}
    
    	@Test
    	public void testGetUserByUserName()
    	{
    		User user = new User("关羽", 25);
    		service.addUser(user);
    		User userA = service.getUserByUserName("关羽");
    		assertEquals82User(userA, user);
    	}
    
    	@Test(expected = UserException.class)
    	public void testExitsUser8UserName2Null()
    	{
    		service.exitsUser8UserName("");
    	}
    
    	@Test
    	public void testExitsUser8UserName2True()
    	{
    		User user = new User("关羽", 25);
    		service.addUser(user);
    		boolean exitsUser8UserName = service.exitsUser8UserName("关羽");
    		assertTrue(exitsUser8UserName);
    	}
    
    	@Test
    	public void testExitsUser8UserName2Flase()
    	{
    		boolean exitsUser8UserName = service.exitsUser8UserName("关羽");
    		assertFalse(exitsUser8UserName);
    	}
    
    	private void assertEquals82User(User userA, User userB)
    	{
    		assertEquals(userA.getUserName(), userB.getUserName());
    		assertEquals(userA.getAge(), userB.getAge());
    	}
    
    }
    

    package org.linkinpark.maven.linkinMaven;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月17日
     * @功能描述: 用户业务层实现类
     */
    public class ServiceImpl implements Service
    {
    	// 用一Map暂时封装用户表
    	private Map<String, User> users = new HashMap<>();
    
    	@Override
    	public boolean addUser(User user)
    	{
    		if (exitsUser8UserName(user.getUserName()))
    		{
    			throw new UserException("用户已经存在,请勿重复添加");
    		}
    		try
    		{
    			users.put(user.getUserName(), user);
    			return true;
    		}
    		catch (Exception e)
    		{
    			return false;
    		}
    	}
    
    	@Override
    	public boolean modifyUser(User user)
    	{
    		if (noneUser8UserName(user.getUserName()))
    		{
    			throw new UserException("用户尚未存在,不可操作修改");
    		}
    		try
    		{
    			users.put(user.getUserName(), user);
    			return true;
    		}
    		catch (Exception e)
    		{
    			return false;
    		}
    	}
    
    	@Override
    	public boolean deleteUser(String userName)
    	{
    		if (noneUser8UserName(userName))
    		{
    			throw new UserException("用户尚不存在,不可操作删除");
    		}
    		try
    		{
    			users.remove(userName);
    			return true;
    		}
    		catch (Exception e)
    		{
    			return false;
    		}
    
    	}
    
    	@Override
    	public User getUserByUserName(String userName)
    	{
    		return users.get(userName);
    	}
    
    	protected boolean exitsUser8UserName(String userName)
    	{
    		if (userName == null || "".equals(userName.trim()))
    		{
    			throw new UserException("用户名为空,不可校验用户是否存在");
    		}
    		if (users.get(userName) != null)
    		{
    			return true;
    		}
    		return false;
    	}
    
    	protected boolean noneUser8UserName(String userName)
    	{
    		return !exitsUser8UserName(userName);
    	}
    
    }
    

    总结:

    TDD,测试驱动开发,特别要注意的是这里的测试先行,测试先行又要特别注意异常的情况,也就是出错的情况。junit的核心思想说的很清楚了,测试不是证明你是对的,只是证明你的代码没有错。在不停的修改测试代码和业务代码的时候,我们的代码也被封装到了一个个的方法中,这样子就能基本上实现了高内聚和低耦合,因为我们在不停的重构,方法定义越来越精短,方法含义也越来越单一,看过开源框架的源码我们就会发现,开源代码的方法体都很少很小,基本都是被封装到了很多的方法中去了,这点也是最值得我们学习的,也是我花这么长时间来写这篇博客的原因。

  • 相关阅读:
    diary and html 文本颜色编辑,行距和其它编辑总汇
    bash coding to changeNames
    virtualbox ubuntu 网络连接 以及 连接 secureCRT
    linux 学习6 软件包安装
    linux 学习8 权限管理
    vim 使用2 转载 为了打开方便
    ubuntu
    linux 学习15 16 启动管理,备份和恢复
    linux 学习 14 日志管理
    linux 学习 13 系统管理
  • 原文地址:https://www.cnblogs.com/LinkinPark/p/5232861.html
Copyright © 2011-2022 走看看