<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          靈魂拷問:到底要不要寫單元測(cè)試,如何正確進(jìn)行單元測(cè)試?

          共 16852字,需瀏覽 34分鐘

           ·

          2021-12-01 00:32

             
          點(diǎn)擊上方“碼農(nóng)突圍”,馬上關(guān)注

          這里是碼農(nóng)充電第一站,回復(fù)“666”,獲取一份專屬大禮包
               
          真愛,請(qǐng)?jiān)O(shè)置“星標(biāo)”或點(diǎn)個(gè)“在看


          來源:blog.csdn.net/new_com/article/details/116098959

          為什么要寫單元測(cè)試

          一聊起測(cè)試用例,很多人第一反應(yīng)就是,我們公司的測(cè)試會(huì)寫測(cè)試用例的,我自己也會(huì)使用postman或者swagger之類的進(jìn)行代碼自測(cè)。那我們研發(fā)到底要不要寫單元測(cè)試用例呢?

          參考阿里巴巴開發(fā)手冊(cè),第8條規(guī)則(單元測(cè)試的基本目標(biāo):語句覆蓋率達(dá)到 70%;核心模塊的語句覆蓋率和分支覆蓋率都要達(dá)到 100%),大廠的要求就是必須嘍。我個(gè)人感覺,寫單元測(cè)試用例也是很有必要的,好處很多,例如:

          • 保證代碼質(zhì)量!!!無論初級(jí),中級(jí),高級(jí)攻城獅開發(fā)工程的代碼,且不說效率如何,功能是必要要保證是正確的;交付測(cè)試以后,bug銳減,聯(lián)調(diào)飛快。
          • 代碼邏輯“文檔化”!!!新人接手維護(hù)模塊代碼時(shí),通過單元測(cè)試用例,以debug的方式就能熟悉業(yè)務(wù)代碼。比起,看代碼,研究表結(jié)構(gòu)梳理代碼結(jié)構(gòu),效率提升飛快。
          • 易維護(hù)!!!新人接手維護(hù)代碼模塊時(shí),提交自己的代碼時(shí),遠(yuǎn)行之前的單元測(cè)試達(dá)到回歸測(cè)試,保證了新改動(dòng)不會(huì)影響老業(yè)務(wù)。
          • 快速定位bug!!!在聯(lián)調(diào)期間,測(cè)試提出bug后,基于uat環(huán)境,編寫出錯(cuò)的api測(cè)試用例。根據(jù),測(cè)試提供的參數(shù)和token就可以以debug的方式跟蹤問題的所在,如果是在微服務(wù)架構(gòu)中,運(yùn)行單元測(cè)試用例,不會(huì)注冊(cè)本地服務(wù)到uat環(huán)境,還能過正常請(qǐng)求注冊(cè)中心的服務(wù)。

          到底如何寫單元測(cè)試

          Java開發(fā)springboot項(xiàng)目都是基于junit測(cè)試框架,比較MockitoJUnitRunnerSpringRunner的使用,MockitoJUnitRunner基于mockito,模擬業(yè)務(wù)條件,驗(yàn)證代碼邏輯。SpringRunner是MockitoJUnitRunner子類,集成了Spring容器,可以在測(cè)試的根據(jù)配置加載Spring bean對(duì)象。

          在Springboot開發(fā)中,結(jié)合@SpringBootTest注解,加載項(xiàng)目配置,進(jìn)行單元測(cè)試。

          基于MockitoJUnitRunner的方法測(cè)試

          以springboot項(xiàng)目為例,一般,對(duì)單個(gè)的方法都是進(jìn)行mock測(cè)試,在測(cè)試方法使用MockitoJUnitRunner,根據(jù)不同條件覆蓋測(cè)試。使用@InjectMocks注解,可以讓模擬的方法正常發(fā)起請(qǐng)求;@Mock注解可以模擬期望的條件。以刪除菜單服務(wù)為例,源碼如下:

           
          @Service
          public class MenuManagerImpl implements IMenuManager {
           
              /**
               * 刪除菜單業(yè)務(wù)邏輯
               **/

              @Override
              @OptimisticRetry
              @Transactional(rollbackFor = Exception.class)
              public boolean delete(Long id
          {
           
                  if (Objects.isNull(id)) {
                      return false;
                  }
                  Menu existingMenu = this.menuService.getById(id);
                  if (Objects.isNull(existingMenu)) {
                      return false;
                  }
                  if (!this.menuService.removeById(id)) {
                      throw new OptimisticLockingFailureException("刪除菜單失敗!");
                  }
                  return true;
              }
          }
           
           
           /**
            * 刪除菜單方法級(jí)單元測(cè)試用例
            **/

          @RunWith(MockitoJUnitRunner.class)
          public class MenuManagerImplTest 
          {
           
              @InjectMocks
              private MenuManagerImpl menuManager;
           
              @Mock
              private IMenuService menuService;
           
              @Test
              public void delete() {
           
                  Long id = null;
                  boolean flag;
                  // id為空
                  flag = menuManager.delete(id);
                  Assert.assertFalse(flag);
           
                  // 菜單返回為空
                  id = 1l;
                  Mockito.when(this.menuService.getById(ArgumentMatchers.anyLong())).thenReturn(null);
                  flag = menuManager.delete(id);
                  Assert.assertFalse(flag);
           
                  // 修改成功
                  Menu mockMenu = new Menu();
                  Mockito.when(this.menuService.getById(ArgumentMatchers.anyLong())).thenReturn(mockMenu);
                  Mockito.when(this.menuService.removeById(ArgumentMatchers.anyLong())).thenReturn(true);
                  flag = menuManager.delete(id);
                  Assert.assertTrue(flag);
              }
          }

          基于SpringRunner的Spring容器測(cè)試

          在api開發(fā)過程中,會(huì)對(duì)單個(gè)api的調(diào)用鏈路進(jìn)行驗(yàn)證,對(duì)第三方服務(wù)進(jìn)行mock模擬,本服務(wù)的業(yè)務(wù)邏輯進(jìn)行測(cè)試。

          一般,會(huì)使用@SpringBootTest加載測(cè)試環(huán)境的Spring容器配置,使用MockMvc以http請(qǐng)求的方式進(jìn)行測(cè)試。以修改新增菜單測(cè)試用例為例,如下:

          /**
           * 成功新增菜單api
          */

          @Api(tags = "管理員菜單api")
          @RestController
          public class AdminMenuController {
           
              @Autowired
              private IMenuManager menuManager;
           
           
              @PreAuthorize("hasAnyAuthority('menu:add','admin')")
              @ApiOperation(value = "新增菜單")
              @PostMapping("/admin/menu/add")
              @VerifyLoginUser(type = IS_ADMIN, errorMsg = INVALID_ADMIN_TYPE)
              public Response<MenuVo> save(@Validated @RequestBody SaveMenuDto saveMenuDto) {
                  return Response.success(menuManager.save(saveMenuDto));
              }
          }
           
           
           
          /**
           * 成功新增菜單單元測(cè)試用例
          */

          @RunWith(SpringRunner.class)
          @SpringBootTest(classes 
          = MallSystemApplication.class)
          @Slf4j
          @AutoConfigureMockMvc
          public class AdminMenuControllerTest extends BaseTest 
          {
           
          /**
           * 成功新增菜單
          */

          @Test
          public void success2save() throws Exception {
           
                  SaveMenuDto saveMenuDto = new SaveMenuDto();
                  saveMenuDto.setName("重置密碼");
                  saveMenuDto.setParentId(1355339254819966978l);
                  saveMenuDto.setOrderNum(4);
                  saveMenuDto.setType(MenuType.button.getValue());
                  saveMenuDto.setVisible(MenuVisible.show.getValue());
                  saveMenuDto.setUrl("https:baidu.com");
                  saveMenuDto.setMethod(MenuMethod.put.getValue());
                  saveMenuDto.setPerms("user:reset-pwd");
                  // 發(fā)起http請(qǐng)求
                  MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
                          .post("/admin/menu/add")
                          .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
                          .content(JSON.toJSONString(saveMenuDto))
                          .accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
                          .header(GlobalConstant.AUTHORIZATION_HEADER, GlobalConstant.ADMIN_TOKEN))
                          .andExpect(MockMvcResultMatchers.status().isOk())
                          .andDo(MockMvcResultHandlers.print())
                          .andReturn();
                  Response<MenuVo> response = JSON.parseObject(mvcResult.getResponse().getContentAsString(), menuVoTypeReference);
                  // 斷言結(jié)果
                  Assert.assertNotNull(response);
                  MenuVo menuVo;
                  Assert.assertNotNull(menuVo = response.getData());
                  Assert.assertEquals(menuVo.getName(), saveMenuDto.getName());
                  Assert.assertEquals(menuVo.getOrderNum(), saveMenuDto.getOrderNum());
                  Assert.assertEquals(menuVo.getType(), saveMenuDto.getType());
                  Assert.assertEquals(menuVo.getVisible(), saveMenuDto.getVisible());
                  Assert.assertEquals(menuVo.getStatus(), MenuStatus.normal.getValue());
                  Assert.assertEquals(menuVo.getUrl(), saveMenuDto.getUrl());
                  Assert.assertEquals(menuVo.getPerms(), saveMenuDto.getPerms());
                  Assert.assertEquals(menuVo.getMethod(), saveMenuDto.getMethod());
              }
          }

          具體編寫單元測(cè)試用例規(guī)則參考測(cè)試用例的編寫。簡(jiǎn)單說,一般api的單元測(cè)試用例,編寫兩類,如下:

          • 業(yè)務(wù)參數(shù)的校驗(yàn),和義務(wù)異常的校驗(yàn)。例如,名稱是否為空,電話號(hào)碼是否正確,用戶未登陸則拋出未登陸異常。
          • 各類業(yè)務(wù)場(chǎng)景的真實(shí)測(cè)試用例,例如,編寫成功添加頂級(jí)菜單的測(cè)試用例,已經(jīng)編寫成功添加子級(jí)菜單的測(cè)試用例。

          注意事項(xiàng)

          配置覆蓋

          此外,如上基于mockmvc的編寫的測(cè)試用例,由于加載了Spring的配置,會(huì)對(duì)項(xiàng)目發(fā)起真實(shí)的調(diào)用。如果,環(huán)境的配置為線上配置,容易出現(xiàn)安全問題。

          一般出于安全考慮,很多公司會(huì)對(duì)真實(shí)環(huán)境的修改操作做事務(wù)回滾操作,甚至根本就不會(huì)進(jìn)行真實(shí)環(huán)境的調(diào)用,使用模擬環(huán)境替換,例如數(shù)據(jù)庫(kù)的操作可以使用h2內(nèi)存數(shù)據(jù)庫(kù)進(jìn)行替換。

          這時(shí),可以在src/test/resources目錄下,添加與src/main/resources目錄下,相同的文件進(jìn)行配置覆蓋。src/test/main目錄下的代碼,會(huì)首先加載src/test/resources目錄下的配置,如果沒有則在加載src/main/resources目錄的配置。常用場(chǎng)景如下:

          • 在單元測(cè)試環(huán)境使用使用內(nèi)存數(shù)據(jù)庫(kù)。
          • ginkens代碼集成運(yùn)行測(cè)試用例時(shí),不希望在集成環(huán)境中輸出日志文件信息,并且以debug級(jí)別輸出日志。

          以日志文件配置覆蓋為例,在src/main/resources目錄下配置日志有文件和控制臺(tái)輸出,如圖:

          main/resource目錄下的logback-spring.xml,內(nèi)容如下:

          <?xml version="1.0" encoding="UTF-8"?>
          <configuration scan="true">
           
           <contextName>mall-system</contextName>
           
              <!-- 控制臺(tái)日志輸出配置 -->
           <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
             <pattern>
              [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%level][%thread] [%contextName] [%logger{80}:%L] %msg%n
             </pattern>
             <charset>UTF-8</charset>
            </encoder>
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
             <level>DEBUG</level>
            </filter>
           </appender>
           
              <!-- 日志文件輸出配置 -->
           <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>log/info.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
             <fileNamePattern>log/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
             <maxFileSize>50MB</maxFileSize>
             <maxHistory>50</maxHistory>
             <totalSizeCap>10GB</totalSizeCap>
            </rollingPolicy>
            <encoder>
             <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%level] [%contextName] [%logger{80}:%L] %msg%n</pattern>
            </encoder>
           </appender>
           
              <!-- 設(shè)置INFO 級(jí)別輸出日志 -->
           <root level="INFO">
            <appender-ref ref="STDOUT" />
            <appender-ref ref="FILE" />
           </root>
          </configuration>

          src/test//resource目錄下的新增logback-spring.xml,去掉日志文件輸出的配置,設(shè)置日志輸出級(jí)別為DEBUG;如果運(yùn)行測(cè)試用例,則加載該配置不會(huì)進(jìn)行日志文件的輸出,并且打印DEBUG級(jí)別日志。如圖:

          <?xml version="1.0" encoding="UTF-8"?>
          <configuration scan="true">
           
              <contextName>mall-system</contextName>
              
              <!-- 控制臺(tái)日志輸出 -->
              <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
                  <encoder>
                      <pattern>
                          [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%level][%thread] [%contextName] [%logger{80}:%L] %msg%n
                      </pattern>
                      <charset>UTF-8</charset>
                  </encoder>
                  <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                      <level>DEBUG</level>
                  </filter>
              </appender>
           
              <!-- DEBUG級(jí)別日志輸出 -->
              <root level="DEBUG">
                  <appender-ref ref="STDOUT"/>
              </root>
          </configuration>

          指定環(huán)境

          一般開發(fā)過程中,我們研發(fā)只會(huì)操作開發(fā)環(huán)境,也是為了避免數(shù)據(jù)安全問題,可以在單元測(cè)試用例中指定運(yùn)行的環(huán)境配置。在測(cè)試類加上@ActiveProfiles("dev"),指定獲取dev環(huán)境的配置。示例,

          /**
          * 獲取dev環(huán)境配置
          */

          @RunWith(SpringRunner.class)
          @SpringBootTest(classes 
          = MallSystemApplication.class)
          @Slf4j
          @AutoConfigureMockMvc
          @ActiveProfiles("dev")
          public class AdminMenuControllerTest extends BaseTest 
          {
          }

          在聯(lián)調(diào)測(cè)試中,對(duì)于出錯(cuò)的api,可以編寫對(duì)應(yīng)的單元測(cè)試用例,使用@ActiveProfiles("uat")指定到測(cè)試環(huán)境,就可以根據(jù)測(cè)試提供的參數(shù)快速定位問題。示例:

          /**
           * 新增菜單api聯(lián)調(diào)
          */

          @RunWith(SpringRunner.class)
          @SpringBootTest(classes 
          = MallSystemApplication.class)
          @Slf4j
          @AutoConfigureMockMvc
          @ActiveProfiles("uat")
          public class AdminMenuControllerTest extends BaseTest 
          {
           
          /**
           * 成功新增菜單
          */

          @Test
          public void success2save() throws Exception {
           
          String token="Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjhjMjhlZWEzLTA5MWEtNDA1OS1iMzliLTRjOGMyNGY4ZjEzMiJ9.xK9srWjeGaq4NXt4BzG2MQ_yN9IaYtPVjKj5MoSS4bX9Ytf1XJNe_NSupR0IItkB48G6mXVZwj5CIwWIYzvsEA";
              String paramJson="{
                  "
          name":"mayuan",
                  "
          parentId":"1",
                  "
          orderNum":"1",
                  "
          type":"1",
                  "
          visible":true,
                  "
          url":"https:baidu.com",
                  "
          method":2,
                  "
          perms":"user:reset-pwd"
             }"
          ;
             // 發(fā)起http請(qǐng)求
             MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
                          .post("/admin/menu/add")
                          .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
                          .content(paramJson)
                          .accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
                          .header(GlobalConstant.AUTHORIZATION_HEADER, token))
                          .andExpect(MockMvcResultMatchers.status().isOk())
                          .andDo(MockMvcResultHandlers.print())
                          .andReturn();
              }
          }

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

               
          點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取
          瀏覽 51
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  最爽亂倫刺激對白1 | 蘑菇视频91 | 亚洲激情一区二区三区 | 五月丁香中文 | 日本国产区 |