(转帖)Struts资源文件:Struts Message Resources

转自:http://www.javafan.net/article/20040430102526725.html

 

页面功能  【加入收藏】 【推荐给朋友】 【字体:大 中 小】 【关闭】   

  

 

Struts Message Resources

作者:Nick Heudecker    来自:未知

总览

许多刚刚学习Struts的程序员在使用Struts的MessageResources特性的时候会遭遇很多困难。本文将试图阐述MessageResources特性的优点并给出了具体的例子说明它的用法。

作者: Nick Heudecker, System Mobile Inc.

概述

类MessageResources可以使开发者方便地支持多语言,包括支持多时间格式和数字格式。使用资源包的另一个好处是允许开发者将标签字符串集中存储在一个位置,而不是分散在不同的JSP页面里。例如,对于每个用户的名字的标签"First Name" ,我们可以将它写在资源包中,在适当的地方通过Struts标签简单的进行引用:

<bean:write key="label.first.name"/>

这样做将会让你对程序的更改变的简单容易,你不必在每一个JSP页面里更改标签的内容了。

用法

使用消息资源包需要你做下面的事情:

1. 为你想要支持的地方创建一个消息资源包。

2. 配置WEB应用,加载消息资源包。

3. 使用相应的JSP标签加载资源或者...

4. ...在一个Action类中加载资源。

创建资源包

MessageResources 类的默认的实现是一个包含"key=value" 对的文件,下面的一个消息资源包文件的例子。

label.username=Username

label.password=Password

label.first.name=First Name

label.last.name=Last Name

label.email=Email Address

label.phone.number=Phone Number

label.welcome=Welcome back {0} {1}!

error.min.length=The input must be at least {0} characters in length.

error.max.length=The input cannot be longer than {0} characters in length.

大括号包围的整数是对java.text.MessageFormat 类的支持,程序员可以向value字符串中传递参数,对每个value字符串你最多可以传递4个参数。

配置

有两种途径通知Struts你的资源包的位置:web.xml 文件或者struts-config.xml 文件。首先来看web.xml 文件的配置:

<servlet>

<servlet-name>action</servlet-name>

<servlet-class>

    org.apache.struts.action.ActionServlet

</servlet-class>

<init-param>

<param-name>

    application

</param-name>

<param-value>

    com.systemmobile.example.ApplicationResources

</param-value>

</init-param>

</servlet>

这个配置说明你的资源包的名字是ApplicationResources.properties,它位于com.systemmobile.example 包中。后缀".properties" 是隐含的,你不必显式地写出来。如果你还有另一个资源文件在相同的包中,例如ApplicationResources_fr.properties ,用来支持法语,你只需要象上面定义的那样列出文件名字即可。

定义资源文件的第二中方法(上面已经提到),是在struts-config.xml 文件中配置:

<message-resources parameter="com.systemmobile.example.ApplicationResources"/>

属性parameter 是必须的。和在web.xml文件中配置一样, 需要注意的是文件在包中的位置。

使用struts-config.xml 文件来配置消息资源文件是推荐的做法,因为它更有可扩展性,更灵活。

你可以使用message-resources 标签从不同的资源文件取不同的消息,前提是在配置的时候为不同的资源文件给出不同的key 属性的值。例如: <message-resources key="myResources" parameter="com.systemmobile.example.ApplicationResources"/>

<message-resources key="moreResources" parameter="com.systemmobile.example.MoreApplicationResources"/>

然后你必须这样使用bean:message 标签: <bean:message bundle="moreResources" key="some.message.key"/>

设置属性null 的值为"false" 后,如果某个资源字符串不存在将返回???key??? 而不是仅仅显示null。这样很容易在JSP页面中看到你没有定义的资源,使得内部测试的速度更快。(关于如何从资源文件中获得消息的详细内容参见国际化 一节) <message-resources parameter="com.systemmobile.example.ApplicationResources" null="false"/>

另外,message-resources 标签允许你使用自己实现的MessageResourcesFactory 接口,这不在本文的讨论范围。

资源文件放在哪里

关于资源文件最常见的问题是将资源文件放在WAR文件的哪里。简单的回答是该文件必须放在你的classpath下面,这意味着将资源文件放在一个JAR 文件中,或者放在/WEB-INF/classes 目录极其子目录下。下表给出了资源文件的位置,在message-resources 标签中"parameter" 属性的值以及简短的说明。

Resources Location parameter Value Description

/WEB-INF/classes/ApplicationResources.properties ApplicationResources 文件放在classes 目录下, 该目录在web应用的classpath中.

/WEB-INF/classes/resources/ApplicationResources.properties resources.ApplicationResources 该文件放在"resources" 目录下, 所以包名也就是路径名要给出。

In the app.jar file, in the com.systemmobile.example package/directory. com.systemmobile.example.ApplicationResources 文件在JAR文件中的全路径。

Tags

最常用Struts 标签是bean:message 标签。使用这个标签的"key" 可以从资源文件中读特定的消息资源。你还可以传入四个参数中的一个或全部:

<bean:message key="label.password"/>

<bean:message key="error.min.length" arg0="6"/>

<bean:message key="label.welcome" arg0="Ralph" arg1="Nader"/>

html:message 可以让你向用户显示错误信息(默认)或消息信息,而html:errors 只显示错误信息。很明显,错误信息或消息信息一定要保存在request里,否则就什么也不会显示。这里有一个显示消息信息的例子:

<logic:messagesPresent message="true">

  <html:messages id="msg" message="true">

    <div class="success">

      <bean:write name="msg"/>

    </div><br/>

  </html:messages>

</logic:messagesPresent>

还有一些标签也有限地支持消息资源,比如html:link。html:link标签通过定义"titleKey" 属性来显示标题文本。许多html 使用 "altKey" 属性从资源文件里获得alternate(替代)文本。

Actions

你还可以在Action 类中使用消息资源文件。Action 类有两个方法得到一个MessageResource 类的实例:

// 返回一个request里的资源文件

protected MessageResources getResources(HttpServletRequest request);

// 返回一个request里的资源文件,

// 该资源文件的标志上<message-resources/> 元素的内容

protected MessageResources getResources(javax.servlet.http.HttpServletRequest request, java.lang.String key);

MessageResources类可以让你从资源文件中得到本地化的消息。The API for MessageResources 可以在资源中找到。比较常用的方法有:

// these methods load a resources key for the given locale

public String getMessage(java.util.Locale locale, java.lang.String key);

public String getMessage(java.util.Locale locale, java.lang.String key,

       java.lang.Object arg0);

public String getMessage(java.util.Locale locale, java.lang.String key,

       java.lang.Object[] args);

public String getMessage(java.util.Locale locale, java.lang.String key,

       java.lang.Object arg0, java.lang.Object arg1)

public String getMessage(java.util.Locale locale, java.lang.String key,

       java.lang.Object arg0, java.lang.Object arg1, java.lang.Object arg2);

public String getMessage(java.util.Locale locale, java.lang.String key, java.lang.Object arg0,

       java.lang.Object arg1, java.lang.Object arg2, java.lang.Object arg3);

// these methods load a resources key for the locale retrieved

// from the HttpServletRequest

public String getMessage(java.lang.String key);

public String getMessage(java.lang.String key, java.lang.Object arg0);

public String getMessage(java.lang.String key, java.lang.Object[] args);

public String getMessage(java.lang.String key, java.lang.Object arg0,

       java.lang.Object arg1);

public String getMessage(java.lang.String key, java.lang.Object arg0,

       java.lang.Object arg1, java.lang.Object arg2);

public String getMessage(java.lang.String key, java.lang.Object arg0,

       java.lang.Object arg1, java.lang.Object arg2, java.lang.Object arg3);

这些返回的字符串可以被设置成request 或 session 的参数并串会表现层。你可能注意到了一些重载方法getMessage(...) 选择了参数Object,而另外一些采用了参数arg0...arg3。这和 bean:message arg0...arg3 属性等价。

除了MessageResources 类,还有一些类使用了资源文件。ActionMessage类被用来从action 向JSP之间传递消息资源中的keys 。消息被用来作为bean 的属性。ActionError, ActionMessage的子类,使用消息资源中的keys 存储验证失败后的错误信息。

国际化

从资源文件中提取一个本地化信息可以由类MessageResources 来处理,或者由它的直接子类PropertyMessageResources类处理。既然类PropertyMessageResources 等经常地被用到,那么我们就来看看它是怎样使用getMessage(Locale, String) 方法来从资源文件中读取消息的。

举例说明:

1. 如果你在ApplicationResources_pt_br.properties (Brazilian Portuguese)中没有发现消息的定义,系统将在ApplicationResources_pt.properties 文件中找,如果ApplicationResources_pt.properties 文件不存在或者也没有该消息,那就去ApplicationResources.properties 文件里查找了。

2. 如果消息找到了,它就被加到本地化的缓存中并返回java.lang.String型数据。

3. 如果消息没有找到,此时如果returnNull 属性被为默认的true,将返回 null。 否则将返回类似 ???key??? 的字符串,key 就是那个被传递的参数。

JSTL

JSTL (JavaServer Pages Standard Tag Library) 的fmt标签最近开始流行起来,用来向JSP中显示资源文件的信息。它能很好地和Struts结合在一起。使用它非常简单,只要下载JSTL 的jar 文件和TLDs 并把它们拷贝到你的应用的正确的位置,然后在web.xml文件中加入下面的内容:

<context-param>

  <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>

  <param-value>ApplicationResources</param-value>

</context-param>

上面的配置是假定你的ApplicationResources.properties文件是放在/WEB-INF/classes 目录下的。 参见above 更多情况。

然后将这个标签库直接放在你的JSP中:

<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>

最后,下面的标签将显示资源文件的内容:

<fmt:message key="label.first.name"/>

还有一个使用fmt 标签获得资源的例子。(注意: 该段程序取自Jakarta JSTL examples。)

// loading a resource from a specific bundle and populating a parameter

<fmt:message key="currentTime" bundle="${deBundle}">

 <fmt:param value="${currentDateString}"/>

</fmt:message>

// using the forEach iterator to populate paramters

<fmt:message key="serverInfo" bundle="${deBundle}">

 <c:forEach var="arg" items="${serverInfoArgs}">

  <fmt:param value="${arg}"/>

 </c:forEach>

</fmt:message>

结论

在向JSP文件方便地传递消息的同时,Struts使用消息资源文件还帮助我们创建国际化的Web应用。我们既可以使用正在快速发展中的JSTL标签,也可以使用Struts标签,或直接在action中得到一条具体的消息。我希望这篇文章为您阐明了一些Struts中常用的但有时会混淆的东西。

关于作者

Nick Heudecker 是一位软件开发人员,具有6年的企业应用的开发经验。 他所在的公司, System Mobile, Inc.,专门从事应用集成,定制软件开发和无线应用。 他还是Sun认证JAVA程序员,现在居住在Ann Arbor, Michigan。

资源

下面的资源也许对您了解更多的关于Struts资源文件有帮助:

JavaDoc for the classes of interest:

java.util.ResourceBundle

java.util.Locale

org.apache.struts.util.MessageResources

org.apache.struts.action.ActionError

org.apache.struts.action.ActionMessage

Ted Husted's Struts Tips: Very handy advice, especially for resource bundle usage.

Struts Home Page

Struts-User Mailing List Archive: Many people use Struts, so there is a very good chance that your question has be answered already. Please use all available resources before asking your question on the mailing list.

JSTL Homepage and the Jakarta JSTL Implementation

注意事项

Packages are just directory structures used to avoid naming conflicts. If you put a message bundle in a directory named resources under /WEB-INF/classes, this is the equivalent of putting the file in a package named resrouces. While basic, this point seems to trip up many new programmers.

 

 

  

 

页面功能  【加入收藏】 【推荐给朋友】 【字体:大 中 小】 【关闭】    

 

 

Copyright © 2003 - 2005 JavaFan.NET All Rights Reserved

慎用setActionForm() 在使用strutstestcase测试struts程序时

setActionForm()方法会调用ActionForm里的reset()方法清掉你传入actionForm中的测试数据。

如:

   EmailResetPsdForm form = new EmailResetPsdForm();

  form.setUserName("ppig");

  setActionForm(form);

在这里,测试数据userName="ppig"不会被传入使用这个form bean的action中。

应该使用替代的方法来传递测试数据:

addRequestParameter("userName", "ppig");

下面是strutstestcase里的文档对setActionForm方法的说明。

    /*

     * setActionForm

     * public void setActionForm(ActionForm form)Sets an ActionForm instance to be used in this

     * test. The given ActionForm instance will be stored in the scope specified in the Struts

     * configuration file (ie: request or session). Note that while this ActionForm instance

     * is passed to the test, Struts will still control how it is used. In particular, it

     * will call the ActionForm.reset() method, so if you override this method in your

     * ActionForm subclass, you could potentially reset attributes in the form passed

     * through this method.

     *

     * Parameters:

     * form - the ActionForm instance to be used in this test.

     *

     */

ant初始化数据库的问题

如果数据库是utf-8编码,用下面的初始化数据库任务会出错:

<target name="initdb" >

<sql driver="com.mysql.jdbc.Driver"

url="jdbc:mysql://localhost:3306/learndiarydb"

userid="dbuser"

password="1234"

classpath="lib/mysql-connector-java-3.1.12-bin.jar"

autocommit="true"

rdbms="mysql"

print="true"

src="database/testdata_mysql.sql" />

</target>

错误报告为:

Buildfile: build.xml

initdb:

      [sql] Executing file: F:\learndiary\learndiary\old\database\testdata_mysql.sql

      [sql] 0 rows affected

      [sql] Failed to execute:    INSERT INTO article VALUES (1,1,0,'鍏憡鐗?,'鍏憡鐗?,'2004-09-28 08:38:54','',1,'2005-11-10 08:27:50',6,1,'admin',1)

BUILD FAILED

F:\learndiary\learndiary\old\build.xml:124: java.sql.SQLException: Syntax error or access violation,  message from server: "You have an error in your SQL syntax.  Check the manual that corresponds to your MySQL server version for the right syntax to use near '閸忣剙鎲¢悧锟?,'2004-09-28 08:38:54','',1,'2005-11-10 08:27:50"

Total time: 12 seconds

而这个初始化通过mysql的source learndiarydb yourpath\testdata_mysql.sql的命令来进行则不会出错。

不知是什么原因?

Junit不能用于多线程的测试吗?

  我在为(改善学习日记的登录模式 (0篇) http://www.learndiary.com/disGoalContentAction.do?goalID=1214&naviStr=a10a60)写发送重置密码信件的测试代码时,发现测试过程中,测试代码中另一个发送邮件的线程没有执行,测试程序就结束了。

  难道junit不能用于多线程的程序的测试吗?

  

  这是含发送邮件的程序:

// $Id: EmailResetPsdAction.java,v 1.8 2005/12/19 16:00:49 dashing_meng Exp $

// Copyright (c) 2004-2005 Http://www.learndiary.com. All Rights Reserved.

// Permission to use, copy, modify, and distribute this software and its

// documentation without fee, and without a written agreement is hereby

// granted, provided that the above copyright notice and this paragraph

// appear in all copies.  This software program and documentation are

// copyrighted by http://www.learndiary.com. The software program and

// documentation are supplied "AS IS", without any accompanying services

// from The LearnDiary. The LearnDiary does not warrant that the operation

// of the program will be uninterrupted or error-free. The end-user

// understands that the program was developed for research purposes and is

// advised not to rely exclusively on the program for any reason.

// IN NO EVENT SHALL HTTP://WWW.LEARNDIARY.COM BE LIABLE TO ANY PARTY FOR

// DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,

// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS

// DOCUMENTATION, EVEN IF HTTP://WWW.LEARNDIARY.COM HAS BEEN ADVISED OF THE

// POSSIBILITY OF SUCH DAMAGE. HTTP://WWW.LEARNDIARY.COM SPECIFICALLY

// DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED

// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE

// SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND

// HTTP://WWW.LEARNDIARY.COM HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,

// SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

package com.learndiary.website.action.account;

import java.sql.Timestamp;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.*;

import org.apache.struts.action.ActionForward;

import org.apache.struts.action.Action;

import org.apache.struts.action.ActionMapping;

import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;

import com.learndiary.website.actionform.EmailResetPsdForm;

import com.learndiary.website.manager.EmailResetPsdManager;

import com.learndiary.website.manager.EmailSender;

import com.learndiary.website.manager.UserManager;

import com.learndiary.website.model.Email;

import com.learndiary.website.model.EmailResetPsdInfo;

import com.learndiary.website.model.UserInfo;

import com.learndiary.website.util.Util;

/**

 * Process user's asking for sending resetting password token email request.

 * @author http://www.learndiary.com,LearnDiary Develop Group

 */

public class EmailResetPsdAction extends Action {

  /**

   *  Function:

   *  Process user's asking for sending resetting password token email request.

   *  Pseudo Coding:

   * 

   *  {

   *    String userName;

   *    UserInfo userInfo;

   *    int userID;

   *    UserManager userManager;

   *    EmailResetPsdManager emailResetPsdManager;

   *    EmailResetPsdInfo emailResetPsdInfo;

   *    Email email;

   * 

   *    userName=((EmailResetPsdForm)form).getUserName();//Get the userName from form

   *    userInfo= userManager. findByName(userName);//Get userInfo

   *    if (userInfo==null){

   *      forward to emailFailure.jsp;

   *    }

   *    userID=userInfo.getUserID();

   *    emailResetPsdInfo=emailResetPsdManager.findByID();// Get emailResetPsdInfo

   *    if (emailResetPsdInfo!=null){

   *      foward to emailFailure.jsp;

   *    }

   * 

   *    //set emaiResetPsdInfo

   *    emaiResetPsdInfo.setUserID(userID);

   *    emaiResetPsdInfo.setToken(com.learndiary.website.util.Util.genRandomStr());

   *    emailResetPsdInfo.setSendDate(System.getCurrentTime());

   *    emailResetPsdManager.insertResetPsdInfo(emailResetPsdInfo);//record emailResetPsdInfo

   *    //set reset passwrod email's content

   *    email.setRecipient(userInfo.getEmail());

   *    email.setSubject("your reset password request.");

   *    email.setText( "http://localhost:8080/learndiary/resetPsdAction.do?userID=" + userID + "token=" + emaiResetPsdInfo.getToken());

   *    emailSender.send(email);

   *    forward to emailSuccess.jsp;

   *  }

   * 

   * 

   *   

   */

    private org.apache.commons.logging.Log __log = LogFactory.getFactory().getInstance(this.getClass());

  public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {

String myaction = mapping.getPath(); 

    __log.info("Enter action: "+myaction);

    String target=null;

    String userName=null;

    UserInfo userInfo=null;

    int userID;

    UserManager userManager=new UserManager();

    EmailResetPsdManager emailResetPsdManager=new EmailResetPsdManager();

    EmailResetPsdInfo emailResetPsdInfo=null;

    Email email=null;

    EmailSender emailSender=new EmailSender();

   

    userName=((EmailResetPsdForm)form).getUserName();//Get the userName from form

    try {

__log.debug("Before findByName,and name is: "+userName);

        userInfo= userManager.findByName(userName);//Get userInfo

__log.debug("After findByName,and userInfo is: "+userInfo);

    } catch (ClassNotFoundException e1) {

e1.printStackTrace();

    }

    if (userInfo==null){

      target=new String("failure");

  return (mapping.findForward(target));//no such a user,forward to emailFailure.jsp;

    }

    userID=userInfo.getUserID();

    __log.debug("userID: "+userID);

    try {

        emailResetPsdInfo=emailResetPsdManager.findByID(userID);// Get emailResetPsdInfo

    } catch (ClassNotFoundException e) {

e.printStackTrace();

    }

   

    __log.debug("existed emailResetPsdInfo is: " + emailResetPsdInfo);

    if (emailResetPsdInfo!=null){//user requested emailing resetting password token in 72 hours

      target=new String("failure");

  return (mapping.findForward(target));//foward to emailFailure.jsp;

    }

 

    //set emaiResetPsdInfo

emailResetPsdInfo=new EmailResetPsdInfo();

    emailResetPsdInfo.setUserID(userID);

    emailResetPsdInfo.setToken(Util.genRandomStr());

    emailResetPsdInfo.setSendTime(new Timestamp(System.currentTimeMillis()));

    __log.debug("new emailResetPsdInfo is: "+emailResetPsdInfo);

    try {

      emailResetPsdManager.insertResetPsdInfo(emailResetPsdInfo);

      __log.debug("insert emailResetPsdInfo ok!");

    } catch (Exception e2) {

      e2.printStackTrace();

    }//record emailResetPsdInfo

    //set reset passwrod email's content

    email=new Email();

    email.setRecipient(userInfo.getEmail());

    email.setSubject("Your resetting password request.");

    email.setText( "http://localhost:8080/learndiary_login/account/resetPsd.jsp?userID="

      + userID + "&token=" + emailResetPsdInfo.getToken());

    __log.debug("email is: " + email); 

    emailSender.send(email);//这里要产生另一个线程发送邮件,但在测试程序中没有执行,在正常运行时会执行

   

    target=new String("success");

return (mapping.findForward(target));//email resetting password token success,

                                     //forward to emailSuccess.jsp;

  }

 

  /**

   * process general error

   * @param e error object

  

  private void generalError(Exception e) {

    e.printStackTrace();

    __log.error(" [EmailResetPsd] Error - " + e.getMessage());

  }*/

}

这时对应的测试程序:

//$Id: EmailResetPsdActionTest.java,v 1.3 2005/12/19 16:00:49 dashing_meng Exp $

//Copyright (c) 2004-2005 Http://www.learndiary.com. All Rights Reserved.

//Permission to use, copy, modify, and distribute this software and its

//documentation without fee, and without a written agreement is hereby

//granted, provided that the above copyright notice and this paragraph

//appear in all copies.  This software program and documentation are

//copyrighted by http://www.learndiary.com. The software program and

//documentation are supplied "AS IS", without any accompanying services

//from The LearnDiary. The LearnDiary does not warrant that the operation

//of the program will be uninterrupted or error-free. The end-user

//understands that the program was developed for research purposes and is

//advised not to rely exclusively on the program for any reason.

//IN NO EVENT SHALL HTTP://WWW.LEARNDIARY.COM BE LIABLE TO ANY PARTY FOR

//DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,

//INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS

//DOCUMENTATION, EVEN IF HTTP://WWW.LEARNDIARY.COM HAS BEEN ADVISED OF THE

//POSSIBILITY OF SUCH DAMAGE. HTTP://WWW.LEARNDIARY.COM SPECIFICALLY

//DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED

//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE

//SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND

//HTTP://WWW.LEARNDIARY.COM HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,

//SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

package com.learndiary.website.action.account;

import java.sql.Timestamp;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import servletunit.struts.MockStrutsTestCase;

import com.learndiary.website.actionform.EmailResetPsdForm;

import com.learndiary.website.dao.EmailResetPsdDAO;

import com.learndiary.website.dao.TransContext;

import com.learndiary.website.dao.UserDAO;

import com.learndiary.website.model.EmailResetPsdInfo;

import com.learndiary.website.model.UserInfo;

/**

 * test EmailResetPsdAction

 * @author http://www.learndiary.com, LearnDiary Develop Group

 */

public class EmailResetPsdActionTest extends MockStrutsTestCase {

Log log = LogFactory.getLog(EmailResetPsdActionTest.class.getName());

private transient TransContext globalTran = null;

UserDAO userDAO=null;

EmailResetPsdDAO emailResetPsdDAO=null;

//the contextDir can be commented by adding classpath:${web} into build.xml

//String contextDir = "E:\zhangwei\eclipse_workspace1\learndiary_login\web";

public EmailResetPsdActionTest(String testName) {

super(testName);

}

   

    /**

     * empty table "user" and "emailresetpsd";

     * insert two user:"ppig" and "tom" into table user,

     * "ppig" to test successful case,

     * "tom" to test duplicate request failure case;

     * insert a record into table emailresetpsd for

     * "tom" to test duplicate request failure case.

     * @see junit.framework.TestCase#setUp()

     */

public void setUp() throws Exception {

super.setUp();

//this.setContextDirectory(new File(contextDir));

log.debug("ahah");

globalTran = new TransContext();

userDAO = new UserDAO(globalTran);

emailResetPsdDAO=new EmailResetPsdDAO(globalTran);

//empty two tables

userDAO.deleteAll();

        emailResetPsdDAO.deleteAll();

       

        UserInfo info1=new UserInfo();//for testing successful request

info1.setUserName("ppig");

info1.setPsd("123456");

info1.setEmail("learndiary@126.com");

userDAO.insertObject(info1);

UserInfo info2=new UserInfo();//for testing duplicate request failure

info2.setUserName("tom");

info2.setPsd("223456");

info2.setEmail("tom@tom.com");

userDAO.insertObject(info2);

        int userID2=((UserInfo)userDAO.findByName("tom")).getUserID();

        

EmailResetPsdInfo emailResetPsdInfo=new EmailResetPsdInfo();

emailResetPsdInfo.setUserID(userID2);

emailResetPsdInfo.setToken("12345678");

emailResetPsdInfo.setSendTime(new Timestamp(System.currentTimeMillis()-1000*60*2));

emailResetPsdDAO.insertObject(emailResetPsdInfo);

}

   

    /**

     * empty table "user" and table "emailresetpsd"

     * @see junit.framework.TestCase#tearDown()

     */

public void tearDown() throws Exception {

super.tearDown();

        userDAO.deleteAll();

        emailResetPsdDAO.deleteAll();

}

/**

* request emailing resetting password token successfully

*/

public void testSuccess() {

// a valid request

setRequestPathInfo("/emailResetPsdAction");

EmailResetPsdForm form = new EmailResetPsdForm();

form.setUserName("ppig");

setActionForm(form);

actionPerform();

verifyForward("success");

// verifyForwardPath("/success.jsp");

// assertEquals("deryl",getSession().getAttribute("authentication"));

verifyNoActionErrors();

}

    /**

     *  request emailing resetting password token failure

     *

     */

public void testFailure() {

// fail when duplicate request

EmailResetPsdForm form = new EmailResetPsdForm();

form.setUserName("tom");

setRequestPathInfo("/emailResetPsdAction");

setActionForm(form);

actionPerform();

verifyForward("failure");

// fail when user not found

form = new EmailResetPsdForm();

form.setUserName("000000000000000");

setRequestPathInfo("/emailResetPsdAction");

setActionForm(form);

actionPerform();

verifyForward("failure");

verifyNoActionErrors();

}

public static void main(String[] args) {

junit.textui.TestRunner.run(EmailResetPsdActionTest.class);

}

}

呵呵,测试了一下新买的浴霸(非程序:))

  新买某种品牌的浴霸,说明书称:出厂经4。C的冰水测试通过。

  这两天在学测试程序,为了验证这个浴霸的安全性,我对它测试了一番。用还是热的水泼在取暖泡上,哧的一响,经受了考验,再来一下,在另一个取暖泡上测试。结果取暖泡里面冒烟了。表面也有裂纹了。失败。

  看来,出厂的测试是夸大其辞呀。

  老婆骂我。。。

  我只是告诉她,经过测试,这个浴霸不能被淋浴头淋到。

  浴霸真的不能被水淋吗?还是我们这个浴霸本身就有问题?还是测试过度了。我自己也糊涂了。。。

(转帖)MYSQL 修改root密码命令

转自:http://it.dg.gd.cn/print.php/770

                  正文

××××××××××××××××××××××××××××××××××××××××××××××××××××

MYSQL 修改root密码命令

2005-10-14       

打印自: IT学院,linux应用,linux技巧,web技术,windows相关,数据库,网管技术,网页技术,unix,轻松一刻,精品网文,精品图片,博客文章

地址: http://it.dg.gd.cn/article.php/770

MYSQL 修改root密码命令

cmd下切换到 mysql 安装目录



d:/mysql/bin

前提:mysql用户root密码为空.

输入 mysql -u root mysql

mysql> 状态下输入 update user set password=password('新密码') where user='root';

回显

Query OK, 0 rows affected (0.00 sec)

Rows matched: 2 Changed: 0 Warnings: 0

mysql> 状态下输入 FLUSH PRIVILEGES;

回显

Query OK, 0 rows affected (0.00 sec)

mysql> 状态下输入 quit

退出 sql

注意每个命令后都要加上一个分号 ";"

mysql 才开始执行该行命令

而第二个指令会让已载入记忆体的 mysql 系统资料库更新

重起 mysql .

在更新 root 密码後,日後要与 MySQL 连线的方法为:

mysql -uroot -p新密码

 

 

责任编辑: yufish

 

在XP下强调单元测试必须由类包的编写者负责编写

转自:http://www-128.ibm.com/developerworks/cn/java/l-junit/

怎样使用Junit Framework进行单元测试的编写 

 

  文档选项

   将此页作为电子邮件发送

 

 

对此页的评价

  帮助我们改进这些内容

 

 

 

级别: 初级

关于作者艾昂科技上海公司

2002 年 7 月 01 日

随着Refactoring技术和XP软件工程技术的广泛推广,单元测试的作用在软件工程中变得越来越重要,而一个简明易学、适用广泛、高效稳定的单元测试框架则对成功的实施单元测试有着至关重要的作用。在java编程语句环境里,Junit Framework是一个已经被多数java程序员采用和实证的优秀的测试框架,但是多数没有尝试Junit Framework的程序员在学习如何Junit Framework来编写适应自己开发项目的单元测试时,依然觉得有一定的难度,这可能是因为Junit随框架代码和实用工具附带的用户指南和文档的着重点在于解释单元测试框架的设计方法以及简单的类使用说明,而对在特定的测试框架(Junit)下如何实施单元测试,如何在项目开发的过程中更新和维护已经存在的单元测试代码没有详细的解释。因此本文档就两个着重点对Junit所附带的文档进行进一步的补充和说明,使Junit能被更多的开发团队采用,让单元测试乃至Refactoring、XP技术更好在更多的开发团队中推广。

1. 单元测试的编写原则

Junit附带文档所列举的单元测试带有一定的迷惑性,因为几乎所有的示例单元都是针对某个对象的某个方法,似乎Junit的单元测试仅适用于类组织结构的静态约束,从而使初学者怀疑Junit下的单元测试所能带来的效果。因此我们需要重新定义如何确定有价值的单元测试以及如何编写这些单元测试、维护这些单元测试,从而让更多的程序员接受和熟悉Junit下的单元测试的编写。

在Junit单元测试框架的设计时,作者一共设定了三个总体目标,第一个是简化测试的编写,这种简化包括测试框架的学习和实际测试单元的编写;第二个是使测试单元保持持久性;第三个则是可以利用既有的测试来编写相关的测试。从这三个目标可以看出,单元测试框架的基本设计考虑依然是从我们现有的测试方式和方法出发,而只是使测试变得更加容易实施和扩展并保持持久性。因此编写单元测试的原则可以从我们通常使用的测试方法借鉴和利用。

 

 回页首

 

2. 如何确定单元测试

在我们通常的测试中,一个单元测试一般针对于特定对象的一个特定特性,譬如,假定我们编写了一个针对特定数据库访问的连接池的类包实现,我们会建立以下的单元测试:

在连接池启动后,是否根据定义的规则在池中建立了相应数量的数据库连接

申请一个数据库连接,是否根据定义的规则从池中直接获得缓存连接的引用,还是建立新的连接

释放一个数据库连接后,连接是否根据定义的规则被池释放或者缓存以便以后使用

后台Housekeeping线程是否按照定义的规则释放已经过期的连接申请

如果连接有时间期限,后台Housekeeping线程是否定期释放已经过期的缓存连接

这儿只列出了部分的可能测试,但是从这个列表我们可以看出单元测试的粒度。一个单元测试基本是以一个对象的明确特性为基础,单元测试的过程应该限定在一个明确的线程范围内。根据上面所述,一个单元测试的测试过程非常类似于一个Use Case的定义,但是单元测试的粒度一般来说比Use Case的定义要小,这点是容易理解的,因为Use Case是以单独的事务单元为基础的,而单元测试是以一组聚合性很强的对象的特定特征为基础的,一般而言一个事务中会利用许多的系统特征来完成具体的软件需求。

从上面的分析我们可以得出,测试单元应该以一个对象的内部状态的转换为基本编写单元。一个软件系统就和一辆设计好的汽车一样,系统的状态是由同一时刻时系统内部的各个分立的部件的状态决定的,因此为了确定一个系统最终的行为符合我们起始的要求,我们首先需要保证系统内的各个部分的状态会符合我们的设计要求,所以我们的测试单元的重点应该放在确定对象的状态变换上。

然而需要注意的并不是所有的对象组特征都需要被编写成独立的测试单元,如何在对象组特征里筛选有价值的测试单元的原则在JUnitTest Infected: Programmers Love Writing Tests一文中得到了正确的描述,你应该在有可能引入错误的地方引入测试单元,通常这些地方存在于有特定边界条件、复杂算法以及需求变动比较频繁的代码逻辑中。除了这些特性需要被编写成独立的测试单元外,还有一些边界条件比较复杂的对象方法也应该被编写成独立的测试单元,这部分单元测试已经在Junit文档中被较好的描述和解释过了。

在基本确定了需要编写的单元测试,我们还应该问自己:编写好了这些测试,我们是否可以有把握地告诉自己,如果代码通过了这些单元测试,我们能认定程序的运行是正确的,符合需求的。如果我们不能非常的确定,就应该看看是否还有遗漏的需要编写的单元测试或者重新审视我们对软件需求的理解。通常来说,在开始使用单元测试的时候,更多的单元测试总是没有错的。

一旦我们确定了需要被编写的测试单元,接下来就应该

 

 回页首

 

3. 如何编写单元测试

在XP下强调单元测试必须由类包的编写者负责编写,这个限定对于我们设定的测试目标是必须的。因为只有这样,测试才能保证对象的运行时态行为符合需求,而仅通过类接口的测试,我们只能确保对象符合静态约束,因此这就要求我们在测试的过程中,必须开放一定的内部数据结构,或者针对特定的运行行为建立适当的数据记录,并把这些数据暴露给特定的测试单元。这也就是说我们在编写单元测试时必须对相应的类包进行修改,这样的修改也发生在我们以前使用的测试方法中,因此以前的测试标记及其他一些测试技巧仍然可以在Junit测试中改进使用。

由于单元测试的总体目标是负责我们的软件在运行过程中的正确无误,因此在我们对一个对象编写单元测试的时候,我们不但需要保证类的静态约束符合我们的设计意图,而且需要保证对象在特定的条件下的运行状态符合我们的预先设定。还是拿数据库缓冲池的例子说明,一个缓冲池暴露给其他对象的是一组使用接口,其中包括对池的参数设定、池的初始化、池的销毁、从这个池里获得一个数据连接以及释放连接到池中,对其他对象而言随着各种条件的触发而引起池的内部状态的变化是不需要知道的,这一点也是符合封装原理的。但是池对象的状态变化,譬如:缓存的连接数在某些条件下会增长,一个连接在足够长的运行后需要被彻底释放从而使池的连接被更新等等,虽然外部对象不需要明确,但是却是程序运行正确的保证,所以我们的单元测试必须保证这些内部逻辑被正确的运行。

编译语言的测试和调试是很难对运行的逻辑过程进行跟踪的,但是我们知道,无论逻辑怎么运行,如果状态的转换符合我们的行为设定,那验证结果显然是正确的,因此在对一个对象进行单元测试的时候,我们需要对多数的状态转换进行分析和对照,从而验证对象的行为。状态是通过一系列的状态数据来描述的,因此编写单元测试首先分析出状态的变化过程(状态转换图对这个过程的描述非常清晰),然后根据状态的定义确定分析的状态数据,最后是提供这些内部的状态数据的访问。在数据库连接池的例子中,我们对池实现的对象DefaultConnectionProxy的状态变换进行分析后,我们决定把表征状态的OracleConnectionCacheImpl对象公开给测试类。参见示例一

示例一

/**

 * 这个类简单的包装了oracle对数据连接缓冲池的实现。

 *

*/

public class DefaultConnectionProxy extends ConnectionProxy {

private static final String name = "Default Connection Proxy";

private static final String description = "这个类简单的包装了oracle对数据连接缓冲池的实现。";

private static final String author = "Ion-Global.com";

private static final int major_version = 0;

private static final int minor_version = 9;

private static final boolean pooled = true;

  private ConnectionBroker connectionBroker = null;

private Properties props;

private Properties propDescriptions;

private Object initLock = new Object();

// Test Code Begin...

/* 为了能够了解对象的状态变化,因此需要把表征对象内部状态变化的部分私有变量提供公共的访问接口

   (或者提供让同一个类包的访问接口),以便使测试单元可以有效地判断对象的状态转变,

    在本示例中对包装的OracleConnectionCacheImpl对象提供访问接口。

*/

OracleConnectionCacheImpl getConnectionCache() {

if (connectionBroker == null) {

throw new IllegalStateException("You need start the server first.");

}

return connectionBroker.getConnectionCache();

}

// Test Code End...

在公开内部状态数据後,我们就可以编写我们的测试单元了,单元测试的选择方法和选择尺度已经在本文前面章节进行了说明,

但是仍然需要注意的是,由于assert方法会抛出一个error,你应该在测试方法的最后集中用assert相关方法进行判断,

这样可以确保资源得到释放。

对数据库连接池的例子,我们可以建立测试类DefaultConnectionProxyTest,同时建立数个test case,如下

示例二

/**

 * 这个类对示例一中的类进行简单的测试。

 *

*/

public class DefaultConnectionProxyTest extends TestCase {

private DefaultConnectionProxy conProxy = null;

private OracleConnectionCacheImpl cacheImpl = null;

private Connection con = null;

/** 设置测试的fixture,建立必要的测试起始环境。

*/

protected void setUp() {

conProxy = new DefaultConnectionProxy();

conProxy.start();

cacheImpl = conProxy.getConnectionCache();

}

/** 对示例一中的对象进行服务启动后的状态测试,检查是否在服务启动后,

连接池的参数设置是否正确。

*/

public void testConnectionProxyStart() {

int minConnections = 0;

int maxConnections = 0;

assertNotNull(cacheImpl);

try {

minConnections = Integer.parseInt(PropertyManager.getProperty

("DefaultConnectionProxy.minConnections"));

maxConnections = Integer.parseInt(PropertyManager.getProperty

("DefaultConnectionProxy.maxConnections"));

} catch (Exception e) {

// ignore the exception

}

assertEquals(cacheImpl.getMinLimit(), minConnections);

assertEquals(cacheImpl.getMaxLimit(), maxConnections);

assertEquals(cacheImpl.getCacheSize(), minConnections);

}

/** 对示例一中的对象进行获取数据库连接的测试,看看是否可以获取有效的数据库连接,

并且看看获取连接后,连接池的状态是否按照既定的策略进行变化。由于assert方法抛出的是

error对象,因此尽可能把assert方法放置到方法的最后集体进行测试,这样在方法内打开的

资源,才能有效的被正确关闭。

*/

public void testGetConnection() {

int cacheSize = cacheImpl.getCacheSize();

int activeSize = cacheImpl.getActiveSize();

int cacheSizeAfter = 0;

int activeSizeAfter = 0;

con = conProxy.getConnection();

if (con != null) {

activeSizeAfter = cacheImpl.getActiveSize();

cacheSizeAfter = cacheImpl.getCacheSize();

try {

con.close();

} catch (SQLException e) {

}

} else {

assertNotNull(con);

}

/*如果连接池中的实际使用连接数小于缓存连接数,检查获取的新的数据连接是否

从缓存中获取,反之连接池是否建立新的连接

*/

if (cacheSize > activeSize) {

assertEquals(activeSize + 1, activeSizeAfter);

assertEquals(cacheSize, cacheSizeAfter);

} else {

assertEquals(activeSize + 1, cacheSizeAfter);

}

}

/** 对示例一中的对象进行数据库连接释放的测试,看看连接释放后,连接池的

状态是否按照既定的策略进行变化。由于assert方法抛出的是error对象,因此尽可

能把assert方法放置到方法的最后集体进行测试,这样在方法内打开的

资源,才能有效的被正确关闭。

*/

public void testConnectionClose() {

int minConnections = cacheImpl.getMinLimit();

int cacheSize = 0;

int activeSize = 0;

int cacheSizeAfter = 0;

int activeSizeAfter = 0;

con = conProxy.getConnection();

if (con != null) {

cacheSize = cacheImpl.getCacheSize();

activeSize = cacheImpl.getActiveSize();

try {

con.close();

} catch (SQLException e) {

}

activeSizeAfter = cacheImpl.getActiveSize();

cacheSizeAfter = cacheImpl.getCacheSize();

} else {

assertNotNull(con);

}

assertEquals(activeSize, activeSizeAfter + 1);

/*如果连接池中的缓存连接数大于最少缓存连接数,检查释放数据连接后是否

缓存连接数比之前减少了一个,反之缓存连接数是否保持为最少缓存连接数

*/

if (cacheSize > minConnections) {

assertEquals(cacheSize, cacheSizeAfter + 1);

} else {

assertEquals(cacheSize, minConnections);

}

}

/** 释放建立测试起始环境时的资源。

*/

protected void tearDown() {

cacheImpl = null;

conProxy.destroy();

}

public DefaultConnectionProxyTest(String name) {

super(name);

}

/** 你可以简单的运行这个类从而对类中所包含的测试单元进行测试。

*/

public static void main(String args[]) {

junit.textui.TestRunner.run(DefaultConnectionProxyTest.class);

}

}

 

当单元测试完成后,我们可以用Junit提供的TestSuite对象对测试单元进行组织,你可以决定测试的顺序,然后运行你的测试。

 

 回页首

 

4. 如何维护单元测试

通过上面的描述,我们对如何确定和编写测试有了基本的了解,但是需求总是变化的,因此我们的单元测试也会根据需求的变化不断的演变。如果我们决定修改类的行为规则,可以明确的是,我们当然会对针对这个类的测试单元进行修改,以适应变化。但是如果对这个类仅有调用关系的类的行为定义没有变化则相应的单元测试仍然是可靠和充分的,同时如果包含行为变化的类的对象的状态定义与其没有直接的关系,测试单元仍然起效。这种结果也是封装原则的优势体现。

 

 回页首

 

 

 回页首

 

关于作者

 

 申文波: 1973年出生,现于艾昂科技上海公司任资深技术顾问。在关系数据库对象建模方面有较长的工作经验,熟悉Java语言,目前从事的工作领域主要包括OOA、OOD和企业应用。您可以通过邮件 alair_china@yahoo.com.cn与他联系。

 

 

 回页首

 

对本文的评价

 

太差! (1) 需提高 (2) 一般;尚可 (3) 好文章 (4) 真棒!(5)

建议?

 

 

 

LP5800->common conversation->social

  I am satisfactory with my learning english in last week.I have 4 mornings in 7 days to get up early to  dictate the common conversation in Lp5800.So,I have finished listening the common conversation in LP5800 one time.

  I found I had forgotten the sentence in common conversation before,so need start the second time to dictate them.

  4/7 week isn't enough,it should be 7/7 week's all days to learn english.But,I am sure I am progressing.

介绍一个最好用的网络硬盘http://www.mofile.com

  我所见过的最快、最大的免费网络硬盘。长期保管30M,文件接力1G保存3天。今天晚上试了一下上传速度:33K/S,比我的虚拟空间的上传速度都快。比起发邮件来说,更是好了不知多少倍:)

  好东西。

介绍一个还不错的英文测试咨询公司的网站http://www.clarkwa

  初步看了他的一篇文章《JUnit Primer》(http://www.learndiary.com/disDiaryContentAction.do?searchDiaryID=1359&goalID=1359&naviStr=a10a60a01328),还不错,在他的网站上还有一些公开的文章,可以看。

  作者(公司拥有者)介绍:

About the Author

Mike Clark is a consultant, author, speaker, and most important, he's an experienced programmer. He is the developer of JUnitPerf, an open source collection of JUnit extensions for continuous performance testing. He also wrote the JUnit FAQ and serves as its editor for the JUnit community. Mike frequently writes and speaks on test-driven development using JUnit. He helps teams build better software faster through his company, Clarkware Consulting, Inc.