单元测试实战

什么是单元测试?先别管她是什么,也别急着甩锅给测试同学,我这里告诉你,单元测试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?

猜你喜欢

转载自blog.csdn.net/qq_26409257/article/details/81538026
今日推荐