欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

单元测试实战

程序员文章站 2022-04-26 09:17:21
...

什么是单元测试?先别管她是什么,也别急着甩锅给测试同学,我这里告诉你,单元测试100%工作必须由开发人员来做。别转眼珠子,就是你,接盘吧,少侠!

第2章

直接上代码

有这么个函数1

       /**
       * 求和
       * @param x 输入Integer
       * @param y 输入Integer
       * @return 输出x+y
       */
       public Integer add(Integer x,Integer y){
              return x + y;
       }

你怎么保证这函数没问题呢?比如输入两个最大整数Integer.MAX_VALUE,返回的还正确吗?

来,我们验证一下,写这么个函数2

       @Test
       public void testAdd(){
              Integer x = 4;
              Integer y = 5;
              Integer z = add(x,y);
              assertNotNull(z);//断言z非空
              assertTrue((x+y)==z);//断言x+y=z
              try{
                     z = add(Integer.MAX_VALUE,Integer.MAX_VALUE);
                     fail("不允许输入值的和大于最大整数!"); //断言并抛出异常
              }catch(AssertionError e){
                     fail();//add应直接抛错,不应走这条路
              }catch(Exception e){}
       }

看到好多assert函数,是什么?断言,org.junit.Assert提供,用于判断结果预期,错误则抛出AssertionError

常用的有assertNotNullassertNullassertTrue, assertFalse,assertNotEquals,assertThat等

顾名思义assertNull就是参数是空,正常往下走,非空则抛错

还有个@Test,这又是啥?测试标记注解,被标明的函数运行时将被junit执行,稳如void main

常用的有@BeforeClass(全部方法执行前),@Before(test方法前),

@Test(测试方法),@Ignore(不执行的测试方法),@After(test后)

执行顺序BeforeClass-> Before->Test->After-> Before->Test->After->……

所以以上,咱需要  import static org.junit.Assert.*;

pom.xml增加

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

回头看!

函数2做了什么呢?

1)验证函数1基本正确性

x+y都不等于z还敢叫求和函数?

2)验证边界条件

任一输入过大或过小,和过大或过小,都是不行的

3)验证异常和错误处理

什么时候该抛,什么时候不该抛,得好好想想

为什么要做这些?

1)可控

人非圣贤,孰能无过,但交到甲方时候的工程不能出错啊,不然验收现场就是*会,产品同理

2)迎接变化

需求永远在变,周全而深远的考虑可以让我们敢于迎接变化

3)重构

没有单测的项目谁敢重构,你敢啊,不服墙就服你

       本章主题:一般单测怎么做的

第3章

       有这么个类3

       @Service
       public class BaseService{
              @Autowired
              BaseDao baseDao;
              public PO get(Long id){
                     PO po = getDao().get(id);
                     return po;
              }
              private BaseDao getDao() {
                     return baseDao;
              }
       }

       baseDao是需要spring注入进来,还是会有外部依赖,比如数据库,网络等,我想隔离开,分小部分测测

       那我就写了这么个类4       

       public class mock{
              @InjectMocks
              BaseService baseService;//模拟主体对象
              @Mock
              BaseDao baseDao;//模拟注入对象
              @Before
              public void setUp() throws Exception {
                     MockitoAnnotations.initMocks(this);//初始化注解模拟注入
              }
              @Test
              void testGet(){
                     PO po = new PO();
                     when(baseDao.get(anyLong())).thenReturn(po);//假定baseDao.get(id)返回vo,不往下层业务层延伸
                     PO result= baseService.get(id); //调了baseDao.get方法,会返回po,而不是往下调
                     assertNotNull(result);
                     assertEquals(result, po);
              }
       }

       看到@InjectMocks、@Mock注解,代表baseDao被注入到了baseService中。

       也可以自定义mock,如BaseDao baseDao = mock(BaseDao.class);

       when().then则是测试桩,用于模拟行为,可返回对象,也可以抛出异常

       anyLong()对应方法参数匹配Long,还有any(),anyFloat()等

       模拟的意义在于将业务逻辑阻隔小范围内,防止延伸到变化过多的区域,推进可维护、模块化。

所以以上,咱需要   import static org.mockito.Mockito.*;

pom.xml增加

    <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.9.5</version>
            <scope>test</scope>
    </dependency>

本章主题:模拟单测怎么做的

 

第4章

我怎么知道类3中的baseDao被spring注入成功呢?

我写了这么个函数5

 

       @Test
       public void testSpringBean(){
              ApplicationContext context =  newClassPathXmlApplicationContext("spring.xml");//获取spring上下文
              assertNotNull(context.getBean("baseDao"));//断言不为空
      }

手动取,好土,于是我换了一个类6

        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration(locations = {
              "classpath: spring.xml"
        })
        public class SpringTest {
               @Autowired
               BaseService baseService;
               @Test
               public void test(){
                   assertNotNull(baseService);//注入成功
                   assertNotNull(baseService.getDao());//baseDao注入成功
               }
        }

利用junit4才有的新功能,结合spring-test,优化了写法。

作为超类给其他需要测试spring注入的类用岂不是美滋滋?

所以以上,咱需要pom.xml增加

        <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-test</artifactId>
              <version>4.0.2.RELEASE</version>
        </dependency>

本章主题:spring注入怎么测

 

第5章

类3放回这里来看看

       @Service
       public class BaseService{
              @Autowired
              BaseDao baseDao;
              public PO get(Long id){
                     PO po = getDao().get(id);
                     return po;
              }
              private BaseDao getDao() {
                     return baseDao;
              }
       }

       我怎么确定baseDao.get(id)能正确从数据库取到数据呢?

       于是我改写了类6

        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration(locations = {
              "classpath: spring-test.xml"
        })
        @TransactionConfiguration
        @Transactional
        public class SpringTest {
           @Autowired
           BaseDao baseDao;
           @Test
           public void test(){
              assertNotNull(baseDao);//注入成功
              assertNotNull(baseDao.get(1L)); //获取id为1的记录
              assertEquals(baseDao.get(1L).getId(),1); //验证记录id
              assertEquals(baseDao.get(1L).getName(),”a”); //验证记录name
           }
      }

加了@Transactional等注解是要干什么呢?

利用数据的事务回滚达到单测而不影响数据库正常数据

在此之前还有些准备工作得做

spring-test.xml得少不同于原spring.xml

      可以采取改配置文件路径,也可以改变配置文件属性,为了和正常区分,最好自定义另外的配置文件

还需要增加jdbc命名空间

    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="http://www.springframework.org/schema/jdbc
    http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd">
        <!-- 初始化数据库表 -->
       <jdbc:initialize-database data-source="oracleSource" ignore-failures="NONE">
              <!—建表脚本-->
              <jdbc:scriptlocation="classpath:sql/init_table.sql"/>
              <!—数据脚本-->
              <jdbc:scriptlocation="classpath:sql/init_data.sql"/>
       </jdbc:initialize-database>

       初始化时建表填数据,然后就可以增删改查啦

       init_table.sql如下

    set names 'utf8';
    DROP TABLE IF EXISTS TABLENAME;
    CREATE TABLE TABLENAME (
       ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
       name varchar(50) DEFAULT NULL,
       PRIMARY KEY (ID)
    );

init_data.sql如下

    set names 'utf8';
    insert into tableName (name) values ('a')

数据库的可以选择mysql,新建个test数据库用于单测,也可以用H2内存数据库哦

本章主题:持久层怎么单测

 

第6章

有这么个类7

    @RestController
    @RequestMapping("/api/v1/test")
    public class BaseController {
              @Autowired
              BaseService baseService;
              @RequestMapping(value=" {id}",method=RequestMethod.GET)
              public Result get(@PathVariable Long id){
                     return new Result(baseService.get(id));
              }
    }

怎么验证url路径以及返回参数呢?

我就写了这么个类8      

    public class BasicControllerTest extends SpringTest{
       public MockMvc mockMvc;      
       @Before
       public void setUp() throws Exception {
              this.mockMvc =     MockMvcBuilders.standaloneSetup(controllerTest).build();//mvn模拟
       }
       @Test
       public void testGet() throws Exception {
                     String mediaType = "application/json;charset=UTF-8";
                     Long id = 1L;
                     String response = mockMvc.perform(
                            get("/api/v1/test/{id}",id)//get方法,url路径
                            .contentType(MediaType.parseMediaType(mediaType))//返回mime格式
                            .accept(MediaType.APPLICATION_JSON)//接受mime格式
                            )
                            .andDo(print())//打印
                            .andExpect(handler().handlerType(ControllerTest.class))//期望执行类
                            .andExpect(handler().methodName("get"))//期望执行方法
                            .andExpect(content().contentType(mediaType))//期望mime格式
                            .andExpect(status().isOk())//期望状态200
                            .andReturn().getResponse().getContentAsString();//返回响应数据
                     Boolean success = JsonPath.read(response, "$.success");//用jsonPath处理json节点数据
                     assertTrue(success);

       }

}

SpringTest类就是删除无关数据、带有很多注解的类6

mockMvc利用http请求模拟响应,并对响应进行期望验证

JsonPath语法是:

json:"{\"success\":true,\"data\":{\"id\":1,\"name\"=\"a\"},\"message\":null}"

"$.success"则是取true,"$.data.name"则是取"a"

以上,咱需要  

import staticorg.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

import staticorg.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import staticorg.springframework.test.web.servlet.result.MockMvcResultHandlers.*;

import com.jayway.jsonpath.JsonPath;

pom.xml增加

    <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path</artifactId>
            <version>2.4.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path-assert</artifactId>
            <version>2.4.0</version>
            <scope>test</scope>
    </dependency>

本章主题:控制层springMVC怎么单测

 

第1章

现在大概知道什么是单元测试了吧?

关键就是单元两个字,无限的细分单元,做到可测试,也就做到了模块化可维护。

下图是测试金字塔,单测占了测试工作的70%,如果不想继续

合并需求分支->发测->改bug->发测->改bug……,无限循环的话,就赶紧单测写起来

每次重构完,单测跑一圈全通过,那状态,比用了飘柔还自信啊。

单元测试实战

myeclipse,idea等IDE提供了快速写单测的方法哦

下面介绍myeclipse的,

对要测的类右键new JUnit Test Case,选New JUnit 4 test

Source Folder选src/test/java,package一样,Next->

选中要测的方法,完成则自动形成了测试类。当然,这只用于后补测试的情况。

单测接触久了,你就该玩TDD了。什么是TDD?

单元测试实战