<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>hypercube1024</title>
    <description>about:Mozilla

巨獸終傾，異教徒歡慶。但並未全盤皆失；自灰燼中，神鳥升起。神鳥俯視異教徒眾，施火與雷。獸已再生，力氣全復；瑪門信徒，恐懼匍匐。</description>
    <link>http://hypercube1024.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>Seam 使用心得（二）接口实现</title>
        <author>hypercube1024</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://hypercube1024.javaeye.com">hypercube1024</a>&nbsp;
          链接：<a href="http://hypercube1024.javaeye.com/blog/149527" style="color:red;">http://hypercube1024.javaeye.com/blog/149527</a>&nbsp;
          发表时间: 2007年12月19日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          Java里提倡使用面向接口编程以减弱组件之间的耦合，在上一篇里已经定义了<br /><pre name="code" class="java">@Local
public interface Register {
	public String register();
}</pre><br />这一节里面将实现这个接口来通过测试。<br /><br />现在应用系统有很多良好的分层方法，但是我还是喜欢Evans的领域驱动设计的方法。<br />1,视图层将使用RichFaces。<br />2,使用无状态SessionBean作为Facade对领域逻辑进行封装。<br />3,领域层实现核心业务逻辑，主要包括有实体，值对象，仓库，服务等。<br /><br />源文件目录结构大致如下：<br />src<br />  |----action  (SLSB)<br />  |----model  (Service, Entity, Repository, Vo)<br />  |----test <br />view<br />  |----css<br />  |----js<br />……<br /><br />经过简单的分析model层向facade暴露UserView接口<br /><pre name="code" class="java">package org.qpt.domain.user;
import org.qpt.domain.user.User.RegState;
/**
 * 用户接口
 * @author xiaoqiu
 */
public interface UserView {
    String getConfirmPwd();
    String getEmail();
    String getName();
    String getPwd();
    /**
     * 注册一个新用户
     * @param ur 用户仓库接口
     * @return 注册结果状态码(共5种状态，上一节已经提到过)
     */
    RegState register(UserRepository ur);
    void setConfirmPwd(String confirmPwd);
    void setEmail(String email);
    void setName(String name);
    void setPwd(String pwd);
}</pre><br /><br />注：因为EJB2严重的依赖于容器的基础设施，如果在实体bean中包含业务逻辑，将很难进行测试，所以实体bean只能作为数据容器，而背离面向对象的分析和设计方法。但是EJB3已经可以对实体进行透明持久化，并且很容易在容器外进行测试，所以就用User实体实现UserView接口，这里用户注册的并没有跨越多个实体，只涉及用户实体，所以包含了业务方法register的User类代码如下：<br /><br /><pre name="code" class="java">/**
 * 用户实体
 * 
 * @author xiaoqiu
 */
@Entity
@Name("user")
@Scope(SESSION)
@Table(name = "users")
public class User implements Serializable, UserView {

    private static final long serialVersionUID = 3836699581343986461L;
    @Id
    @NotNull
    @Length(min = 6, max = 15)
    private String name;
    @NotNull
    @Length(min = 6, max = 15)
    private String pwd;
    @Transient
    private String confirmPwd;
    @NotNull
    @Email
    private String email;
    @Transient
    private String confirmEmail;

    public enum RegState {
        EXISTS_EMAIL, EXISTS_NAME, PWD_NOT_EQ_IN_TWICE, EMAIL_NOT_EQ_IN_TWICE, SUCCESS
    }

    @Override
    public RegState register(UserRepository ur) {
        if (!pwd.equals(confirmPwd)) {
            return PWD_NOT_EQ_IN_TWICE;
        } else if (!email.equals(confirmEmail)) {
            return EMAIL_NOT_EQ_IN_TWICE;
        } else if (ur.findByProperty("name", name)) {
            return EXISTS_NAME;
        } else if (ur.findByProperty("email", email)) {
            return EXISTS_EMAIL;
        } else {
            ur.save(this);
            return SUCCESS;
        }
    }

……
get&set method
}</pre><br />考虑到视图层RichFaces和hibernate验证结合可以进行很简单的零脚本ajax验证，所以本例子里面使用了hibernate验证。<br />由于register方法需要用的UserRepository来访问数据库，所以现在来看一下UserRepository接口。通过隔离基础设施，这就是GOF设计模式中著名的Strategy模式，由于Java语言想要在实体中注入UserRepository在技术上存在困难，所以把UserRepository作为方法的参数传递我个人认为也是一种较为简洁的方法，而且通过接口的隔离不会和基础设施产生强烈的耦合，UserRepository代码如下：<br /><br /><pre name="code" class="java">/**
 * 用户仓库接口
 * @author xiaoqiu
 */
@Local
public interface UserRepository {
    void save(User user);
    User findByName(String name);
    List&lt;User> findAllUser();
    /**
     * 找出该属性是否存在当前值
     * @param name 属性名
     * @param value 属性值
     * @return 该属性存在这个值返回true，否则返回false
     */
    boolean findByProperty(String name, String value);
}</pre><br /><br />在本例里面UserRepository的实现使用的JPA，这个接口的实现相当容易所以它的实现类UserRepositoryJPAImp代码就不贴出来了，可以直接看提供的源码。<br /><br />model层的接口已经实现完毕，接下来实现facade，代码如下：<br /><pre name="code" class="java">@Stateless
@Name("register")
public class RegisterBean implements Register {

    @Logger
    private Log log;
    @In
    FacesMessages facesMessages;
    @In
    private UserView user;
    @In("userRepository")
    UserRepository ur;

    public String register() {
        RegState state = user.register(ur);
        switch (state) {
            case PWD_NOT_EQ_IN_TWICE:
                log.info("the register state is " + state);
                facesMessages.addFromResourceBundle("myapp.register.pwd.noteq");
                break;
            case EMAIL_NOT_EQ_IN_TWICE:
                log.info("the register state is " + state);
                facesMessages.addFromResourceBundle("myapp.register.email.noteq");
                break;
            case EXISTS_NAME:
                log.info("the register state is " + state);
                facesMessages.addFromResourceBundle("myapp.register.name.exists");
                break;
            case EXISTS_EMAIL:
                log.info("the register state is " + state);
                facesMessages.addFromResourceBundle("myapp.register.email.exists");
                break;
            case SUCCESS:
                log.info("register a new user #{user.name}" + "\nthe register state is " + state);
                facesMessages.addFromResourceBundle("myapp.register.success");
                break;
        }
        return state.toString();
    }
}</pre><br />这里facade是很薄的一层，不能包含任何业务逻辑，它主要根据model返回的状态是向显示层发送信息，使用facesMessages组件就可以很容易向Richfaces发送ajax验证的响应信息，并且事务处理统一在facade层进行，由于使用EJB，所以就可以使用容器提供的事务管理。<br /><br />上述类的结构如下图所示：<br />facade: Register-->RegisterBean-->UserView&UserRepository<br />domain:UserView-->User(Eneity)&lt;-->UserRepository-->UserRepositoryJPAImp<br />至此为止，注册用户所需要的全部接口都已经实现，现在就可以用seam test命令来运行上一节写好的测试代码了。
          <br/>
          <span style="color:red;">
            <a href="http://hypercube1024.javaeye.com/blog/149527#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 19 Dec 2007 20:59:59 +0800</pubDate>
        <link>http://hypercube1024.javaeye.com/blog/149527</link>
        <guid>http://hypercube1024.javaeye.com/blog/149527</guid>
      </item>
      <item>
        <title>Seam 使用心得（一）测试先行</title>
        <author>hypercube1024</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://hypercube1024.javaeye.com">hypercube1024</a>&nbsp;
          链接：<a href="http://hypercube1024.javaeye.com/blog/149377" style="color:red;">http://hypercube1024.javaeye.com/blog/149377</a>&nbsp;
          发表时间: 2007年12月19日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          最近开始关注Seam，正值Seam2.0发布之际我也下载来试用，还是准备先做一个用户注册的例子。由于一直喜欢TDD所以先从测试用例开始写起吧。<br />首先使用seam-gen生成一个项目目录，然后再进行必要的配置。<br />1)配置数据源，我使用的hsqldb内存数据库，这样比较方便<br />在myApp-test-ds.xml这样进行配置<br /><pre name="code" class="xml">&lt;datasources>
   &lt;local-tx-datasource>
        &lt;jndi-name>myAppTestDatasource&lt;/jndi-name>
        &lt;connection-url>jdbc:hsqldb:.&lt;/connection-url>
        &lt;driver-class>org.hsqldb.jdbcDriver&lt;/driver-class>
        &lt;user-name>sa&lt;/user-name>
        &lt;password>&lt;/password>
    &lt;/local-tx-datasource>
&lt;/datasources></pre><br /><br />persistence-test.xml中的配置<br /><pre name="code" class="xml">&lt;persistence-unit name="myApp" transaction-type="JTA">
      &lt;provider>org.hibernate.ejb.HibernatePersistence&lt;/provider>
      &lt;jta-data-source>java:/myAppTestDatasource&lt;/jta-data-source>
      &lt;properties>
         &lt;property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
         &lt;property name="hibernate.hbm2ddl.auto" value="create-drop"/>
         &lt;property name="hibernate.show_sql" value="true"/>
         &lt;property name="hibernate.format_sql" value="false"/>
         &lt;property name="hibernate.cache.use_second_level_cache" value="false"/>
         &lt;property name="jboss.entity.manager.factory.jndi.name" value="java:/myAppEntityManagerFactory"/>
      &lt;/properties>
&lt;/persistence-unit></pre><br /><br />这样测试数据源的配置就完成了，当然还要把hsqldb.jar放在测试的classpath中。<br /><br />2)配置测试用例<br />在测试文件夹中创建一个用于配置测试用例类的xml，我的就是在test/org/qpt/test/user里面建立一个RegisterTest.xml，内容如下：<br /><pre name="code" class="xml">&lt;suite name="Register Tests" verbose="2" parallel="false">
    &lt;test name="Register Test">
	&lt;!--配置jndi数据源，用于数据库测试-->
        &lt;parameter name="datasourceJndiName" value="java:/myAppTestDatasource"/>
        &lt;classes>
            &lt;class name="org.qpt.test.user.RegisterTest" />
        &lt;/classes>
    &lt;/test>
&lt;/suite></pre><br /><br />3)<span style="color: red">检查你的build.xml中test任务是否正确的拷贝了测试所有需要用到的jar包和xml文件</span>,我的build.xml是用seam-gen自动生成的，有些文件例如dataset.xml模拟数据库的标准数据的文件就没有被自动拷贝到相应的目录导致运行测试就报异常，我为此郁闷了好几天，网上一直找不到答案，结果静下心仔细研究了一下才发现build.xml的问题-_-!，所以我在build.xml加入了如下任务：<br /><pre name="code" class="xml">&lt;copy todir="${test.dir}">
            &lt;fileset dir="${src.test.dir}">
                &lt;exclude name="**/*.java" />
            &lt;/fileset>
&lt;/copy>
&lt;copy tofile="${test.dir}/myApp-ds.xml"
              file="${basedir}/resources/myApp-test-ds.xml"
              overwrite="true"/></pre><br /><br />4)编写测试用例<br />一般来说用户注册要调用一个register()的业务方法，所以我的无状态session bean接口如下：<br /><br /><pre name="code" class="java">package org.qpt.facade;
import javax.ejb.Local;

@Local
public interface Register {

    /**
     * 注册一个新用户
     * @return 状态码有5种情况：1,注册成功(SUCCESS)
     * 2,两次输入的密码不同(PWD_NOT_EQ_IN_TWICE)
     * 3,两次输入的email不同(EMAIL_NOT_EQ_IN_TWICE)
     * 4,用户名已经在数据库里面存在(EXISTS_NAME)
     * 5,email已经在数据库里面存在(EXISTS_EMAIL)
     */
    public String register();
}</pre><br /><br />返回的状态码可以用enum来实现，如：<br /><pre name="code" class="java">public enum RegState {
        EXISTS_EMAIL, EXISTS_NAME, PWD_NOT_EQ_IN_TWICE, EMAIL_NOT_EQ_IN_TWICE, SUCCESS
}</pre><br />现在就可以在org.qpt.test.user.RegisterTest类里面开始编写测试用例了，但是由于使用了ejb所以现在要用到嵌入式jboss，要把jboss-embedded-all.jar放到测试的classpath中。(<span style="color: red">注意：根据官方文档的说明，嵌入式jboss只能在jdk1.5下面使用，如果使用的jdk1.6将报异常，但是经过搜索官方网站已经发现了解决方案，那就是要下载一个新的org.jboss.metadata.spi.signature.Signature类重新编译后覆盖原始的，这个类的源文件在最后附上</span>)<br /><br />下面的代码是测试代码的一部分，完整的代码应该验证register方法返回的5种状态 ：<br /><pre name="code" class="java">public class RegisterTest extends DBUnitSeamTest {// 1
@Override
    protected void prepareDBUnitOperations() {
        beforeTestOperations.add(new DataSetOperation("org/qpt/test/user/RegisterDataSet.xml")); // 2
    }
	......
@Test
    public void testCorrectCase() throws Exception { //3
        new FacesRequest() {

            @Override
            protected void updateModelValues() {
                setValue("#{user.name}", "qwerty");
                setValue("#{user.pwd}", "123456");
                setValue("#{user.confirmPwd}", "123456");
                setValue("#{user.email}", "qwerty@123.com");
                setValue("#{user.confirmEmail}", "qwerty@123.com");
            }

            @Override
            protected void processValidations() {
                validateValue("#{user.name}", "qwerty");
                validateValue("#{user.pwd}", "123456");
                validateValue("#{user.confirmPwd}", "123456");
                validateValue("#{user.email}", "qwerty@123.com");
                validateValue("#{user.confirmEmail}", "qwerty@123.com");
                assert !isValidationFailure();
            }

            @Override
            protected void invokeApplication() {
                assert invokeMethod("#{register.register}").equals("SUCCESS");
            }
        }.run();
    }
  @Test
    public void testPwdIllegaCase() throws Exception { 
        new FacesRequest() {

            @Override
            protected void updateModelValues() {
                setValue("#{user.name}", "qwerty");
                setValue("#{user.pwd}", "654321");
                setValue("#{user.confirmPwd}", "123456");
                setValue("#{user.email}", "qwerty@123.com");
                setValue("#{user.confirmEmail}", "qwerty@123.com");
            }

            @Override
            protected void invokeApplication() {
                assert invokeMethod("#{register.register}").equals("PWD_NOT_EQ_IN_TWICE");
            }
        }.run();
    }
	......

@Test
    public void testNameExistsCase() throws Exception {
        new FacesRequest() {

            @Override
            protected void updateModelValues() {
                setValue("#{user.name}", "hypercube");
                setValue("#{user.pwd}", "654321");
                setValue("#{user.confirmPwd}", "654321");
                setValue("#{user.email}", "qwerty@124.com");
                setValue("#{user.confirmEmail}", "qwerty@124.com");
            }

            @Override
            protected void invokeApplication() {
                assert invokeMethod("#{register.register}").equals("EXISTS_NAME");
            }
        }.run();
    }

	......

}</pre><br />1,因为用户注册肯定要操作数据库，所以我继承了DBUnitSeamTest，这样就可以方便的使用dbunit和hsqldb内存数据库进行测试了。<br />2,继承DBUnitSeamTest就要实现他的一个抽象方法protected void prepareDBUnitOperations()，主要是使用标准数据初始化数据库。RegisterDataSet.xml里面就是定义了表结构和标准数据，我的users表内容如下：<br /><pre name="code" class="xml">&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;dataset>
    &lt;users name="hypercube" pwd="123456" email="hypercube@123.com" />
&lt;/dataset></pre><br />如果你被有指定其他的操作，DataSetOperation默认使用DatabaseOperation.CLEAN_INSERT作为一个构造参数。这个例子首先清空定义在BaseData.xml中所有的表然后插入所有声明在BaseData.xml中的行在@test方法每次调用之前。<br />3,这个方法模拟了JSF请求的生命周期包括初始化模型的值，验证模型属性的合法性(配合hibernate验证)，调用register()方法，然后通过assert验证方法是否正确的执行。
          <br/>
          <span style="color:red;">
            <a href="http://hypercube1024.javaeye.com/blog/149377#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 19 Dec 2007 12:15:08 +0800</pubDate>
        <link>http://hypercube1024.javaeye.com/blog/149377</link>
        <guid>http://hypercube1024.javaeye.com/blog/149377</guid>
      </item>
  </channel>
</rss>