zoukankan      html  css  js  c++  java
  • sonar + jacoco + mockMvc 模拟session 用户登录 配合SpringSecurity 权限 快速测试代码覆盖率.

    遇到mock 测试简直就是神器,特别是要做代码覆盖率,直接测试controller就好了,缺点,虽然可以回滚事务,但是依赖数据库数据,解决,根据SpringBoot ,再建立一个专门跑单元测试的数据库,以及application.yml

    想起以前用的 unitils 整合测试,巨额时间成本,都是在写XML.遇到时间变化的条件,还一点办法都没有,唯一觉得是优势的就是与环境解耦,不依赖数据库

     

    pom配置

     <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <fork>true</fork>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <!--<configuration>-->
                        <!--<skip>true</skip>-->
                    <!--</configuration>-->
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.6.1</version>
                    <configuration>
                        <!--<skip>true</skip>-->
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>${project.build.sourceEncoding}</encoding>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.jacoco</groupId>
                    <artifactId>jacoco-maven-plugin</artifactId>
                    <configuration>
                        <includes>com.*</includes>
                    </configuration>
                    <executions>
                        <execution>
                            <id>pre-test</id>
                            <goals>
                                <goal>prepare-agent</goal>
                            </goals>
                        </execution>
                        <execution>
                            <id>post-test</id>
                            <phase>test</phase>
                            <goals>
                                <goal>report</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>

    jenkins 集成了 sonar 配置 Analysis properties 一定要配置  sonar.tests=src/test/java 否则会吧单元测试的覆盖率也算上,坑爹,默认就应该排除掉

    sonar.exclusions 可以排除掉,自相矛盾的规则(永远达不成,例如 全是静态方法的类,可以写成final 并且添加私有构造方法,这个私有构造方法,永远都覆盖不了,除非你还去反射调用),或者测试不会执行的类,这里我排除了SpringBoot的启动类

    sonar.projectKey=testKey
    sonar.projectName=testProject
    sonar.projectVersion=1.0
    sonar.sources=src/main/java
    sonar.tests=src/test/java
    sonar.exclusions=src/main/java/com/test/Application.java
    sonar.java.binaries=target/classes
    sonar.language=java
    sonar.sourceEncoding=UTF-8

    回滚事务

    在测试类加上注解@Rollback

    注意:之前喜欢在Controller 使用 @Transactional(propagation = Propagation.REQUIRES_NEW),但是这样的话,是强行开启一个新事务,不会加入上层事务,所以哪怕是Controller 也应该使用@Transactional(propagation = Propagation.REQUIRED)

    否则@Rollback无效

    如果是微服务SpringCloud,回滚不了另一个服务的事务,那么直接进入FallBack服务降级就好了

    以下是我的测试类的注解部分

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    @Rollback
    @Transactional
    @ActiveProfiles(profiles = "dev")
    public class BaseTests {
    
    }

    加入SpringSecurity过滤器链

    模拟用户登录,注意登录用户的权限问题

       @Autowired
        protected TestRestTemplate restTemplate;
    
        @Autowired
        protected WebApplicationContext wac;
    
        @Autowired
        private Filter springSecurityFilterChain;
    
        @Autowired
        private Filter invoiceContextFilter;
    
        protected MockMvc mockMvc;
    
        protected MockHttpSession session;
    
        @PostConstruct
        public void setup() throws Exception {
            this.mockMvc = MockMvcBuilders.webAppContextSetup(wac)
                    .addFilters(springSecurityFilterChain, invoiceContextFilter)
                    .build();
            this.session = new MockHttpSession();
            login();
            getLoginSession("testuser", "789456a");
        }
       protected void login() throws Exception {
            MvcResult result = this.mockMvc
                    .perform(get("/login"))
                    .andReturn();
            Assert.assertNotNull(result.getModelAndView());
        }
        /**
         * 获取登入信息session
         *
         * @return
         * @throws Exception
         */
        protected void getLoginSession(String name, String pwd) throws Exception {
    
            MvcResult result = this.mockMvc
                    .perform(post("/doLogin").contentType(MediaType.APPLICATION_FORM_URLENCODED)
                            .param("username", name).param("password",pwd)
                            .param("verifiCode", "ABCD"))
                    .andExpect(status().isFound())
                    .andReturn();
            MockHttpSession mockHttpSession = (MockHttpSession) result.getRequest().getSession();
            this.mockMvc
                    .perform(get("/success").session(mockHttpSession))
                    .andExpect(status().isOk());
            this.session = mockHttpSession;
        }
    }

     作用域为session 的bean 处理

    controller 依赖注入了 作用域为 @Scope(scopeName = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)  的bean

    例如

    @Component("cuzSessionAttributes")
    @Scope(scopeName = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public class UserAttributes implements Serializable {
    
    }

    JunitTest中,可以从MockHttpSession取出这个bean,然后修改bean的属性,再设置回session,以达到测试分支覆盖率的作用

       @Test
        public void testDo() throws Exception {
            UserAttributes userAttributes = (UserAttributes )session.getAttribute("scopedTarget.userAttributes");
            userAttributes.setSomeThing("abc");
            session.setAttribute("scopedTarget.userAttributes",userAttributes);
            this.mockMvc
                    .perform(get("/do")
                            .session(session)).andExpect(status().isOk());
        }

    上传文件测试

    @Test
        public void upload() throws Exception {
            InputStream inputStream = this.getClass().getResourceAsStream("/test.xlsx");
            MockMultipartFile multipartFile = new MockMultipartFile("txt_file","test.xlsx","multipart/form-data; boundary=----WebKitFormBoundarybF0B6B6hk52YSBvk", inputStream);
            MvcResult result = this.mockMvc.perform(
                    fileUpload("/upload").file(multipartFile).session(session)
            )
                    .andExpect(status().isOk()).andReturn();
        }

    uploadController

    网上流传着一段上传多文件的代码,这段代码mock上传是会转换异常的

     @RequestMapping(value = "/batchUpload", method = RequestMethod.POST)
        public Result String batchUpload(HttpServletRequest request) {
            List<MultipartFile> files = ((MultipartHttpServletRequest) request).getFiles("file");
        //*****
        }
    }

    改为这样

       /**
         * 上传
         */
        @PostMapping(path = "/upload", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
        @Transactional(propagation = Propagation.REQUIRED)
        public Result upload(MultipartHttpServletRequest multiRequest) {
            Iterator<String> iter = multiRequest.getFileNames();
            while (iter.hasNext()) {
                MultipartFile file = multiRequest.getFile(iter.next());
           //*****
    }  
    
    

    文件下载测试

       @Test
        public void download() throws Exception {
            MvcResult mvcResult = this.mockMvc
                    .perform(get("/download")
                            .accept(MediaType.APPLICATION_OCTET_STREAM)
                            .session(session))
                    .andExpect(status().isOk()).andReturn();
    Assert.assertNotNull(mvcResult.getResponse().getContentAsByteArray());
    }

    验证码问题 

    SpringBoot解决方式 Environment

    获取环境,这里根据junit环境还有dev环境判断,可以跳过验证码验证

        @Autowired
        private Environment env;
    
    String active = env.getProperty("spring.profiles.active");
                if(!active.equals("junit")&&!active.equals("dev")){
  • 相关阅读:
    echart所有汉字都显示中文,就echarts的toolbox注释显示乱码
    【转】 JSONObject使用方法
    JSON: property "xxx" has no getter method in class "..."
    【转】Oracle数据库中Sequence的用法
    Android实例-获取程序版本号(XE10+小米2)
    Android实例-调用系统APP(XE10+小米2)
    BAT-使用BAT方法清理系统垃圾
    Android实例-全屏显示程序(XE10+小米2)(无图)
    问题-Delphi2007编译时提示内存错误“sxs.dll. No Debug Info.ACCESS 0xXXXXX"
    DelphiXE7中创建WebService(服务端+客户端)
  • 原文地址:https://www.cnblogs.com/sweetchildomine/p/7658805.html
Copyright © 2011-2022 走看看