java EE技術體系——CLF平臺API開發注意事項(2)——後端測試

前言:上篇博客說到了關於開發中的一些狀況,這篇博客主要說明一些關於測試的內容。css

1、宏觀說明

要求:每個API都必須通過測試。   備註:若是涉及到服務間調用(如權限和基礎數據),而對方服務不可用時,立刻索取對方服務API,自行構建mock service(嘿嘿,小夥伴們都懂得,咱家作mock service的速度很快哈)java


工具:Arquillian    備註:和以往測試使用JUnit不一樣,本平臺項目測試使用Auquillian框架。 簡單瞭解Arquillian:http://www.infoq.com/cn/articles/dan-allen-arquillian-frameworkweb

本平臺已經集成了Arquillian相應的配置,你們直接在測試包裏面添加本身的測試類便可,若是須要了解從無到有的配置,請參考:http://arquillian.org/guides/getting_started/?utm_source=cta sql


基本使用狀況:本測試,徹底模擬客戶端的調用軌跡,進行包含業務在內的測試。因此,接下來須要重點說明已經配置好的兩個基本類和相關文件。apache

2、細節說明

2.1,抽象類,組裝測試環境

package com.dmsdbj.library.controller;

import com.dmsdbj.library.controller.util.HeaderUtil;
import com.dmsdbj.library.repository.AbstractRepository;
import com.dmsdbj.library.repository.producer.EntityManagerProducer;
import com.dmsdbj.library.repository.producer.LoggerProducer;
import java.io.File;
import java.net.URL;
import java.util.Map;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.ClassLoaderAsset;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
import org.jboss.shrinkwrap.resolver.api.maven.MavenResolverSystem;
import org.junit.Before;
import org.junit.runner.RunWith;

/**
 * Abstract class for base application packaging.
 *
 */
@RunWith(Arquillian.class)
public abstract class AbstractTest {

    @ArquillianResource
    private URL deploymentUrl;
    private WebTarget webTarget;
    protected final static MavenResolverSystem RESOLVER = Maven.resolver();

    public static WebArchive buildArchive() {
        File[] jacksonFiles = RESOLVER.resolve("com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.7.5").withTransitivity().asFile();
        File[] deltaspikeFiles = RESOLVER.resolve("org.apache.deltaspike.core:deltaspike-core-api:1.5.0").withTransitivity().asFile();
        File[] deltaspikeImplFiles = RESOLVER.resolve("org.apache.deltaspike.core:deltaspike-core-impl:1.5.0").withTransitivity().asFile();

        final WebArchive archive = ShrinkWrap.create(WebArchive.class);
        archive.addClass(AbstractRepository.class).addPackage(HeaderUtil.class.getPackage())
                .addClass(EntityManagerProducer.class).addClass(LoggerProducer.class)
                .addAsLibraries(jacksonFiles).addAsLibraries(deltaspikeFiles).addAsLibraries(deltaspikeImplFiles)
                .addAsWebInfResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"))
                .addAsResource("test-persistence.xml", "META-INF/persistence.xml")
                .addAsResource(new ClassLoaderAsset("META-INF/sql/insert.sql"), "META-INF/sql/insert.sql")
                .setWebXML("web.xml");
        return archive;
    }

    @Before
    public void buildWebTarget() throws Exception {
        webTarget = ClientBuilder.newClient().target(deploymentUrl.toURI().toString() + "resources/");
    }

    protected Invocation.Builder target(String path) {
        return webTarget.path(path).request();
    }

    protected Invocation.Builder target(String path, Map<String, Object> params) {
        WebTarget target = webTarget.path(path);
        for (String key : params.keySet()) {
            if (path.contains(String.format("{%s}", key))) {
                target = target.resolveTemplate(key, params.get(key));
            } else {
                target = target.queryParam(key, params.get(key));
            }
        }
        return target.request();
    }

}

2.2,抽象類,模擬客戶端調用環境

package com.dmsdbj.library.controller;

import com.dmsdbj.library.app.config.ConfigResource;
import com.dmsdbj.library.app.config.Constants;
import com.dmsdbj.library.app.security.SecurityUtils;
import com.dmsdbj.library.app.security.jwt.JWTAuthenticationFilter;
import com.dmsdbj.library.app.service.mail.MailService;
import com.dmsdbj.library.app.util.RandomUtil;
import com.dmsdbj.library.app.service.UserService;
import com.dmsdbj.library.controller.dto.LoginDTO;
import com.dmsdbj.library.controller.dto.UserDTO;
import com.dmsdbj.library.entity.AbstractAuditingEntity;
import com.dmsdbj.library.entity.AuditListner;
import com.dmsdbj.library.entity.Authority;
import com.dmsdbj.library.entity.User;
import com.dmsdbj.library.repository.AuthorityRepository;
import com.dmsdbj.library.repository.UserRepository;
import static com.dmsdbj.library.controller.AbstractTest.buildArchive;
import java.util.Map;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.core.Response;
import org.jboss.shrinkwrap.api.asset.ClassLoaderAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
import org.junit.Before;

/**
 * Abstract class for application packaging.
 *
 */
public abstract class ApplicationTest extends AbstractTest {

    protected static final String USERNAME = "admin";
    protected static final String PASSWORD = "admin";
    protected static final String INVALID_PASSWORD = "invalid_password";
    protected static final String INCORRECT_PASSWORD = "pw";
    private static final String AUTH_RESOURCE_PATH = "api/authenticate";

    protected String tokenId;

    public static WebArchive buildApplication() {
        return buildArchive().addPackages(true, ConfigResource.class.getPackage(), MailService.class.getPackage(), UserDTO.class.getPackage(), SecurityUtils.class.getPackage(), RandomUtil.class.getPackage())
                .addClass(User.class).addClass(Authority.class).addClass(AbstractAuditingEntity.class).addClass(AuditListner.class)
                .addClass(UserRepository.class).addClass(AuthorityRepository.class).addClass(UserService.class)
                .addAsResource(new ClassLoaderAsset("config/application.properties"), "config/application.properties")
                .addAsResource(new ClassLoaderAsset("i18n/messages.properties"), "i18n/messages.properties")
                .addClass(UserJWTController.class).addPackage(JWTAuthenticationFilter.class.getPackage());
    }

    @Before
    public void setUp() throws Exception {
        login(USERNAME, PASSWORD);
    }

    @After
    public void tearDown() {
        logout();
    }

    protected Response login(String username, String password) {
        LoginDTO loginDTO = new LoginDTO();
        loginDTO.setUsername(username);
        loginDTO.setPassword(password);
        Response response = target(AUTH_RESOURCE_PATH).post(Entity.json(loginDTO));
        tokenId = response.getHeaderString(Constants.AUTHORIZATION_HEADER);
        return response;
    }

    protected void logout() {
        tokenId = null;
    }

    @Override
    protected Invocation.Builder target(String path) {
        return super.target(path).header(Constants.AUTHORIZATION_HEADER, tokenId);
    }

    @Override
    protected Invocation.Builder target(String path, Map<String, Object> params) {
       return super.target(path, params).header(Constants.AUTHORIZATION_HEADER, tokenId);
    }

}

注意:這兩個抽象類,在測試的時候,惟一可能會須要修改的,是用戶名和密碼。修改場景:測試API的權限;如createAccount(User user)的權限爲admin,那麼這裏可能須要更改其餘角色,以測試權限訪問的準確性!

2.3,具體測試類

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.dmsdbj.library.controller;

import static com.dmsdbj.library.controller.ApplicationTest.buildApplication;
import com.dmsdbj.library.entity.TOpinion;
import com.dmsdbj.library.repository.TOpinionRepository;
import java.util.ArrayList;
import static java.util.Collections.singletonMap;
import java.util.List;
import javax.inject.Inject;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import static org.hamcrest.CoreMatchers.is;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.InSequence;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import static org.valid4j.matchers.http.HttpResponseMatchers.hasContentType;
import static org.valid4j.matchers.http.HttpResponseMatchers.hasStatus;

/**
 *
 * @author Angelina
 */
public class OpinionControllerTest extends ApplicationTest {

    @Deployment
    public static WebArchive createDeployment() {
        return buildApplication().addClass(TOpinion.class).addClass(TOpinionRepository.class).addClass(OpinionController.class);
    }
    
    private static TOpinion opinion;

    @Inject
    private TOpinionRepository TOpinionRepository;

    @Test
    @InSequence(1)
    public void findOpinionByStatus() throws Exception {
        List<String> status=new ArrayList<>();
        status.add("待解決");
        int databaseSize = TOpinionRepository.exampleForNameQuery2(status).size();
       Response response = target("/opinion/findByStatus",singletonMap("status","待解決")).get();
        assertThat(response, hasStatus(Response.Status.OK));
        assertThat(response, hasContentType(MediaType.APPLICATION_JSON_TYPE));

        List<TOpinion> Opinion;
        Opinion= response.readEntity(List.class);
        assertThat(Opinion.size(),is(databaseSize));
    }

}
注意:

1,這裏面的WebArchive方法,是組建業務調用線的。目前的方法說明:controller依賴repository,逐次依賴entity。在咱們真是開發項目結構中,咱們有service層,因此,測試的時候,須要再repository後面,加上service依賴!json

2,嚴禁使用System.out.println去進行測試,必須使用斷言assertThatapi

3,每一個方法須要進行的測試項:狀態碼,消費類型,數據格式,權限。具體實際,嚴格按照API文檔執行測試!安全

4,在測試包中,有一個TUserControllerTest的測試類,這個類是一個測試模板,對於delete、put、post、get等方式,以及各類傳參、各類返回值類型的寫法markdown

3、總結

雖然我們沒有用Junit,但Arquillian是基於其再次封裝。對於我們來講,沒有任何的學習成本和難度,因此,心態放平,一切都是so easy。對於新事物,應該保持一種激情!app

對於我們的安全控制,目前的規範是java EE中Security,實現的是OAuth2.0協議,採用的模式是簡化模式。 具體狀況,我找時間,再分享!

相關文章
相關標籤/搜索