JUnit介绍(转)
测试的重要性毋庸再说,但如何使测试更加准确和全面,并且独立于项目之外并且避免硬编码,JUnit给了我们一个很好的解决方案。
一、引子
首先假设有一个项目类SimpleObject如下:
public class SimpleObject{
public List methodA(){
.....
}
}
其中定义了一个methodA方法返回一个对象,好,现在我们要对这个方法进行测试,看他是不是返回一个List对象,是不是为空,或者长度是不是符合标准等等。我们写这样一个方法判断返回对象是否不为Null:
public void assertNotNull(Object object){
//判断object是否不为null
....
}
这个方法在JUnit框架中称之为一个断言,JUnit提供给我们了很多断言,比如assertEqual,assertTrue...,我们可以利用这些断言来判断两个值是否相等或者二元条件是否为真等问题。
接下来我们写一个测试类
import junit.framework.*;
public class TestSimpleObject extends TestCase{
public TestSimpleObject(String name){
super(name);
}
public void testSimple(){
SimpleObject so=new SimpleObject();
assertNotNull(so.methodA());
}
}
然后我们可以运行JUnit来检测我们的测试结果,这样我们在不影响Project文件的前提下,实现了对Project单元的测试。
二、JUnit框架的结构
通过前面的引子,其实我们已经了解了JUnit基本的结构:
1、import声明引入必须的JUnit类
2、定义一个测试类从TestCase继承
3、必需一个调用super(String)的构造函数
4、测试类包含一些以test..开头的测试方法
5、每个方法包含一个或者多个断言语句
当然还有一些其他的内容,但满足以上几条的就已经是一个JUnit测试了
三、JUnit的命名规则和习惯
1、如果有一个名为ClassA的被测试函数,那么测试类的名称就是TestClassA
2、如果有一个名为methodA的被测试函数,那么测试函数的名称就是testMethodA
四、JUnit自定义测试组合
在JUnit框相架下,他会自动执行所有以test..开头的测试方法(利用java的反射机制),如果不想让他这么“智能”,一种方法我们可以改变测试方法的名称,比如改成pendingTestMethodA,这样测试框架就会忽略它;第二种方法我们可以自己手工组合我们需要的测试集合,这个魔力我们可以通过创建test suite来取得,任何测试类都能够包含一个名为suite的静态方法:
public static Test suite();
还是以一个例子来说明,假设我们有两个名为TestClassOne、TestClassTwo的测试类,如下:
import junit.framework.*;
public class TestClassOne extends TestCase{
public TestClassOne(String method){
super(method);
}
public void testAddition(){
assertEquals(4,2+2);
}
public void testSubtration(){
assertEquals(0,2-2);
}
}
import junit.framework.*;
public class TestClassTwo extends TestCase{
public TestClassTwo(String method){
super(method);
}
public void testLongest(){
ProjectClass pc=new ProjectClass();
assertEquals(100,pc.longest());
}
public void testShortest(){
ProjectClass pc=new ProjectClass();
assertEquals(1,pc.shortest(10));
}
public void testAnotherShortest(){
ProjectClass pc=new ProjectClass();
assertEquals(2,pc.shortest(5));
}
public static Test suite(){
TestSuite suite=new TestSuite();
//only include short tests
suite.addTest(new TestClassTwo("testShortest"));
suite.addTest(new TestClassTwo("testAnotherShortest"));
}
}
首先看TestClassTwo ,我们通过suite显式的说明了我们要运行哪些test方法,而且,此时我们看到了给构造函数的String参数是做什么用的了:它让TestCase返回一个对命名测试方法的引用。接下来再写一个高一级别的测试来组合两个测试类:
import junit.framework.*;
public class TestClassComposite extends TestCase{
public TestClassComposite(String method){
super(method);
}
static public Test suite(){
TestSuite suite = new TestSuite();
//Grab everything
suite.addTestSuite(TestClassOne.class);
//Use the suite method
suite.addTest(TestClassTwo.suite());
return suite;
}
}
组合后的测试类将执行TestClassOne中的所有测试方法和TestClassTwo中的suite中定义的测试方法。
五、JUnit中测试类的环境设定和测试方法的环境设定
每个测试的运行都应该是互相独立的;从而就可以在任何时候,以任意的顺序运行每个单独的测试。
虽然这样是有好处的,但我们如果在每个测试方法里都写上相同的设置和销毁测试环境的代码,那显然是不可取的,比如取得数据库联接和关闭连接。好在JUnit的TestCase基类提供了两个方法供我们改写,分别用于环境的建立和清理:
protected void setUp();
protected void tearDown();
同样道理,在某些情况下,我们需要为整个test suite设置一些环境,以及在test suite中的所有方法都执行完成后做一些清理工作。要达到这种效果,我们需要针对suite做一个setUp和tearDown,这可能稍微复杂一点,它需要提供所需的一个suite(无论通过什么样的方法)并且把它包装进一个TestSetup对象
看下面这个例子:
public class TestDB extends TestCase{
private Connection dbConn;
private String dbName;
private String dbPort;
private String dbUser;
private String dbPwd;
public TestDB(String method){
super(method);
}
//Runs before each test method
protected void setUp(){
dbConn=new Connection(dbName,dbPort,dbUser,dbPwd);
dbConn.connect();
}
//Runs after each test method
protected void tearDown(){
dbConn.disConnect();
dbConn=null;
}
public void testAccountAccess(){
//Uses dbConn
....
}
public void testEmployeeAccess(){
//Uses dbConn
....
}
public static Test suite(){
TestSuite suite=new TestSuite();
suite.addTest(new TestDB("testAccountAccess"));
suite.addTest(new TestDB("testEmployeeAccess"));
TestSetup wrapper=new TestSetup(suite){
protected void setUp(){
oneTimeSetUp();
}
protected void tearDown(){
oneTimeTearDown();
}
}
return wrapper;
}
//Runs at start of suite
public static void oneTimeSetUp(){
//load properties of initialization
//one-time initialize the dbName,dbPort...
}
//Runs at end of suite
public static void oneTimeTearDown(){
//one-time cleanup code goes here...
}
}
上面这段代码的执行顺序是这样的:
1、oneTimeSetUp()
2、 setUp();
3、 testAccountAccess();
4、 tearDown();
5、 setUp();
6、 testEmployeeAccess();
7、 tearDown();
8、oneTimeTearDown();
六、自定义JUnit断言
通常而言,JUnit所提供的标准断言对大多数测试已经足够了。然而,在某些环境下,我们可能更需要自定义一些断言来满足我们的需要。
通常的做法是定义一个TestCase的子类,并且使用这个子类来满足所有的测试。新定义的共享的断言或者公共代码放到这个子类中。
七、测试代码的放置
三种放置方式:
1、同一目录——针对小型项目
假设有一个项目类,名字为
com.peiyuan.business.Account
相应的测试位于
com.peiyuan.business.TestAccount
即物理上存在于同一目录
优点是TestAccount能够访问Account的protected成员变量和函数
缺点是测试代码到处都是,且堆积在产品代码的目录中
2、子目录
这个方案是在产品代码的目录之下创建一个test子目录
同上,假设有一个项目类,名字为
com.peiyuan.business.Account
相应的测试位于
com.peiyuan.business.test.TestAccount
优点是能把测试代码放远一点,但又不置于太远
缺点是测试代码在不同的包中,所以测试类无法访问产品代码中的protected成员,解决的办法是写一个产品代码的子类来暴露那些成员。然后在测试代码中使用子类。
举一个例子,假设要测试的类是这样的:
package com.peiyuan.business;
public class Pool{
protected Date lastCleaned;
....
}
为了测试中获得non-public数据,我们需要写一个子类来暴露它
package com.peiyuan.business.test;
import com.peiyuan.business.Pool;
public class PoolForTesting extends Pool{
public Date getLastCleaned(){
return lastCleaned;
}
....
}
3、并行树
把测试类和产品代码放在同一个包中,但位于不同的源代码树,注意两棵树的根都在编译器的CLASSPATH中。
假设有一个项目类,位于
prod/ com.peiyuan.business.Account
相应的测试位于
test/ com.peiyuan.business.TestAccount
很显然这种做法继承了前两种的优点而摒弃了缺点,并且test代码相当独立
八、Mock的使用
1、基础
截至目前,前面提到的都是针对基本的java代码的测试,但是倘若遇到这样的情况:某个方法依赖于其他一些难以操控的东西,诸如网络、数据库、甚至是servlet引擎,那么在这种测试代码依赖于系统的其他部分,甚至依赖的部分还要再依赖其他环节的情况下,我们最终可能会发现自己几乎初始化了系统的每个组件,而这只是为了给某一个测试创造足够的运行环境让他可以运行起来。这样不仅仅消耗了时间,还给测试过程引入了大量的耦合因素。
他的实质是一种替身的概念。
举一个例子来看一下:假设我们有一个项目接口和一个实现类。如下:
public interface Environmental{
public long getTime();
}
public class SystemEnvironment implements Environmental{
public long getTime(){
return System.currentTimeMillis();
}
}
再有一个业务类,其中有一个依赖于getTime的新方法
public class Checker{
Environmental env;
public Checker(Environmental anEnv){
env=anEnv;
}
public void reminder(){
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(env.getTime());
int hour =cal.get(Calendar.HOUR_OF_DAY);
if(hour>=17){
......
}
}
}
由上可见,reminder方法依赖于getTime为他提供时间,程序逻辑实在下午5点之后进行提醒动作,但我们做测试的时候不可能等到那个时候,所以就要写一个假的Environmental来提供getTime方法,如下:
public class MockSystemEnvironment implements Environmental{
private long currentTime;
public long getTime(){
return currentTime;
}
public void setTime(long aTime){
currentTime= aTime;
}
}
写测试的时候以这个类来替代SystemEnvironment就实现了替身的作用。
2、MockObject
接下来再看如何测试servlet,同样我们需要一个web服务器和一个servlet容器环境的替身,按照上面的逻辑,我们需要实现HttpServletRequest和HttpServletResponse两个接口。不幸的是一看接口,我们有一大堆的方法要实现,呵呵,好在有人已经帮我们完成了这个工作,这就是mockobjects对象。
import junit.framework.*;
import com.mockobjects.servlet.*;
public class TestTempServlet extends TestCase {
public void test_bad_parameter() throws Exception {
TemperatureServlet s = new TemperatureServlet();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
//在请求对象中设置参数
request.setupAddParameter( "Fahrenheit", "boo!");
//设置response的content type
response.setExpectedContentType( "text/html");
s.doGet(request,response);
//验证是否响应
response.verify();
assertEquals("Invalid temperature: boo!\ n",
response.getOutputStreamContents());
}
public void test_boil() throws Exception {
TemperatureServlet s = new TemperatureServlet();
MockHttpServletRequest request =
new MockHttpServletRequest();
MockHttpServletResponse response =
new MockHttpServletResponse();
request.setupAddParameter( "Fahrenheit", "212");
response.setExpectedContentType( "text/html");
s.doGet(request,response);
response.verify();
assertEquals("Fahrenheit: 212, Celsius: 100.0\ n",
response.getOutputStreamContents());
}
}
3、EasyMock
EasyMock采用“记录-----回放”的工作模式,基本使用步骤:
* 创建Mock对象的控制对象Control。
* 从控制对象中获取所需要的Mock对象。
* 记录测试方法中所使用到的方法和返回值。
* 设置Control对象到“回放”模式。
* 进行测试。
* 在测试完毕后,确认Mock对象已经执行了刚才定义的所有操作
项目类:
package com.peiyuan.business;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* <p>Title: 登陆处理</p>
* <p>Description: 业务类</p>
* <p>Copyright: Copyright (c) 2006</p>
* <p>Company: </p>
* @author Peiyuan
* @version 1.0
*/
public class LoginServlet extends HttpServlet {
/* (非 Javadoc)
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
// check username & password:
if("admin".equals(username) && "123456".equals(password)) {
ServletContext context = getServletContext();
RequestDispatcher dispatcher = context.getNamedDispatcher("dispatcher");
dispatcher.forward(request, response);
}
else {
throw new RuntimeException("Login failed.");
}
}
}
测试类:
package com.peiyuan.business;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.easymock.MockControl;
import junit.framework.TestCase;
/**
* <p>Title:LoginServlet测试类 </p>
* <p>Description: 基于easymock1.2</p>
* <p>Copyright: Copyright (c) 2006</p>
* <p>Company: </p>
* @author Peiyuan
* @version 1.0
*/
public class LoginServletTest extends TestCase {
/**
* 测试登陆失败的情况
* @throws Exception
*/
public void testLoginFailed() throws Exception {
//首先创建一个MockControl
MockControl mc = MockControl.createControl(HttpServletRequest.class);
//从控制对象中获取所需要的Mock对象
HttpServletRequest request = (HttpServletRequest)mc.getMock();
//“录制”Mock对象的预期行为
//在LoginServlet中,先后调用了request.getParameter("username")和request.getParameter("password")两个方法,
//因此,需要在MockControl中设置这两次调用后的指定返回值。
request.getParameter("username"); // 期望下面的测试将调用此方法,参数为"username"
mc.setReturnValue("admin", 1); // 期望返回值为"admin",仅调用1次
request.getParameter("password"); // 期望下面的测试将调用此方法,参数为" password"
mc.setReturnValue("1234", 1); // 期望返回值为"1234",仅调用1次
//调用mc.replay(),表示Mock对象“录制”完毕
mc.replay();
//开始测试
LoginServlet servlet = new LoginServlet();
try {
//由于本次测试的目的是检查当用户名和口令验证失败后,LoginServlet是否会抛出RuntimeException,
//因此,response对象对测试没有影响,我们不需要模拟它,仅仅传入null即可。
servlet.doPost(request, null);
fail("Not caught exception!");
}
catch(RuntimeException re) {
assertEquals("Login failed.", re.getMessage());
}
// verify:
mc.verify();
}
/**
* 测试登陆成功的情况
* @throws Exception
*/
public void testLoginOK() throws Exception {
//首先创建一个request的MockControl
MockControl requestCtrl = MockControl.createControl(HttpServletRequest.class);
//从控制对象中获取所需要的request的Mock对象
HttpServletRequest requestObj = (HttpServletRequest)requestCtrl.getMock();
//创建一个ServletContext的MockControl
MockControl contextCtrl = MockControl.createControl(ServletContext.class);
//从控制对象中获取所需要的ServletContext的Mock对象
final ServletContext contextObj = (ServletContext)contextCtrl.getMock();
//创建一个RequestDispatcher的MockControl
MockControl dispatcherCtrl = MockControl.createControl(RequestDispatcher.class);
//从控制对象中获取所需要的RequestDispatcher的Mock对象
RequestDispatcher dispatcherObj = (RequestDispatcher)dispatcherCtrl.getMock();
requestObj.getParameter("username"); // 期望下面的测试将调用此方法,参数为"username"
requestCtrl.setReturnValue("admin", 1); // 期望返回值为"admin",仅调用1次
requestObj.getParameter("password"); // 期望下面的测试将调用此方法,参数为" password"
requestCtrl.setReturnValue("123456", 1); // 期望返回值为"1234",仅调用1次
contextObj.getNamedDispatcher("dispatcher");
contextCtrl.setReturnValue(dispatcherObj, 1);
dispatcherObj.forward(requestObj, null);
dispatcherCtrl.setVoidCallable(1);
requestCtrl.replay();
contextCtrl.replay();
dispatcherCtrl.replay();
//为了让getServletContext()方法返回我们创建的ServletContext Mock对象,
//我们定义一个匿名类并覆写getServletContext()方法
LoginServlet servlet = new LoginServlet() {
public ServletContext getServletContext() {
return contextObj;
}
};
servlet.doPost(requestObj, null);
}
}
JUnit介绍(转)的更多相关文章
- JUnit介绍
8.1.1 JUnit简介 JUnit主要用来帮助开发人员进行Java的单元测试,其设计非常小巧,但功能却非常强 大. 下面是JUnit一些特性的总结: — 提供的API可以让开发人员写出测试结果明 ...
- Junit介绍以及使用
在介绍junit之前,把一些知识点提前了解一下 单元测试是一个对单一实体(类或方法)的测试. 测试用例(Test Case)是为某个特殊目标而编制的一组测试输入.执行条件以及预期结果,以便测试某个程序 ...
- JUnit介绍,JUnit是什么?
JUnit是什么? JJUnit是用于编写和运行可重复的自动化测试的开源测试框架, 这样可以保证我们的代码按预期工作.JUnit可广泛用于工业和作为支架(从命令行)或IDE(如Eclipse)内单独的 ...
- 使用 JUnit 进行单元测试 - 教程
tanyuanji@126.com 版本历史 JUnit 该教程主要讲解 JUnit 4.x 版本的使用,以及如何在Eclipse IDE 中如何使用JUnit 目录 tanyuanji@126. ...
- Spring集成JUnit单元测试框架
一.JUnit介绍 JUnit是Java中最有名的单元测试框架,用于编写和运行可重复的测试,多数Java的开发环境都已经集成了JUnit作为单元测试的工具.好的单元测试能极大的提高开发效率和代码质量. ...
- Junit4 单元测试框架的常用方法介绍
Junit 介绍: Junit是一套框架(用于JAVA语言),由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework),即 ...
- 【Java】Junit快速入门
Junit介绍 JUnit是一个Java语言的单元测试框架.它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个. JUnit ...
- junit基础学习之-简介(1)
JUnit介绍 JUnit是一个开源的Java单元测试框架,由 Erich Gamma 和 Kent Beck 开发完成. 1 JUnit简介 JUnit主要用来帮助开发人员进行Java的单元测试, ...
- Spring+JUnit4单元测试入门
(一).JUnit介绍 JUnit是Java中最有名的单元测试框架,多数Java的开发环境都已经集成了JUnit作为单元测试的工具.好的单元测试能极大的提高开发效率和代码质量. Maven导入juni ...
随机推荐
- 转 原生js canvas实现苹果电脑mac OS窗口最小化效果
http://www.17sucai.com/pins/demo-show?id=2459 http://www.17sucai.com/pins/demo-show?id=2458 很多资料 ,前 ...
- xPath Helper插件
xPath Helper插件 xPath helper是一款Chrome浏览器的开发者插件,安装了xPath helper后就能轻松获取HTML元素的xPath,程序员就再也不需要通过搜索html源代 ...
- Linux中普通用户配置sudo权限(带密或免密)
配置步骤如下: 1.登陆或切换到root用户下: 2.添加sudo文件的写权限,命令是:chmod u+w /etc/sudoers 3.编辑sudoers文件:vi /etc/sudoers 找到这 ...
- 生活英语读写MOOC-Literature Tutor-有声名著阅读推荐
生活英语读写MOOC-Literature Tutor-有声名著阅读推荐 1. Alice's Adventures in Wonderland 爱丽丝漫游奇境记 音频与文本下载地址:链接:http: ...
- ubuntu16.4下使用QT修改系统时间
我也是在网上找的,自己随便改了一下六个lineEdit控件,每个控件输入日期时间,点击按钮触发函数可修改时间. 1 //一键修改系统时间 QString year = ui->lineEdit_ ...
- 高可用Redis(七):Redis持久化
1.什么是持久化 持久化就是将数据从掉电易失的内存同步到能够永久存储的设备上的过程 2.Redis为什么需要持久化 redis将数据保存在内存中,一旦Redis服务器被关闭,或者运行Redis服务的主 ...
- figure 的使用
1.figure语法及操作(1)figure语法说明 figure(num=None, figsize=None, dpi=None, facecolor=None, edgecolor=None, ...
- javaweb c3p0连接oracle12c
最近在搞javaweb,在连接池上碰到了一系列的问题,在Junit测试时,oracle12c报错: ORA-28040: 没有匹配的验证协议 百度解决:修改 $ORACLE_HOME/network/ ...
- Hadoop之Flume 记录
出现这个错误是自己的粗心大意,解决: 在配置flume-conf.properties文件时,source和channel的对应关系是: myAgentName.sources.mySourceNam ...
- MySQL 远程连接问题
使用Workbench 无法远程连接Mysql服务器提示如下错误: 查找原因: 显示只能localhost 访问. 解决方法:修改授权远程访问 create user 'root'@'%' ident ...