| 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所有记录的页面。 |