zoukankan      html  css  js  c++  java
  • 浅谈单元测试

    单元测试

    软件测试按照阶段可分为单元测试、集成测试、系统测试以及验收测试,今天我们要介绍的就是单元测试。

    阶段 测试对象 测试人员 测试方法
    单元测试 编码后 最小单位程序模块 软件开发人员 白盒测试
    集成测试 单元测试之后 组装后的模块 软件开发人员 灰盒测试
    系统测试 集成测试之后 已经集成好的软件系统 测试人员 黑盒测试
    验收测试 系统测试之后 整个系统 测试人员 黑盒测试

    1、什么是单元测试?

    首先我们要先了解一下什么是单元,单元就是指人为规定的最小的被测功能模块,在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。而单元测试则是对这些 ” 单元 “ 进行检测,看功能是否正确。单元测试的编写人员是软件开发人员。

    2、单元测试的作用和局限性有哪些呢?

    从测试金字塔中我们可以发现,越是底层的测试,牵扯到相关内容越少,而高层测试则涉及面更广,因此bug发现的越晚,修改的成本就越高。比如单元测试,它的关注点只有一个单元,而没有其它任何东西。所以单元测试修改bug的成本是最小的。进行单元测试具有一下作用:

    • 提高代码质量

    • 提升开发效率

    • 降低开发成本

    单元测试的局限性:

    • 单元测试只测试程序单元自身的功能。因此,它不能发现集成错误、性能问题、或者其他系统级别的问题。

    3、好的单元测试时什么样的?

    • 正确清晰:有利于帮助其他开发者理解代码逻辑,理解如何使⽤相关的类或者函数。

    • 完整:良好设计的单元测试案例覆盖程序单元分支和循环条件的所有路径。好的单元测试完备⽽不重复。同样的测试场景,或者同类型的测试输⼊不要写多个单元测试,找⼀个有代表性的场景输⼊就可以了。

    • 健壮:当被测试的类或者函数被修改内部实现或者添加功能时,⼀个好的单测应该完全不需要被修改或者只有极少的修改。

    4、单元测试的步骤

    单元测试的代码结构⼀般一个三步经典结构:准备,调⽤,断⾔。

    • 准备:⽬的是准备好调⽤所需要的外部环境,如数据,Stub,Mock,临时变量,调⽤请求,环境背景变量等等。

    • 调⽤:实际调⽤需要测试⽅法,函数或者流程。

    • 断⾔:判断调⽤部分的返回结果是否符合预期。

    测试框架JUnit

    JUnit是用于编写可复用测试集的简单框架,是xUnit的一个子集。xUnit是一套基于测试驱动开发的测试框架,有PythonUnit、CppUnit、JUnit等。

    1、常用注解的使用

    注解 说明
    @Before 初始化方法
    @After 释放资源
    @Test 测试方法,在这里可以测试期望异常和超时时间
    @Ignore 忽略的测试方法
    @BeforeClass 针对所有测试,只执行一次,且必须为static void
    @AfterClass 针对所有测试,只执行一次,且必须为static void
    @RunWith 指定测试类使用某个运行器
    @Parameters 指定测试类的测试数据集合
    @Rule 允许灵活添加或重新定义测试类中的每个测试方法的行为
    @FixMethodOrder 指定测试方法的执行顺序
    public class ClassNameTest {
        @BeforeClass
        public static void beforeClass() throws Exception {
            System.out.println("测试类执行之前执行,主要用来初使化公共资源等");
        }
    
        @AfterClass
        public static void afterClass() throws Exception {
            System.out.println("测试类执行之后执行,主要用来释放资源或清理工作");
        }
    
        @Before
        public void setup() throws Exception {
            System.out.println("测试方法执行之前执行");
        }
    
        @After
        public void teardown() throws Exception {
            System.out.println("测试方法执行之后执行");
        }
    
        @Test
        public void test() {
            System.out.println("测试方法");
        }
    
        @Test
        @Ignore("可以忽略这个方法")
        public void testIgnore() {
            System.out.println("测试忽略方法");
        }
    
        @Test(expected = ArithmeticException.class)
        public void divisionWithException() {
            System.out.println("测试异常");
            int i = 1 / 0;
        }
    
        @Test(timeout = 1000)
        public void infinity() {
            System.out.println("测试超时");
            while (true) ;
        }
    }
    

    2、常用断言

    断言 说明
    assertArrayEquals(expecteds, actuals) 查看两个数组是否相等。
    assertEquals(expected, actual) 查看两个对象是否相等。类似于字符串比较使用的equals()方法
    assertNotEquals(first, second) 查看两个对象是否不相等。
    assertNull(object) 查看对象是否为空。
    assertNotNull(object) 查看对象是否不为空。
    assertSame(expected, actual) 查看两个对象的引用是否相等。类似于使用“==”比较两个对象
    assertNotSame(unexpected, actual) 查看两个对象的引用是否不相等。类似于使用“!=”比较两个对象
    assertTrue(condition) 查看运行结果是否为true。
    assertFalse(condition) 查看运行结果是否为false。
    assertThat(actual, matcher) 查看实际值是否满足指定的条件
    fail() 让测试失败

    Mockit模拟测试框架

    Mockito 是一个强大的用于 Java 开发的模拟测试框架, 通过 Mockito 我们可以创建和配置 Mock 对象, 进而简化有外部依赖的类的测试.

    1、Mockito框架的好处

    • 可以很简单的虚拟出一个复杂对象(比如虚拟出一个接口的实现类);

    • 可以配置 mock 对象的行为;

    • 可以使测试用例只注重测试流程与结果;

    • 减少外部类、系统和依赖给单元测试带来的耦合。

    2、使用 Mockito 的大致流程如下

    • 创建外部依赖的 Mock 对象, 然后将此 Mock 对象注入到测试类中.

    • 执行测试代码.

    • 校验测试代码是否执行正确

    3、什么是mock?

    mock就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法,这个虚拟的对象就是mock对象。mock对象就是真实对象在调试期间的代替品。

    如图所示,ConsumableQueryService耗材查询服务依赖ConsumableTransfer和ConsumableRemoteService,现在需要测试耗材查询服务,一种方法是构建真实的 ConsumableTransfer,和ConsumableRemoteService 实例, 然后注入到 ConsumableQueryService 中,这样就违背了单元测试只测试程序单元自身的功能的原则,这时候我们就需要使用mock对象来进行替代了。替换后, 我们就可以对 ConsumableQueryService 进行测试, 并且不需要关注它的复杂的依赖。

    4、Mockito使用

    使用MockitoAnnotations模拟对象

    public class ConsumableQueryServiceTest {
        @InjectMocks
        private ConsumableQueryService consumableQueryService;
    
        @Mock
        private ConsumableTransfer consumableTransfer;
    
        @Mock
        private ConsumableRemoteService consumableRemoteService;
    
        @Before
        public void initMock() {
            MockitoAnnotations.initMocks(this);
            ConsumableBatchesQO qo = new ConsumableBatchesQO();
            qo.setChannelId(1);
            qo.setWarehouseId(1);
            qo.setKeyword("耗材");
            qo.setCascaded(true);
            qo.setNeedOutOfDate(Boolean.FALSE);
            List<ConsumableBatchesInfo> consumableBatchesInfos = new ArrayList<>();
            ConsumableBatchesInfo batchesInfo = new ConsumableBatchesInfo();
            batchesInfo.setUnitId(-1);
            batchesInfo.setName("耗材测试");
            ConsumableClinicInfo channelInfo = new ConsumableClinicInfo();
            batchesInfo.setChannelInfo(channelInfo);
            consumableBatchesInfos.add(batchesInfo);
            when(consumableRemoteService.findBatches(qo)).thenReturn(consumableBatchesInfos);
            ConsumableBO consumableBO = new ConsumableBO();
            consumableBO.setId(batchesInfo.getUnitId());
            consumableBO.setName(batchesInfo.getName());
            when(consumableTransfer.toConsumableBO(consumableBatchesInfos.get(0))).thenReturn(consumableBO);
        }
    
        @Test
        public void testQueryByKeyWord() {
            List<ConsumableBO> consumables = consumableQueryService.queryByKeyWord(1, 1, "耗材");
            Assert.assertEquals(consumables.get(0).getName(), "耗材测试");
        }
    }
    

    使用MockitoJUnitRunner模拟对象

    @RunWith(MockitoJUnitRunner.class)
    public class ConsumableQueryServiceTest {
        @InjectMocks
        private ConsumableQueryService consumableQueryService;
    
        @Mock
        private ConsumableTransfer consumableTransfer;
    
        @Mock
        private ConsumableRemoteService consumableRemoteService;
    
        @Before
        public void initMock() {
            ConsumableBatchesQO qo = new ConsumableBatchesQO();
            qo.setChannelId(1);
            qo.setWarehouseId(1);
            qo.setKeyword("耗材");
            qo.setCascaded(true);
            qo.setNeedOutOfDate(Boolean.FALSE);
            List<ConsumableBatchesInfo> consumableBatchesInfos = new ArrayList<>();
            ConsumableBatchesInfo batchesInfo = new ConsumableBatchesInfo();
            batchesInfo.setUnitId(-1);
            batchesInfo.setName("耗材测试");
            ConsumableClinicInfo channelInfo = new ConsumableClinicInfo();
            batchesInfo.setChannelInfo(channelInfo);
            consumableBatchesInfos.add(batchesInfo);
            when(consumableRemoteService.findBatches(qo)).thenReturn(consumableBatchesInfos);
            ConsumableBO consumableBO = new ConsumableBO();
            consumableBO.setId(batchesInfo.getUnitId());
            consumableBO.setName(batchesInfo.getName());
            when(consumableTransfer.toConsumableBO(consumableBatchesInfos.get(0))).thenReturn(consumableBO);
        }
    
        @Test
        public void testQueryByKeyWord() {
            List<ConsumableBO> consumables = consumableQueryService.queryByKeyWord(1, 1, "耗材");
            Assert.assertEquals(consumables.get(0).getName(), "耗材测试");
        }
    }
    

    使用MockitoRule模拟对象

    public class ConsumableQueryServiceTest {
        @InjectMocks
        private ConsumableQueryService consumableQueryService;
    
        @Mock
        private ConsumableTransfer consumableTransfer;
    
        @Mock
        private ConsumableRemoteService consumableRemoteService;
    
        @Rule
        public MockitoRule rule = MockitoJUnit.rule();
    
        @Before
        public void initMock() {
            ConsumableBatchesQO qo = new ConsumableBatchesQO();
            qo.setChannelId(1);
            qo.setWarehouseId(1);
            qo.setKeyword("耗材");
            qo.setCascaded(true);
            qo.setNeedOutOfDate(Boolean.FALSE);
            List<ConsumableBatchesInfo> consumableBatchesInfos = new ArrayList<>();
            ConsumableBatchesInfo batchesInfo = new ConsumableBatchesInfo();
            batchesInfo.setUnitId(-1);
            batchesInfo.setName("耗材测试");
            ConsumableClinicInfo channelInfo = new ConsumableClinicInfo();
            batchesInfo.setChannelInfo(channelInfo);
            consumableBatchesInfos.add(batchesInfo);
            when(consumableRemoteService.findBatches(qo)).thenReturn(consumableBatchesInfos);
            ConsumableBO consumableBO = new ConsumableBO();
            consumableBO.setId(batchesInfo.getUnitId());
            consumableBO.setName(batchesInfo.getName());
            when(consumableTransfer.toConsumableBO(consumableBatchesInfos.get(0))).thenReturn(consumableBO);
        }
    
        @Test
        public void testQueryByKeyWord() {
            List<ConsumableBO> consumables = consumableQueryService.queryByKeyWord(1, 1, "耗材");
            Assert.assertEquals(consumables.get(0).getName(), "耗材测试");
        }
    }
    

    5、Mockit常用注解

    • @InjectMocks进行依赖注入

    • @Mock注解,我们用来初始化Mock对象

    • @Captor参数捕获器的注解

    • @Spy包装Java对象

    6、Mockit常用方法

    verify函数

    verify函数默认验证的是执行了times(1),也就是某个测试函数是否执行了1次.因此,times(1)通常被省略了。

    Method Meaning
    times(n) 次数为n,默认为1(times(1))
    never() 次数为0,相当于times(0)
    atLeast(n) 最少n次
    atLeastOnce() 最少一次
    atMost(n) 最多n次

    实列化虚拟对象

    @Before
        public void initMock() {
            ConsumableBatchesQO qo = new ConsumableBatchesQO();
            qo.setChannelId(1);
            qo.setWarehouseId(1);
            qo.setKeyword("耗材");
            qo.setCascaded(true);
            qo.setNeedOutOfDate(Boolean.FALSE);
            List<ConsumableBatchesInfo> consumableBatchesInfos = new ArrayList<>();
            ConsumableBatchesInfo batchesInfo = new ConsumableBatchesInfo();
            batchesInfo.setUnitId(-1);
            batchesInfo.setName("耗材测试");
            ConsumableClinicInfo channelInfo = new ConsumableClinicInfo();
            batchesInfo.setChannelInfo(channelInfo);
            consumableBatchesInfos.add(batchesInfo);
            //实例化虚拟对象
            when(consumableRemoteService.findBatches(qo)).thenReturn(consumableBatchesInfos);
            consumableRemoteService.findBatches(qo);
            consumableRemoteService.findBatches(qo);
            //校验次数
            verify(consumableRemoteService, times(2)).findBatches(qo);
            ConsumableBO consumableBO = new ConsumableBO();
            consumableBO.setId(batchesInfo.getUnitId());
            consumableBO.setName(batchesInfo.getName());
            when(consumableTransfer.toConsumableBO(consumableBatchesInfos.get(0))).thenReturn(consumableBO);
        }
    
  • 相关阅读:
    hdu2988:Dark roads(最小生成树)
    hdu1596:find the safest road(最短路)
    hdu1596:find the safest road(最短路)
    CultureInfo中重要的InvariantCulture
    c#通过反射获取类上的自定义特性
    分享我们项目中基于EF事务机制的架构 【转载】
    ASP.NET MVC3中的路由系统(Routes) .
    为ASP.NET MVC应用添加自定义路由
    Mvc生成页面之t4模板相关
    LINQ to SQL语句对应SQL的实现
  • 原文地址:https://www.cnblogs.com/AmyZheng/p/12622327.html
Copyright © 2011-2022 走看看