specs.4.8.gz_使用Specs2和客户端API 2.0进行富有表现力的JAX-RS集成测试
specs.4.8.gz
毫無疑問, JAX-RS是一項杰出的技術。 即將發布的規范JAX-RS 2.0帶來了更多的強大功能,尤其是在客戶端API方面。 今天的帖子的主題是JAX-RS服務的集成測試。 有很多優秀的測試框架,例如REST,可以確保提供幫助,但是我要展示的方式是使用富有表現力的BDD風格。 這是我的意思的示例:
看起來像現代BDD框架的典型Given / When / Then風格。 使用靜態編譯語言,我們在JVM上能達到多近的距離? 事實證明,非常接近,這要歸功于出色的specs2測試工具。
值得一提的是, specs2是一個Scala框架。 盡管我們將編寫一些Scala ,但是我們將以非常直觀的方式來完成它,這對于經驗豐富的Java開發人員來說是熟悉的。 測試中的JAX-RS服務是我們在上一篇文章中開發的 。 這里是:
package com.example.rs;import java.util.Collection;import javax.inject.Inject; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo;import com.example.model.Person; import com.example.services.PeopleService;@Path( '/people' ) public class PeopleRestService {@Inject private PeopleService peopleService;@Produces( { MediaType.APPLICATION_JSON } )@GETpublic Collection< Person > getPeople( @QueryParam( 'page') @DefaultValue( '1' ) final int page ) {return peopleService.getPeople( page, 5 );}@Produces( { MediaType.APPLICATION_JSON } )@Path( '/{email}' )@GETpublic Person getPeople( @PathParam( 'email' ) final String email ) {return peopleService.getByEmail( email );}@Produces( { MediaType.APPLICATION_JSON } )@POSTpublic Response addPerson( @Context final UriInfo uriInfo,@FormParam( 'email' ) final String email, @FormParam( 'firstName' ) final String firstName, @FormParam( 'lastName' ) final String lastName ) {peopleService.addPerson( email, firstName, lastName );return Response.created( uriInfo.getRequestUriBuilder().path( email ).build() ).build();}@Produces( { MediaType.APPLICATION_JSON } )@Path( '/{email}' )@PUTpublic Person updatePerson( @PathParam( 'email' ) final String email, @FormParam( 'firstName' ) final String firstName, @FormParam( 'lastName' ) final String lastName ) {final Person person = peopleService.getByEmail( email ); if( firstName != null ) {person.setFirstName( firstName );}if( lastName != null ) {person.setLastName( lastName );}return person; }@Path( '/{email}' )@DELETEpublic Response deletePerson( @PathParam( 'email' ) final String email ) {peopleService.removePerson( email );return Response.ok().build();} }非常簡單的JAX-RS服務來管理人員。 Java實現提供并支持所有基本的HTTP動詞 : GET , PUT , POST和DELETE 。 為了完整起見,讓我也包括服務層的一些方法,因為這些方法引起我們關注的一些例外。
package com.example.services;import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap;import org.springframework.stereotype.Service;import com.example.exceptions.PersonAlreadyExistsException; import com.example.exceptions.PersonNotFoundException; import com.example.model.Person;@Service public class PeopleService {private final ConcurrentMap< String, Person > persons = new ConcurrentHashMap< String, Person >(); // ...public Person getByEmail( final String email ) {final Person person = persons.get( email ); if( person == null ) {throw new PersonNotFoundException( email );}return person;}public Person addPerson( final String email, final String firstName, final String lastName ) {final Person person = new Person( email );person.setFirstName( firstName );person.setLastName( lastName );if( persons.putIfAbsent( email, person ) != null ) {throw new PersonAlreadyExistsException( email );}return person;}public void removePerson( final String email ) {if( persons.remove( email ) == null ) {throw new PersonNotFoundException( email );}} }基于ConcurrentMap的非常簡單但可行的實現。 在沒有請求電子郵件的人的情況下,將引發PersonNotFoundException 。 分別在已經存在帶有請求電子郵件的人的情況下引發PersonAlreadyExistsException 。 這些異常中的每一個在HTTP代碼中都有一個對應項: 404 NOT FOUND和409 CONFLICT 。 這就是我們告訴JAX-RS的方式:
package com.example.exceptions;import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status;public class PersonAlreadyExistsException extends WebApplicationException {private static final long serialVersionUID = 6817489620338221395L;public PersonAlreadyExistsException( final String email ) {super(Response.status( Status.CONFLICT ).entity( 'Person already exists: ' + email ).build());} }package com.example.exceptions;import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status;public class PersonNotFoundException extends WebApplicationException {private static final long serialVersionUID = -2894269137259898072L;public PersonNotFoundException( final String email ) {super(Response.status( Status.NOT_FOUND ).entity( 'Person not found: ' + email ).build());} }完整的項目托管在GitHub上 。 讓我們結束無聊的部分,然后轉到最甜蜜的部分: BDD 。 如文檔所述, specs2對Given / When / Then樣式提供了很好的支持也就不足為奇了。 因此,使用specs2 ,我們的測試用例將變成這樣:
'Create new person with email <a@b.com>' ^ br^'Given REST client for application deployed at ${http://localhost:8080}' ^ client^'When I do POST to ${rest/api/people}' ^ post(Map('email' -> 'a@b.com', 'firstName' -> 'Tommy', 'lastName' -> 'Knocker'))^'Then I expect HTTP code ${201}' ^ expectResponseCode^'And HTTP header ${Location} to contain ${http://localhost:8080/rest/api/people/a@b.com}' ^ expectResponseHeader^不錯,但是那些^ , br , client , post , ExpectResponseCode和ExpectResponseHeader是什么? ^ , br只是用于支持Given / When / Then鏈的一些specs2糖。 其他的post , ExpectResponseCode和ExpectResponseHeader只是我們定義用于實際工作的幾個函數/變量。 例如, 客戶端是一個新的JAX-RS 2.0客戶端,我們可以這樣創建(使用Scala語法):
val client: Given[ Client ] = ( baseUrl: String ) => ClientBuilder.newClient( new ClientConfig().property( 'baseUrl', baseUrl ) )baseUrl來自Given定義本身,它包含在$ {…}構造中。 此外,我們可以看到Given定義具有很強的類型: Given [Client] 。 稍后我們將看到, When和Then的情況相同 ,它們確實具有相應的強類型when [T,V]和Then [V] 。
流程如下所示:
- 從給定定義開始,該定義返回Client 。
- 繼續進行When定義,該定義接受Given的 Client并返回Response
- 最后是數量的Then定義,這些定義接受來自When的 響應并檢查實際期望
這是帖子定義的樣子(本身就是When [Client,Response] ):
def post( values: Map[ String, Any ] ): When[ Client, Response ] = ( client: Client ) => ( url: String ) => client.target( s'${client.getConfiguration.getProperty( 'baseUrl' )}/$url' ).request( MediaType.APPLICATION_JSON ).post( Entity.form( values.foldLeft( new Form() )( ( form, param ) => form.param( param._1, param._2.toString ) ) ),classOf[ Response ] )最后是ExpectResponseCode和ExpectResponseHeader ,它們非常相似,并且具有相同的類型Then [Response] :
val expectResponseCode: Then[ Response ] = ( response: Response ) => ( code: String ) => response.getStatus() must_== code.toInt val expectResponseHeader: Then[ Response ] = ( response: Response ) => ( header: String, value: String ) => response.getHeaderString( header ) should contain( value )又一個示例,根據JSON有效負載檢查響應內容:
'Retrieve existing person with email <a@b.com>' ^ br^'Given REST client for application deployed at ${http://localhost:8080}' ^ client^'When I do GET to ${rest/api/people/a@b.com}' ^ get^'Then I expect HTTP code ${200}' ^ expectResponseCode^'And content to contain ${JSON}' ^ expectResponseContent('''{'email': 'a@b.com', 'firstName': 'Tommy', 'lastName': 'Knocker' } ''')^這次我們使用以下get實現來執行GET請求:
val get: When[ Client, Response ] = ( client: Client ) => ( url: String ) => client.target( s'${client.getConfiguration.getProperty( 'baseUrl' )}/$url' ).request( MediaType.APPLICATION_JSON ).get( classOf[ Response ] )盡管specs2具有豐富的匹配器集,可對JSON有效負載執行不同的檢查,但我使用的是spray-json ,這是Scala中的輕量,干凈且簡單的JSON實現(的確如此!),這是ExpectResponseContent實現:
def expectResponseContent( json: String ): Then[ Response ] = ( response: Response ) => ( format: String ) => {format match { case 'JSON' => response.readEntity( classOf[ String ] ).asJson must_== json.asJsoncase _ => response.readEntity( classOf[ String ] ) must_== json} }最后一個示例(對現有電子郵件進行POST ):
'Create yet another person with same email <a@b.com>' ^ br^'Given REST client for application deployed at ${http://localhost:8080}' ^ client^'When I do POST to ${rest/api/people}' ^ post(Map( 'email' -> 'a@b.com' ))^'Then I expect HTTP code ${409}' ^ expectResponseCode^'And content to contain ${Person already exists: a@b.com}' ^ expectResponseContent^看起來很棒! 好的,富有表現力的BDD ,使用強類型和靜態編譯! 當然,可以使用JUnit集成,并且可以與Eclipse一起很好地工作。
不要忘了自己的specs2報告(由maven-specs2-plugin生成): mvn clean test
請在GitHub上查找完整的項目。 另外,請注意,由于我使用的是最新的JAX-RS 2.0里程碑(最終草案),因此發布該API時可能會有所變化。
參考: Andriy Redko {devmind}博客上的JCG合作伙伴 Andrey Redko 使用Specs2和客戶端API 2.0進行了富有表現力的JAX-RS集成測試 。
翻譯自: https://www.javacodegeeks.com/2013/03/expressive-jax-rs-integration-testing-with-specs2-and-client-api-2-0.html
specs.4.8.gz
總結
以上是生活随笔為你收集整理的specs.4.8.gz_使用Specs2和客户端API 2.0进行富有表现力的JAX-RS集成测试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不负如来不负卿的上一句是什么啊 不负如来
- 下一篇: 再社会化名词解释 再社会化解释