引言
前段时间对我们系统进行了微服务化拆分,最终出现几个单独的纯接口工程(没有web界面);最近又在搞一个基于国际化的纯接口转换工程。这些工程都有一个共同的特点,就是没有web界面,只是单纯的对外提供服务。没有界面,对应研发人员来说,很难进行自测。
以前我们研发的自测方式无非就两种:1、把接口工程部署到测试环境,在调用方的测试环境页面上进行测试;2、自己开发一个servlet的测试界面,进行测试。第一种方式,无法进行断点联调;第二种方式工作量大,需要为每个接口开发单独的页面,但这些页面对业务来说又没啥用。
基于以上原因,准备通过JUnit+spring bean的装配方式,搭建一个适用于纯接口工程的测试demo工程 给新同事作为参考。简单的说最终的效果就是要求不启动接口工程,采用非侵入的方式,就可以实现JUnit测试方法调用。这里所谓的非侵入,指的是不要影响业务代码。
本章所有示例代码,都已上传GitHub,地址详见文章结尾。
spring bean装配方式
由于我们的接口工程都是采用的spring作为Bean容器搭建的,要想使用Junit单元测试,就必须把所有相关的bean先进行装配,在测试方法调用前 被注入到spring容器。也就是说要为接口工程实现Junit单元测试,最主要的就是要自己实现bean的装配。这里有一定的工作量,所以很多系统都不愿去做。但如果设计得好,形成一个模式,就可以在各个接口工程中复用。
首先我们来分析下Spring提供的三种bean装配机制:
1、显式XML装配:在XML中进行显示的配置。这种方式一般是用在对jdbc连接池的配置,以及外部依赖接口的配置。还有一些老系统采用的老版本的spring,这些老系统基本都是采用的XMl配置的方式。
2、隐式自动装配:隐式的bean发现机制和自动装配。这是spring目前比较推崇的方式,目前对于我们内部能控制的业务bean都是采用的这种自动装配方式。但对于依赖参数或者外部bean,无法进行自动装配,我们系统一般采用的是第1种XML配置方式。其实这种情况spring更推崇我们使用第3种方式。
3、显式java装配:在java中进行显示的配置。这种方式在我们系统中目前基本没有使用,但相比第1种方式会更加灵活,spring也推荐我们使用这种方式。
由于不同的适用场景,以及不同开发人员的习惯,我们的接口工程中可能同时存在这三类装配方式。我们的首要工作就是要在执行JUnit单元测试方法之前,把这些通过不同装配方式的bean自动注入到容器。下面分别对整合这三类方式进行讲解
整合“隐式自动装配”
Spring的隐式自动装配有两种形式:java、xml,java方式比较灵活可以分为三种,对应隐式自动装配的方式大致如下:
下面我们分别对每种方式的使用简单讲解,再运用到Junit单元测试创建中。
1、基于java注解:@ComponentScan标记,标记在扫描类上(非业务类)。三种典型的使用方式:
a、@ComponentScan不带参数:会扫描该被标记类根目录、以及所有子目录下bean类,并把扫描到的所有包含@Component标记的bean类自动装配并注入容器。这种方式侵入性,会在业务代码目录下创建一个扫描类,尽量避免使用,如下的UserServiceConfig类,只是为了扫描使用,类体为空:
@ComponentScan public class UserServiceConfig { }
所在目录为:
所在的目录中UserService为接口类跳过,子目录中UserServiceImpl类被Component标记,会被扫描到,进行自动装配,UserServiceImpl代码如下:
@Component("us") public class UserServiceImpl implements UserService { @Autowired(required = false) private UserDao userDao; @Override public void add() { if (userDao != null){ userDao.add(); } System.out.println("service层:用户添加成功"); } }
在test包(不会被maven打入部署包中)创建Junit测试代码类UserServiceTest,代码如下:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=UserServiceConfig.class) public class UserServiceTest { @Autowired private UserService us; @Test public void usNullTest(){ Assert.notNull(us); us.add(); } }
@RunWith(SpringJUnit4ClassRunner.class),指定使用Junit4与spring一起使用。
@ContextConfiguration(classes=UserServiceConfig.class),指定spring自动装配路径为UserServiceConfig的跟目录,及其子目录。
执行Junit的usNullTest方法,打印信息为:
service层:用户添加成功
说明UserServiceImpl自动装配成功,但是它依赖的UserDao没有被注入。
类似的我们可以在UserDao所在的根目录创建一个UserDaoConfig,并标记为@ComponentScan。
把@ContextConfiguration(classes={UserServiceConfig.class,UserDaoConfig.class})添加到junit测试类中:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={UserServiceConfig.class,UserDaoConfig.class}) public class UserServiceTest { @Autowired private UserService us; @Test public void usNullTest(){ Assert.notNull(us); us.add(); } }
再次执行usNullTest测试方法,打印信息为:
dao层:用户添加成功 service层:用户添加成功
说明UserDao被自动装配到UserServiceImpl中,并注入到容器。该方法的Junit单元测试方法创建并测试通过,并无需部署和启动程序就可以完成测试。这里的add测试方法比较简单,正常的业务,可以还需手动传入各种不同的参数,对该方法进行测试。
但这种方法有个弊端,就是需要在每个业务bean跟目录下去创建一个配置扫描类,对业务有侵入性,而且创建配置扫描类多个也非常麻烦。在创建Junit单元测试时,你可以在test包中创建一个跟业务包相同的包路径,并把扫描类放到该路径下,可以减少侵入性,比如上述UserServiceConfig扫描类可以这样创建:
但各个子工程模块test包中的代码是彼此不可见的,所有还是有一定局限。
b、@ComponentScan(basePackages = {"com.xx1","com.xx2","com.xx3”}) 带basePackages参数,采用这种配置方式,可以完全做到非侵入式:扫描类可以创建在test包中,由Packages指定需要扫描的路径。这是我个人非常建议的方式,具体操作只需要在test包中创建一个PackageScaner类(业务代码包中不会再有扫描类),代码如下:
@Configuration @ComponentScan("com.sky.locale") public class PackageAllScaner { }
再创建一个全面的Junit测试类(不建议一个系统就建一个测试类,实际根据具体业务进行拆分)AllAutoServiceTest,代码如下:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=PackageAllScaner.class) public class AllAutoServiceTest { @Autowired private UserService us; @Autowired private ProductService ps; @Autowired private OrderService os; @Test public void usNullTest(){ Assert.notNull(us); us.add(); Assert.notNull(ps); ps.add(); Assert.notNull(os); os.add(); } }
运行测试方法,打印结果如下:
dao层:用户添加成功 service层:用户添加成功 dao层:商品添加成功 service层:商品添加成功 dao层:订单添加成功 service层:订单添加成功
说明所有包下的自动装配都注入成功并测试通过,实际开发中不建议把多个测试写在一个测试方法,根据具体业务调整。
C、@ComponentScan(basePackageClasses = {xxx1.class, xxx2.class })指定basePackageClasses参数:这种方式可以扫描指定xxx1、xxx2类所在的目录及其子目录下 被@Component标记bean,并进行自动装配。相比第二种方式,该扫描类也可以创建在test包中,在一定程度上没有侵入性。但如果需要扫描的目录下没有类,就需要自己创建一个空类作为基准,个人不是很推荐。如果一定要创建:可以在test包下创建以一个跟业务包完全相同的路径,并在该路径下创建扫描类。具体使用方式:
扫描类:
@ComponentScan(basePackageClasses = {ServiceScan.class})
public class ClassScaner {
}
ServiceScan类在test包下,上述代码会扫描业务包com.sky.locle.service中所有带@Component标记的bean:
Junit测试类:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=ClassScaner.class) public class UserServiceTest { //这里就不写测试方法了,感兴趣的可以自己尝试下 }
如果待扫描的目录下存在业务类,可以使用,否则需要自己创建一个空的扫描类,具有侵入性,这时不建议使用。
“隐式自动装配”基于java的@ComponentScan标记 的三种方式就将完了。总结下,我们做非侵入的Junit单元测试,最好选择第二种指定package方式@ComponentScan(basePackages = {"com.xx1","com.xx2","com.xx3”})
2、XMl配置:基于XML配置实现的“隐式自动装配”配置方式为:<context:component-scan base-package="com.xxx" />。在实际开发中,我们经常使用的方式,效果等同于java注解的第二种方式:@ComponentScan(basePackages = {"com.xxx"})。
我们在写Junit单元测试时,不需要创建自己的xml配置文件,如果一定要创建可以在test包下,防止侵入业务代码。但我们可以在Junit测试代码中直接引用已有的xml配置文件。
假如,业务代码中已存在一个xml bean配置文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.sky.locale.service.product" /> <context:component-scan base-package="com.sky.locale.dao.product" /> </beans>
我们Junit测试类可以直接引入使用:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:spring-test.xml"}) public class XmlAutoServiceTest { @Autowired private ProductService ps; @Test public void usNullTest(){ Assert.notNull(ps); ps.add(); } }
执行测试方法,打印信息为:
dao层:商品添加成功 service层:商品添加成功
测试通过,基于“隐式自动装配”这种方式的 Junit单元测试 创建方式 讲解结束。
整合“显式XML装配”
显式XML装配 在spring 2.5以前版本里大量使用。现在一些无法自动装配的bean也会选择使用这种方式,比如配置jdbc连接池以及外部依赖的接口。我们现在常用jdbc配置方式是,把jdbc参数信息放到.properties配置文件中,然后通过XMl装配的方式注入到容器中。
首先我们先看下常用的XML装配方式:
1、不带id的方式:
<!-- 不指定id,默认id为:com.sky.locale.service.explicit.impl.ExplicitTestServiceImpl#0 --> <bean class="com.sky.locale.dao.explicit.impl.ExplicitTestDaoImpl" />
2、带id方式:
<bean id="explicitTestDao" class="com.sky.locale.dao.explicit.impl.ExplicitTestDaoImpl" />
3、构造方法方式,成员为引用,使用ref;成员为基础类型,使用value。
<bean id="explicitTestService" class="com.sky.locale.service.explicit.impl.ExplicitTestServiceImpl"> <constructor-arg name="explicitTestDao" ref="explicitTestDao"/> </bean>
4、构造方法 c命令空间方式,构造方法方式的简化版:
<bean id="explicitTestService1" class="com.sky.locale.service.explicit.impl.ExplicitTestServiceImpl" c:explicitTestDao-ref="explicitTestDao" />
5、settter方式:
<!-- setter注入--> <bean id="explicitTestService2" class="com.sky.locale.service.explicit.impl.ExplicitTestServiceImpl"> <property name="explicitTestDao" ref="explicitTestDao"/> <property name="name" value="123"/> <property name="books"> <list> <value>monkeys</value> <value>pigs</value> </list> </property> </bean>
6、setter对应的p命名空间方式:
<!-- p 命名空间注入 --> <bean id="explicitTestService3" class="com.sky.locale.service.explicit.impl.ExplicitTestServiceImpl" p:explicitTestDao-ref="explicitTestDao" p:name="123"> <property name="books"> <list> <value>monkeys</value> <value>pigs</value> </list> </property> </bean>
对于这种list,set等集合成员,还可以单独提取出来,变形成这样:
<!-- 使用util:list把list转移出去 --> <bean id="explicitTestService5" class="com.sky.locale.service.explicit.impl.ExplicitTestServiceImpl" p:explicitTestDao-ref="explicitTestDao" p:name="123" p:books-ref="books"> </bean> <util:list id="books"> <value>monkeys</value> <value>pigs</value> </util:list>
这种提取,同样适用于c命令空间。
对于这种方式的整合到Junit,其实前面已经用到,直接在Junit测试类中指定对应的xml即可:@ContextConfiguration(locations = {"classpath:xxxx.xml"})。这里我们以Junit整合一个jdbc数据源为例进行讲解,首先看下需要装配的数据源类TestJdbcSource:
public class TestJdbcSource { private String userName; private String password; private String uri; public TestJdbcSource(String userName,String password,String uri){ this.userName = userName; this.password = password; this.uri = uri; } public void getSource(){ System.out.println("连接数据库uri:"+uri); System.out.println("连接数据库用户名:"+userName); System.out.println("连接数据库密码:"+password); } }
然后看下装配该bean的xml配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 不需要依赖的id,可以不用指定--> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:test.properties</value> </list> </property> </bean> <bean id="testDbSource" class="com.sky.locale.dao.jdbc.TestJdbcSource"> <constructor-arg name="uri" value="${jdbc.url}"/> <constructor-arg name="userName" value="${jdbc.username}"/> <constructor-arg name="password" value="${jdbc.password}"/> </bean> </beans>
在看下属性配置文件test.properties内容:
jdbc.url=jdbc:msyql:loadbalance://localhost/test_db jdbc.username=root jdbc.password=123
我们要在JUnit中使用该数据源,可以直接引入xml配置即可,代码如下:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:spring-jdbc.xml"}) public class JdbcSourceTest { @Autowired private TestJdbcSource testDbSource; @Test public void jdbcTest(){ Assert.notNull(testDbSource); testDbSource.getSource(); } }
运行测试方法,打印信息为:
连接数据库uri:jdbc:msyql:loadbalance://localhost/test_db 连接数据库用户名:root 连接数据库密码:123
说明Junit整合“显示XML装配” 成功。
整合”显式java装配”
这种方式类似“显示xml装配”,但比xml更加灵活。新版spring也推荐使用这种方式(所谓的去xml化),但可惜的是现在我们系统还基本没有使用过。我们首先看下”显式java装配”方法有那些形式:
首先创建一个空类,@Configuration表示该类为一个配置类:
@Configuration public class ExplicitTestConfig { }
然后依次往里面添加下列方法,进行“显示java装配”。
1、没有依赖其他对象的bean,直接调用其无参构造方法:
@Bean(name="explicitTestDao") public ExplicitTestDao explicitTestDao(){ return new ExplicitTestDaoImpl(); }
2、有依赖其他对象的bean,调用其构造方法,并注入需要的对象:
@Bean(name = "explicitTestService0") public ExplicitTestService explicitTestService0(){ return new ExplicitTestServiceImpl(explicitTestDao()); }
这里以explicitTestDao()方法,会让人错误的以为多次的创建了新的对象,其实spring默认是单例,这种写法不会重复创建对象。
3、有依赖其他对象的bean,为避免第2中方式的错觉,可以直接以bean名注入:
@Bean(name = "explicitTestService1") public ExplicitTestService explicitTestService1(ExplicitTestDao explicitTestDao){ return new ExplicitTestServiceImpl(explicitTestDao); }
推荐使用这种方式,同时注意这里创建的bean跟2中相同,但name不同。
4、setter方式注入,与方式2对应:
@Bean(name = "explicitTestService2") public ExplicitTestService explicitTestService3(){ ExplicitTestServiceImpl impl = new ExplicitTestServiceImpl(); impl.setExplicitTestDao(explicitTestDao()); return impl; }
5、setter方式注入,与方式3对应:
@Bean(name = "explicitTestService3") public ExplicitTestService explicitTestService2(ExplicitTestDao explicitTestDao){ ExplicitTestServiceImpl impl = new ExplicitTestServiceImpl(); impl.setExplicitTestDao(explicitTestDao); return impl; }
可以看到通过“显示java装配”的方式,可以在方法里任意的实现自己的逻辑,比”xml”更加灵活。
与Junit整合,也很简单,直接在JUnit测试类中通过注解引入上述配置类,@ContextConfiguration(classes=ExplicitTestConfig.class)即可。
整合三种装配方式
前面把Junit分别三种装配方式整合进行了讲解。但我们所在的项目,业务代码很有可能以上三种装配方式都有使用,这时候创建Junit单元测试,需要把不同的装配方式整合到一起。其中”自动装配”其实也是通过,java或者xml配置实现的。所有我们只需要整合java装配和xml装配。具体可以使用@import 和@importResource注解来实现。
假设有个业务测试,需要用到商品接口、用户接口、jdbc数据连接。我们可以把各个业务块的java装配、xml转配整合到一起。具体整合内容如下:
@Import({UserServiceConfig.class,ProductServiceConfig.class}) @ImportResource("classpath:spring-jdbc.xml") public class MultConfig { } @ComponentScan public class UserServiceConfig { } @ComponentScan public class ProductServiceConfig { } <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 不需要依赖的id,可以不用指定--> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:test.properties</value> </list> </property> </bean> <bean id="testDbSource" class="com.sky.locale.dao.jdbc.TestJdbcSource"> <constructor-arg name="uri" value="${jdbc.url}"/> <constructor-arg name="userName" value="${jdbc.username}"/> <constructor-arg name="password" value="${jdbc.password}"/> </bean> </beans>
这种整合方式有点类似 我们用一个顶级的xml配置文件,整合各个业务模块xml配置一样:
<?xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byName"> <import resource="xx1.xml" /> <import resource="xx2.xml" /> <import resource="xx3.xml" /> </beans>
整合完成后,创建Junit测试类,这时只需引入整合后的MultConfig类即可:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=MultConfig.class) public class MultTest { @Autowired private UserService us; @Autowired private ProductService ps; @Autowired private TestJdbcSource testDbSource; @Test public void mutliTest(){ Assert.notNull(us); Assert.notNull(ps); Assert.notNull(testDbSource); us.add(); ps.add(); testDbSource.getSource(); } }
执行测试方法,打印结果为:
service层:用户添加成功 service层:商品添加成功 连接数据库uri:jdbc:msyql:loadbalance://localhost/test_db 连接数据库用户名:root 连接数据库密码:123
整合成功。
理论上通过这种方式,你可以把程序中所有bean都注入到容器中,而不需要部署程序,就可以对任意一个接口方法进行测试。
通过以上讲解,应该覆盖了我们在创建Junit单元测试所遇到的大部分情况。根据不同场景,灵活运用,可以让我们的纯接口工程测试更加轻松。
以上示例代码已上传到GitHub,地址:https://github.com/gantianxing/spring-test.git
相关推荐
8. **测试用例**:工程内可能包含JUnit测试类,对Controller、Service或DAO层进行单元测试,以确保代码的功能正确无误。 通过这个基础环境搭建工程,开发者可以快速理解SSM框架的集成方式,学习如何配置和使用这些...
5. 其他可能的辅助库,如JUnit进行单元测试,Servlet API和JSTL用于Web开发。 配置好`pom.xml`后,执行`mvn install`命令,Maven将下载所有依赖并构建项目。接下来,我们需要创建Spring的配置文件,一般为`...
11. 测试:编写JUnit测试或者通过浏览器进行功能测试。 在工程实践中,你还需要考虑安全性、异常处理、国际化等细节,例如使用Struts2的拦截器增强功能,Spring的安全管理,以及MyBatis的缓存机制等。此外,随着...
- JUnit单元测试:集成Junit单元测试的方法和示例。 - 系统环境变量和包扫描:如何读取系统环境变量和自定义Spring Boot应用的包扫描路径。 - 依赖管理:Spring Boot通过引入spring-boot-starter-parent项目作为父...
- **test**目录:可能包含单元测试代码,用JUnit或其他测试框架编写,确保代码功能正确。 5. **配置文件**: - **.classpath**:Eclipse项目中的类路径配置,列出所有项目的库依赖。 - **.mymetadata**和`....
11. 为项目添加JUnit测试支持,通过编写测试类和测试方法来验证业务逻辑的正确性。 12. 将SpringMVC整合到已经搭建好的框架中,需要创建一个annotated-mvc-servlet.xml文件,配置web层的组件以及视图解析器等。 13...
测试方面,可以利用JUnit对Spring服务进行单元测试,使用FlexUnit对Flex组件进行集成测试。 总结,这个"flex 与 spring 源代码工程"为我们提供了一个实际的示例,展示了如何在Java后端和Flex前端之间搭建起高效的...
9. 测试和调试:使用JUnit进行单元测试,确保各个组件正常工作,然后在服务器上部署应用进行集成测试。 在使用压缩包文件时,开发者需要将"lib2"目录下的所有JAR文件添加到项目的类路径中,以确保所有依赖得到满足...
7. **单元测试与集成测试**:Spring提供了JUnit支持,帮助编写单元测试。同时,你可以使用Spring Test模块进行集成测试,确保整个应用的正确运行。 8. **RESTful API设计**:在Spring MVC中,你可能会学习如何创建...
3. src/test/java:存放测试代码,使用JUnit或Spring Test进行单元测试和集成测试。 三、数据库集成与数据访问层(DAO) Spring Boot默认支持多种数据库,如MySQL、PostgreSQL等,通过 starter-data-jpa 模块,...
9. **单元测试**:项目可能包含了对Service层或DAO层的JUnit测试,以验证功能的正确性。 10. **Web层**:Spring MVC的Controller负责接收HTTP请求,调用Service层方法并返回视图。 这个"SpringMyBatisWeb"的压缩包...
- `SpringbootdemoApplicationTests`: JUnit 测试类。 - `application.properties`: 配置文件。 - `pom.xml`: Maven 构建文件。 #### 项目启动与运行 - 使用内置的 Tomcat 服务器启动项目,只需运行 `...
11. 添加JUnit:给项目添加JUnit测试,编写测试类以确保功能的正确性和完整性。 12. Spring MVC整合:在整合了Spring和Hibernate后,接下来需要整合Spring MVC。创建一个名为annomvc-servlet.xml的配置文件,用于...
编写JUnit测试用例验证SSM框架的正确性,然后在IDE中运行项目,通过浏览器访问测试接口,查看结果。 9. **部署**: 使用Maven的`mvn package`命令打包成war文件,然后将war文件部署到Tomcat服务器。 通过以上...
SpringRoo是一款旨在提高开发效率的工具,它能够帮助开发者快速搭建基于Spring框架的应用程序。通过一系列命令行操作,开发者可以快速创建出包含基本业务逻辑的应用程序骨架。 **1.2 为什么使用SpringRoo?** - **...
客户关系管理系统框架搭建: crm项目的架构 * 创建web工程 * 引入jar包 * mysql的驱动包 * hibernate需要的jar包 * spring需要的jar包 * struts2需要的jar包 * jstl 需要的jar包 ... * junit:开发人员测试用的
在工程名上右击,选择`Build Path`,然后点击`Add Libraries...`,按照向导步骤引入JUnit库,以便在项目中进行单元测试。 **一、配置Struts2** 1. 添加Struts2所需的JAR包。至少需要包括以下六个JAR文件: - `...