美文网首页
单元测试

单元测试

作者: OPice | 来源:发表于2019-11-07 08:42 被阅读0次

  每个开发人员都写过很多代码、函数,但是你能保证你写的每个函数都能执行并且正常吗?
  我们太多时间站在功能需求的角度来审视我们的代码,认为需求实现功能逻辑正常,我们就完成了自己的使命。功能逻辑固然重要这个也是我们的目标。但是仅此而已吗,首先作为开发人员要知道,代码的终极目标有两个:实现需求保证逻辑正常保证代码质量和可维护性。测试人员只能帮助我们查漏需求是否完整实现,对于代码质量和可维护性是需开发自己保证的,所以单元测试必不可少。

JUnit

  测试驱动开发,所谓测试驱动开发,就是先写接口- >在写测试->写实现->运行测试。当然这是一种理想情况,大多数我们在开发中还是先写实现,后写测试代码。

  • 避免为单元测试写测试,单元测试必须非常简单
  • 单元测试不能相互依赖,可以独立运行
  • 除了必要的覆盖测试用例,还要注意一些临界值 比如:null、0、“” 等

JUnit 5的使用:
maven依赖:

<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.5.2</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>5.5.2</version>
            <scope>test</scope>
        </dependency>

1、 断言

  public Integer add(int a, int b){
       return a + b;
   }
//对add方法的单元测试
    @Test
    public void testAdd() {
        assertEquals(3, add(2, 1));
        assertEquals(0, add(0, 0));
        assertEquals(0, add(1, -1));
    }

断言的方法:assertTrue()、assertFalse()、assertNull()、assertNotNull()、assertEquals()、assertNotEquals()\assertArrayEquals()、assertThrows() ......

注意:测试异常使用assertThrows

2、 初始化资源

方法注解 描述
@BeforeEach 单个方法之前
@BeforeAll 所有测试方法之前
@AfterEach 单个测试方法之后
@AfterAll 所有测试方法之后

3、 条件测试

@Test
@EnabledIfEnvironmentVariable(named = "DEBUG", matches = "true")
public void testAddOnlyDebug() {
        assertEquals(3, add(2, 1));
        assertEquals(0, add(0, 0));
        assertEquals(0, add(1, -1));
    }
只有 DEBUG = true时才会执行

其他条件判断注解:

  • @EnabledIf 可以执行任何java语句,比如:@EnabledIf("System.currentTimeMillis()>1573044591641")

其他不经常使用:

  • @DisabledOnOs:操作系统有关
  • @DisabledOnJre:jre环境有关

4、 参数化测试

   @ParameterizedTest
   @MethodSource
    public void testAdd(int expect, int val1, int val2) {
        assertEquals(expect, add(val1, val2));
    }

    public Integer add(int a, int b) {
        return a + b;
    }

    static List<Arguments> testAdd() {
        return List.of(Arguments.arguments(3, 2, 1), Arguments.arguments(2, 1, 1));

    }
  • @MethodSource 使用方法传参数
  • @ValueSource 直接将参数显示 @ValueSource(ints = { -1, -5, -100 })
  • @CsvSource 每一个字符串表示一行,一行包含的若干参数用,分隔。
    比如:@CsvSource({ "abc, Abc", "APPLE, Apple", "gooD, Good" })
  • @CsvFileSource 单独的csv文件提供
    @CsvFileSource(resources = { "/test.csv" })

Mock

引入maven依赖

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

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

<!-- build tool -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.4</version>
    <scope>provided</scope>
</dependency>

mock 一个简单对象

@Getter
@Setter
public class Event {
    private String name;
    private String type;
}
@Test
public void mockData(){
     //模拟对象
     Event event = Mockito.mock(Event.class);
     //当调用 event.getName() 时都返回name1
     //thenReturn 相似用法还有 thenThrow()、thenAnswer()、thenCallRealMethod()
     Mockito.when(event.getName()).thenReturn("name1");
     //断言判断 
     assertEquals("name1",event.getName());
}
当调用event.getName() 时返回 “name1”

Mockito 常用 API :

  • verify() 校验方法是否被调用
  • doThrow() 模拟抛出异常
    doThrow(new RuntimeException()).when(event).getName();
    当调用 event.getName() 时抛出RuntimeException
    
  • doAnswer()
      doAnswer(new Answer() {
              @Override
              public Object answer(InvocationOnMock invocation) throws Throwable {
                  Object[] arguments = invocation.getArguments();
                  String name =  (String) arguments[0];
                  if ("name".equals(name)){
                      return name;
                  }else{
                      throw  new RuntimeException();
                  }
              }
          }).when(event).setName(anyString());
          event.setName("name");
         当调用 event.setName("name") 只有参数是“name”时通过,其他值抛出异常
    
  • doNothing()
doNothing().when(event).setName("name");
event.setName("name");
什么都不做
还有一种情况:
doNothing().doThrow(new RuntimeException()).when(event).setName("name");
event.setName("name");
event.setName("name");
第一次什么都不做,第二次抛出异常
  • doReturn()
List list = new ArrayList<String>();
//Mockito.spy(Object)  用spy监控真实对象,设置真实对象行为
List spy = spy(list);
Mockito.when(spy.get(0)).thenReturn("hello");
//当调用spy.get(0)时会调用真实对象的get(0)函数,此时会发生IndexOutOfBoundsException异常,因为真实List对象是空的
//所以需要doReturn
doReturn("hello").when(spy).get(0);
  • doCallRealMethod()
Event mock = mock(Event.class);
doCallRealMethod().when(mock).getString("name");
// 调用event.getString()的真实现
mock.getString("event");
  • inOrder()
Event mock = mock(Event.class);
mock.setName("name");
mock.setName("event");

InOrder inOrder = inOrder(mock);
inOrder.verify(mock).setName("name");
inOrder.verify(mock).setName("event");

// 验证mock.setName("name");  mock.setName("event"); 执行顺序,
/**inOrder.verify(mock).setName("event"); 
inOrder.verify(mock).setName("name");**/
//报错
  • times()
//验证 getName调用次数 是否2次
verify(mock, times(2)).getName();
  • only()
//只被调用一次
verify(mock, only()).getName();

简化Mock的写法: @Mock private Event event;

Spring-test

maven依赖

<!-- 版本号和你spring的保持一致 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <scope>test</scope>
</dependency>

Demo

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = EventServiceTest.EventServiceConfig.class)
public class EventServiceTest {
    @Autowired
    private EventService eventService;

    /**
     * 测试 eventService.findEventById 方法
     */
    @Test
    public void findEventById(){
        Assert.assertNotNull(eventService.findById(2));
        Assert.assertEquals(eventService.findById(2).getAppCode(),"appCode");

    }



    @Configuration
    static class EventServiceConfig {
        @Bean
        public EventService eventService(EventMapper eventMapper){
            EventServiceImpl eventService = new EventServiceImpl();
            eventService.setEventMapper(eventMapper);
            return eventService;
        }

        @Bean
        public EventMapper eventMapper(){
           //mock EventMapper
            EventMapper mock = Mockito.mock(EventMapper.class);
            Event event = new Event();
            event.setAppCode("appCode");
            //当执行EventMapper.selectByPrimaryKey方法时返回 event
            Mockito.when(mock.selectByPrimaryKey(Mockito.any())).thenReturn(event);
            return mock;
        }
    }
}

@RunWith JUnit提供,表示用那种方式来执行这个测试,SpringRunner 由Spring-test提供
@ContextConfiguration 配置Spring容器的配置

上面栗子 是为了测试 eventService.findEventById 方法,发现eventService和依赖EventMapper都是由Spring 容器注入,使用spring-test提供的测试。
将依赖的EventMapper依赖Mock,因为我们主要测试的是eventService.findEventById的逻辑。而且不要使用公共配置,保持每个单元测试之间相互独立,在测试时依赖的Bean越多说明逻辑越复杂,就需要将代码重构。

Spring-boot-test

maven依赖

<!-- 和你的spring版本保持一致 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-test</artifactId>
    <scope>test</scope>
</dependency>

Demo

@RunWith(SpringRunner.class)
@SpringBootTest(classes = EventServiceSpringBootTest.Config.class)
public class EventServiceSpringBootTest {
    @MockBean
    private EventMapper eventMapper;

    @Autowired
    private EventService eventService;

    @Before
    public void init(){
        Event event = new Event();
        event.setAppCode("appCode");
        Mockito.when(eventMapper.selectByPrimaryKey(Mockito.any())).thenReturn(event);
    }

    @Test
    public void selectByIdTest(){
        Assert.assertNotNull(eventService.findById(2));
        Assert.assertEquals(eventService.findById(2).getAppCode(),"appCode");
    }


    @Configuration
    static class Config {
        @Bean
        public EventService eventService(EventMapper eventMapper) {
            EventServiceImpl eventService = new EventServiceImpl();
            eventService.setEventMapper(eventMapper);
            return eventService;
        }
    }
}

这里多加了一个@MockBean 用来帮助mock一个对象。

测试覆盖率

  单元测试覆盖率只是一个跑分,这个不是我们最终要追求的目标。还是那句话,做单元测试不仅仅是为了完成政治任务,或者一个好看的报告。做单元测试是为了提升代码的质量和架构,不要为了做单元测试而做单元测试。

IDea工具
右键测试类


image.png image.png

参考:Mockito 中文文档 ( 2.0.26 beta )

相关文章

网友评论

      本文标题:单元测试

      本文链接:https://www.haomeiwen.com/subject/ymxtbctx.html