单元测试
最近由于实习的缘故,需要接触单元测试,但是由于在学校的时候没有系统的学习过软件测试,故以此文章作为自己学习单元测试的总结。
一.单元测试的基本概念
根据维基百科:单元测试(Unit Testing)又称为模块测试,是针对软件设计的最小单位进行正确性检验的测试工作。简单来说,就是隔离程序,并证明单个部件是正确的,符合设计目标的。单元测试是保证程序可靠性的重要手段,毕竟每一个程序员在写程序的时候都有可能犯错,通过写单测,来自测程序的可靠性。
在面向过程编程中,单元是单个程序、函数或者过程;在面向对象编程中,单元是指方法。在大型软件过程中,也是针对单个函数进行单元测试。
通常来说,程序员每修改一次,就会执行最少一次单元测试以保证自己编写的代码的可靠性。单元测试是软件可靠性和系统进度的重要方式,通过单元测试能够较早地发现程序中的错误,避免低级错误延迟到QA阶段,影响项目的整体进展。
通常来说,单元测试应该由RD完成,QA负责更大层次的测试。
二.单元测试的好处
很多程序员的实际工作中很少写单测,认为写单测浪费时间,而且测试应该是QA的工作,但是实际上好的单测不仅不会浪费,反而会大大节省我们的时间。大部分公司都需要进行严格单测。
我将列举几个单测的好处
-
减少bug
单元测试的目的就是通过足够准确的测试用例保证代码逻辑的正确性。所以在单测的过程中,一般是能够较早地解决一些bug的。单测也能够让QA的同学能够将工作重心花在系统的集成测试上,提升团队的整体效率。
-
减少修复bug的成本
一般修复bug都需要先定位问题再解决问题,如果没有单测的情况下,是需要通过debug单步来定位问题的,这样就会导致debug的效率降低。
-
帮助重构,提高重构的成功率
在有单测的情况下,开发人员能够很方便的重构代码,重构之后只需要做一遍单元测试就可以检验重构后的代码是否正确。
-
提升开发速度
单测虽然表面上占用的RD的时间,降低了RD的效率,但是实际上单测能够显著提升后期debug的效率。
三.单元测试的示例
我将在Spring Boot项目中利用Spring Boot+Junit框架实现一个单测示例。完整项目地址:Daily-Code-Java/union-test-demo at master · qiushuang139/Daily-Code-Java (github.com)
-
添加项目依赖
项目需要添加的依赖包括junit、Spring-Boot-starter-test等。完整依赖如下:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> </dependency> </dependencies>
-
编写待测试的Controller
这一步较为简单,简单的Controller即可。
@RequestMapping("/demo") @RestController public class DemoController { @RequestMapping() public String test(){ return "hello world"; } }
-
编写测试用例以及数据准备
创建测试类有两种方法:
-
在将要编写单测的类中点击鼠标右键
-
在test文件夹添加
创建完成测试类后再编写测试用例即可,代码如下:
@RunWith(SpringRunner.class) @SpringBootTest(classes = UnionTestDemoApplication.class)// public class DemoControllerTest { private MockMvc mockMvc; @Autowired private WebApplicationContext webApplicationContext; @BeforeClass public static void testDemoControllerBeforeClassInitialization(){ System.out.println("所有测试用例执行前被执行,用于初始化"); } @AfterClass public static void testDemoControllerAfterClassDestroy(){ System.out.println("所有测试用例被执行后被执行,用户数据清理"); } @Before public void testDemoControllerBefore(){ mockMvc= MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); System.out.println("测试函数调用前被执行,一般用于数据的准备工作"); } @Test //测试用例1 不应该通过 //@Transactional//如果有数据库等操作,但是又不希望对数据库造成影响,可以使用此注解,测试用例执行完后,自动回滚 public void testDemoController1() throws Exception { String result=mockVisit("/demo"); boolean tag=result.contains("haaaa"); Assert.assertTrue(tag); } @Test //测试用例2,应该通过 public void testDemoController2()throws Exception{ String result=mockVisit("/demo"); boolean tag=result.contains("hello world"); Assert.assertTrue(tag); } @After public void testDemoControllerAfter(){ System.out.println("测试函数调用后被执行,一般执行一些测试后的清理工作"); } //使用MockMVC模拟访问访问Controller,并返回数据 public String mockVisit(String url){ try { ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.get(url)); MvcResult mvcResult = resultActions.andDo(MockMvcResultHandlers.print()).andExpect(MockMvcResultMatchers.status().isOk()).andReturn(); return mvcResult.getResponse().getContentAsString(); }catch (Exception ex){ ex.printStackTrace(); return null; } } }
运行测试结果:
-
符合预期,测试用例1不能用过,测试用例2能够通过。
四.总结
在软件工程中,进行单元测试是一种良好的习惯,单元测试不仅能够保证软件的可靠性,也可以在单元测试的编写中,锻炼程序员的思维能力。