• java EE技术体系——CLF平台API开发注意事项(2)——后端测试


    前言:上篇博客说到了关于开发中的一些情况,这篇博客主要说明一些关于测试的内容。

    一、宏观说明

    要求:每一个API都必须经过测试。   备注:如果涉及到服务间调用(如权限和基础数据),而对方服务不可用时,马上索取对方服务API,自行构建mock service(嘿嘿,小伙伴们都懂得,咱家做mock service的速度很快哈)


    工具:Arquillian    备注:和以往测试使用JUnit不同,本平台项目测试使用Auquillian框架。 简单了解Arquillian:http://www.infoq.com/cn/articles/dan-allen-arquillian-framework

    本平台已经集成了Arquillian相应的配置,大家直接在测试包里面添加自己的测试类即可,如果需要了解从无到有的配置,请参考:http://arquillian.org/guides/getting_started/?utm_source=cta 


    基本使用情况:本测试,完全模拟客户端的调用轨迹,进行包含业务在内的测试。所以,接下来需要重点说明已经配置好的两个基本类和相关文件。

    二、细节说明

    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依赖!

    2,严禁使用System.out.println去进行测试,必须使用断言assertThat

    3,每个方法需要进行的测试项:状态码,消费类型,数据格式,权限。具体实际,严格按照API文档执行测试!

    4,在测试包中,有一个TUserControllerTest的测试类,这个类是一个测试模板,对于delete、put、post、get等方式,以及各种传参、各种返回值类型的写法

    三、总结

    虽然咱们没有用Junit,但Arquillian是基于其再次封装。对于咱们来说,没有任何的学习成本和难度,所以,心态放平,一切都是so easy。对于新事物,应该保持一种激情!

    对于咱们的安全控制,目前的规范是java EE中Security,实现的是OAuth2.0协议,采用的模式是简化模式。 具体情况,我找时间,再分享!

  • 相关阅读:
    避免数据脏读
    OGG配置文件中参数化的运用
    GoldenGate基于中间队列文件的初始化
    一次linux中毒,挖矿病毒
    goldengate新版本中查看日志读取点
    dlopen用法参数flag介绍
    gdb调试带参数和调试core
    在现有的git服务器上面创建新的repo
    Play Old Diablo 2 on macOS Catalina
    Odoo中的Environment对象
  • 原文地址:https://www.cnblogs.com/hhx626/p/7534566.html
Copyright © 2020-2023  润新知