At line 152 added 321 lines. |
|
你需要为src/dao/**/Constants.java添加__PERSON_KEY__变量,这个"personForm"与struts-config.xml中的名称相对应。 |
|
[{Java2HtmlPlugin |
|
/** |
* The request scope attribute that holds the person form. |
*/ |
public static final String PERSON_KEY = "personForm"; |
}] |
|
如果你尝试运行测试,你会得到几个NoSuchMethodErrors,所以,让我们定义PersonAction的edit、save和delete方法。 |
|
!!创建PersonAction [#4] |
|
在src/web/**/action,创建PersonAction.java,有下面的内容: |
|
[{Java2HtmlPlugin |
|
package org.appfuse.webapp.action; |
|
import javax.servlet.http.HttpServletRequest; |
import javax.servlet.http.HttpServletResponse; |
|
import org.apache.struts.action.ActionForm; |
import org.apache.struts.action.ActionForward; |
import org.apache.struts.action.ActionMapping; |
import org.apache.struts.action.ActionMessage; |
import org.apache.struts.action.ActionMessages; |
|
import org.appfuse.model.Person; |
import org.appfuse.service.PersonManager; |
import org.appfuse.webapp.form.PersonForm; |
|
/** |
* @struts.action name="personForm" path="/editPerson" scope="request" |
* validate="false" parameter="method" input="mainMenu" |
*/ |
public final class PersonAction extends BaseAction { |
|
public ActionForward cancel(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, |
HttpServletResponse response) |
throws Exception { |
return mapping.findForward("mainMenu"); |
} |
|
public ActionForward delete(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, |
HttpServletResponse response) |
throws Exception { |
if (log.isDebugEnabled()) { |
log.debug("Entering 'delete' method"); |
} |
|
ActionMessages messages = new ActionMessages(); |
PersonForm personForm = (PersonForm) form; |
|
// Exceptions are caught by ActionExceptionHandler |
PersonManager mgr = (PersonManager) getBean("personManager"); |
mgr.removePerson(personForm.getId()); |
|
messages.add(ActionMessages.GLOBAL_MESSAGE, |
new ActionMessage("person.deleted", |
personForm.getFirstName() + ' ' + |
personForm.getLastName())); |
|
// save messages in session, so they'll survive the redirect |
saveMessages(request.getSession(), messages); |
|
return mapping.findForward("mainMenu"); |
} |
|
public ActionForward edit(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, |
HttpServletResponse response) |
throws Exception { |
if (log.isDebugEnabled()) { |
log.debug("Entering 'edit' method"); |
} |
|
PersonForm personForm = (PersonForm) form; |
|
// if an id is passed in, look up the user - otherwise |
// don't do anything - user is doing an add |
if (personForm.getId() != null) { |
PersonManager mgr = (PersonManager) getBean("personManager"); |
Person person = mgr.getPerson(personForm.getId()); |
personForm = (PersonForm) convert(person); |
updateFormBean(mapping, request, personForm); |
} |
|
return mapping.findForward("edit"); |
} |
|
public ActionForward save(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, |
HttpServletResponse response) |
throws Exception { |
if (log.isDebugEnabled()) { |
log.debug("Entering 'save' method"); |
} |
|
// Extract attributes and parameters we will need |
ActionMessages messages = new ActionMessages(); |
PersonForm personForm = (PersonForm) form; |
boolean isNew = ("".equals(personForm.getId())); |
|
if (log.isDebugEnabled()) { |
log.debug("saving person: " + personForm); |
} |
|
PersonManager mgr = (PersonManager) getBean("personManager"); |
Person person = (Person) convert(personForm); |
mgr.savePerson(person); |
|
// add success messages |
if (isNew) { |
messages.add(ActionMessages.GLOBAL_MESSAGE, |
new ActionMessage("person.added", |
personForm.getFirstName() + " " + |
personForm.getLastName())); |
|
// save messages in session to survive a redirect |
saveMessages(request.getSession(), messages); |
|
return mapping.findForward("mainMenu"); |
} else { |
messages.add(ActionMessages.GLOBAL_MESSAGE, |
new ActionMessage("person.updated", |
personForm.getFirstName() + " " + |
personForm.getLastName())); |
saveMessages(request, messages); |
|
return mapping.findForward("edit"); |
} |
} |
} |
}] |
|
你会注意到以上的代码有许多对''convert''的调用来转化PersonForm到Person,这个转化''convert''方法在BaseAction.java(调用ConvertUtil.convert())中,并且使用[BeanUtils.copyProperties|http://jakarta.apache.org/commons/beanutils/api/org/apache/commons/beanutils/BeanUtils.html#copyProperties(java.lang.Object,%20java.lang.Object)] 来实现POJOs → ActionForms和ActionForms → POJOs的转化。 |
|
;:''如果你运行Eclipse,你需要对project运行"refresh"来显示PersonForm,它在build/web/gen目录中,这个目录必须是project的源目录之一,这是Eclipse看到和导入PersonForm的唯一方式,因为它是通过XDoclet产生并且不在通常的源目录中,你可以在build/web/gen/org/appfuse/webapp/form/PersonForm.java找到它。'' |
|
;:''在[BaseAction|http://raibledesigns.com/downloads/apptracker/api/org/appfuse/webapp/action/BaseAction.java.html]里你可以定义自己的转换器(例如 [DateConverter|http://raibledesigns.com/downloads/apptracker/api/org/apptracker/util/DateConverter.java.html]),因此BeanUtils.copyProperties能够知道Strings → Objects的转化。如果你的POJOs存在Lists(例如 父子关系),你需要使用{{convertLists(java.lang.Object)}}方法手工完成这个转化。'' |
|
现在你需要添加''edit'' forward和''savePerson'' action-mapping,这两个早就在PersonActionTest中提到,为此,可以在PersonAction.java添加一些额外的XDoclet标签,在类的声明前面加入这些注释,你一定已经有了''editPerson'' action-mapping的XDoclet标签,我在这里展示你会在类中看到的所有的XDoclet标签。 |
|
[{Java2HtmlPlugin |
|
/** |
* @struts.action name="personForm" path="/editPerson" scope="request" |
* validate="false" parameter="method" input="mainMenu" |
* |
* @struts.action name="personForm" path="/savePerson" scope="request" |
* validate="true" parameter="method" input="edit" |
* |
* @struts.action-forward name="edit" path="/WEB-INF/pages/personForm.jsp" |
*/ |
public final class PersonAction extends BaseAction { |
}] |
|
action-mappings中''editPerson''和''savePerson''主要区别是''savePerson''的XDoclet标签的validation标记是true(见validation="true"),注意"input"属性必须指向到一个forward,而不是个路径(例如/editPerson.html),如果你希望edit同save一样是是用用save的路径也是可以的,只要确保validate="false",然后在你的"save"方法中,你可以执行form.validate()来处理错误。 |
你一定注意到了这里是用PersonManager的方式与PersonManagerTest中的一样,PersonAction和PersonManagerTest都是PersonManagerImpl的''客户'',这很有意义。 |
|
本教程几乎所有的事情都搞定了,让我们来运行这些测试! |
|
!!运行PersonActionTest [#5] |
|
|
如果你查看PersonActionTest,你会发现所有的测试依赖于数据库中一条id=1的记录(testRemove依赖于id=2),所以要添加那些样本数据(metadata/sql/sample-data.xml),我会添加到底部,顺序并不重要(目前),因为它们互相并不存在依赖关系。 |
|
{{{ |
<table name='person'> |
<column>id</column> |
<column>first_name</column> |
<column>last_name</column> |
<row> |
<value>1</value> |
<value>Matt</value> |
<value>Raible</value> |
</row> |
<row> |
<value>2</value> |
<value>James</value> |
<value>Davidson</value> |
</row> |
</table> |
}}} |
|
DBUnit会在执行测试前加载这些数据,所以这些记录PersonActionTest可以得到。 |
|
如果现在你执行__ant test-web -Dtestcase=PersonAction__ - 所有事情会按计划执行,在作尝试前请确定Tomcat已经运行。 |
|
%%(color:green)BUILD SUCCESSFUL\\ |
Total time: 1 minute 21 seconds%% |
|
!!清理JSP来使它更好看 [#6] |
|
让我们开始清理personForm.jsp,修改<html:form>的''action''为"savePerson",这保证验证起作用,同时修改''focus''属性,从focus=""改为focus="firstName",这样在页面打开后光标会落到firstName字段(使用JavaScript)。 |
|
现在你如果执行__ant db-load deploy__,启动Tomcat,然后在你的浏览器输入[http://localhost:8080/appfuse/editPerson.html?id=1],你会看到如下信息: |
|
%%(border: 1px solid black; margin: 0 auto; height: 166px; width: 337px) |
[personForm-final.png] |
%% |
|
%%note __注意:如果你修改了''web''中的任何文件,请使用__deploy-web__,否则,使用__deploy__来编译和部署。%% |
|
最后,为了使页面更加的用户友好,你也许希望为用户在form上面添加一段信息,只需在personForm.jsp页的顶端添加(使用 <fmt:message>)。 |
|
!![[可选的] 创建Canoo WebTests来测试模拟浏览器的actions [#7] |
本教程最后的步骤(可选的)是创建[Canoo WebTest|http://webtest.canoo.com]来测试JSPs。 |
|
;:''我说是可选的,是因为你可以通过浏览器运行同样的测试。'' |
|
你可以使用如下的URLs来测试不同actions的adding、editing和saving。 |
|
* Add - [http://localhost:8080/appfuse/editPerson.html]. |
* Edit - [http://localhost:8080/appfuse/editPerson.html?id=1] (make sure and run __ant db-load__ first). |
* Delete - [http://localhost:8080/appfuse/editPerson.html?method=Delete&id=1] (or edit and click on the Delete button). |
* Save - Click [edit|http://localhost:8080/appfuse/editPerson.html?id=1] and then click the Save button. |
|
Canoo测试非常的灵活,只需要配置XML文件,为了测试add、edit、save和delete,打开test/web/web-tests.xml添加如下的XML,你一定注意到这些片断的target名称是''PersonTests'',会运行所有相关的测试。 |
|
;:''我使用CamelCase的target命名方式(vs. 传统的小写,下划线分割的方式)是因为当拼写''-Dtestcase=Name''时,我会经常习惯于使用CamelCase方式来命名我的JUnit Tests。'' |
|
[{Java2HtmlPlugin |
|
<!-- runs person-related tests --> |
<target name="PersonTests" |
depends="EditPerson,SavePerson,AddPerson,DeletePerson" |
description="Call and executes all person test cases (targets)"> |
<echo>Successfully ran all Person JSP tests!</echo> |
</target> |
|
<!-- Verify the edit person screen displays without errors --> |
<target name="EditPerson" |
description="Tests editing an existing Person's information"> |
<webtest name="editPerson"> |
&config; |
<steps> |
&login; |
<invoke description="click Edit Person link" url="/editPerson.html?id=1"/> |
<verifytitle description="we should see the personDetail title" |
text=".*${personDetail.title}.*" regex="true"/> |
</steps> |
</webtest> |
</target> |
|
<!-- Edit a person and then save --> |
<target name="SavePerson" |
description="Tests editing and saving a user"> |
<webtest name="savePerson"> |
&config; |
<steps> |
&login; |
<invoke description="click Edit Person link" url="/editPerson.html?id=1"/> |
<verifytitle description="we should see the personDetail title" |
text=".*${personDetail.title}.*" regex="true"/> |
<setinputfield description="set lastName" name="lastName" value="Canoo"/> |
<clickbutton label="Save" description="Click Save"/> |
<verifytitle description="Page re-appears if save successful" |
text=".*${personDetail.title}.*" regex="true"/> |
</steps> |
</webtest> |
</target> |
|
<!-- Add a new Person --> |
<target name="AddPerson" |
description="Adds a new Person"> |
<webtest name="addPerson"> |
&config; |
<steps> |
&login; |
<invoke description="click Add Button" url="/editPerson.html"/> |
<verifytitle description="we should see the personDetail title" |
text=".*${personDetail.title}.*" regex="true"/> |
<setinputfield description="set firstName" name="firstName" value="Abbie"/> |
<setinputfield description="set lastName" name="lastName" value="Raible"/> |
<clickbutton label="${button.save}" description="Click button 'Save'"/> |
<verifytitle description="Main Menu appears if save successful" |
text=".*${mainMenu.title}.*" regex="true"/> |
<verifytext description="verify success message" |
text="Information for <strong>Abbie Raible</strong> has been added successfully."/> |
</steps> |
</webtest> |
</target> |
|
<!-- Delete existing person --> |
<target name="DeletePerson" |
description="Deletes existing Person"> |
<webtest name="deletePerson"> |
&config; |
<steps> |
&login; |
<invoke description="click Edit Person link" url="/editPerson.html?id=1"/> |
<clickbutton label="${button.delete}" description="Click button 'Delete'"/> |
<verifytitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/> |
<verifytext description="verify success message" |
text="Information for <strong>Matt Canoo</strong> has been deleted successfully."/> |
</steps> |
</webtest> |
</target> |
}] |
|
完成添加后,你一定可以在Tomcat运行时运行__ant test-canoo -Dtestcase=PersonTests__,或者使用__ant test-jsp -Dtestcase=PersonTests__让Ant为你启动/关闭Tomcat,为了在所有的Canoo测试中包括PersonTests,把它作为"run-all-tests"的依赖target。 |
|
你必须注意到Canoo没有客户端的日志,如果你期望看到一些信息,你可以在每一个target的</canoo>和</target>之间添加如下代码。 |
|
{{{<loadfile property="web-tests.result" |
srcFile="${test.dir}/data/web-tests-result.xml"/> |
<echo>${web-tests.result}</echo>}}} |
|
%%(color:green)BUILD SUCCESSFUL\\ |
Total time: 11 seconds%% |
|
|
---- |
|
''Next Up:'' __Part IV:__ [添加验证和列表页面|ValidationAndList] - 给personForm的firstName和lastName字段添加必添的验证逻辑和显示person所有记录的页面。 |