在intellij越来越普及的情况下,利用JUnit在intellij中进行测试就显得很基础了,但网上的资料总有误导的地方,这里记录一下。
总体而言,要开始单元测试,可以分为三步,添加相关的插件,添加相关的依赖,编写测试方法,下面依序说下。
一、添加相关的插件
在intellij中利用JUnit进行测试,需要三个插件,Junit,用来执行测试用例,JUnitGenerator V2.0,用来生成测试用例,Coverage,用来生成测试报告。
安装插件完毕,还需要对JUnit进行适当的设置:
Junit Generator设置
Setting --》 Other Setting--》 Junit Generator
更改输出路径,
Output Path:
${SOURCEPATH}/../../test/java/${PACKAGE}/${FILENAME}
更改默认单元测试框架,
Default Template:
Junit 4
更改JUnit4的默认模板,
Junit 4
test. $entry.packageName $entry.packageName
$date
$today
二,添加相关的依赖
在maven项目中,添加如下的依赖:
三、开始编写测试方法
1.步骤
1)查看项目的project structure,确保:
Source Folders: src\main\java
Test Source Folders: src\test\java
2)在被测试的类中,选择 generate -> JUnit Test -> JUnit 4
3)在生成的测试类中编写相关的测试代码
四、JUnit的注解
1.断言
编写单元测试方法的一个根本不同在于要用断言来表达测试是否通过。
@Test public void testAdd() throws Exception { int value = new Util().add(2,4); Assert.assertEquals(6,value); } org.junit.Assert assertTrue(String message, boolean condition) assertEquals(String message, Object expected, Object actual) assertArrayEquals(String message, Object[] expecteds, Object[] actuals) assertNull(String message, Object object) assertSame(String message, Object expected, Object actual) assertThat(T actual, Matcher super T> matcher)
JUnit提供了各式各样的断言供使用,选择合适的即可。
2.@test的子属性
如果要测试异常或者时间,则可以利用@test注解的子属性。
excepted属性,异常测试 @Test (expected = Exception.class) public void testDivideException() throws Exception { new Junit_Test().divide(3,0); fail("除数为零没有抛出异常"); } timeout属性,超时测试
@Test (timeout = 1000) public void testDivideTimeout() throws Exception { new Junit_Test_Demo().divide(6,3); }
3.常用注解
@Test,标明是一个测试方法
@Before,在每个测试方法执行前都执行
@After,在每个测试方法执行完都执行
@BeforeClass,在测试类一开始就执行
@AfterClass,在测试类执行完毕执行
@Ignore,暂时忽略这个测试方法
4.高级注解
Rule:可以用来扩展JUnit的功能,改变测试方法的行为;
@ClassRule,类级别,执行测试类的时候只调用一次被注解的Rule
@Rule,方法级别,每个测试方法执行的时候都会调用被注解的Rule
内置的Rule
TemporaryFolder,创建临时目录或文件
ExternalResource,在测试之前创建资源,并在测试完成后销毁
TestName ,获取目前测试方法的名字
TestWatcher ,在每个触发点执行自定义的逻辑
Verifier ,在测试执行完成之后做一些校验,以验证测试结果是不是正确
ErrorCollector ,收集多个错误,并在测试执行完后一次过显示出来
@RunWith:默认BlockJunit4ClassRunner,可以指定特殊的Runner;
Suit,一次执行多个类的测试用例
Parameterized,批量指定多个待测参数
Category,对测试类中的被测试方法进行分类执行
Theories,为待测方法提供一组参数的排列组合
五,Mock
说到单元测试,就不能不提Mock,常用的mock框架很多,比较多用的是mockito和powermock。
Mockito
Mock技术框架,能让我们隔离外部依赖以便对我们自己的业务逻辑代码进行单元测试,在编写单元测试时,不需要再进行繁琐的初始化工作,在需要调用某一个接口时,直接模拟一个假方法,并任意指定方法的返回值。 Mockito的工作原理是通过创建依赖对象的proxy,所有的调用先经过proxy对象,proxy对象拦截了所有的请求再根据预设的返回值进行处理。Mockito对于Java接口使用接口代理的方式来模拟,对于Java类使用继承的方式来模拟(也即会创建一个新的Class类)。
PowerMock
PowerMock则在Mockito原有的基础上做了扩展,通过修改类字节码并使用自定义ClassLoader加载运行的方式来实现mock静态方法、final方法、private方法、系统类的功能。 从两者的项目结构中就可以看出,PowerMock直接依赖于Mockito,所以如果项目中已经导入了PowerMock包就不需要再单独导入Mockito包,如果两者同时导入还要小心PowerMock和Mockito不同版本之间的兼容问题。
1.流程
Mockito Mock mock(Class classToMock); mock(Class classToMock, String name) Stub when(mock.someMethod()).thenReturn(value) when(mock.someMethod()).thenThrow(new RuntimeException) when(mock.someMethod()).thenAnswer() exec
首先要利用mock来构造依赖,其次利用when语句来构造stub,然后就可以执行测试方法了。
其实还可以利用spy来构造依赖,但与mock构造有不同的地方:
mock
对于未指定处理规则的调用会按方法返回值类型返回该类型的默认值(如int、long则返回0,boolean则返回false,对象则返回null,void则什么都不做)
spy
未指定处理规则时则会直接调用真实方法
2.最佳实践
如何进行单元测试,每个公司都有不同的做法,一些最佳实践总结如下:
AIR原则
automatic,必须使用断言,禁止使用输出进行验证
independent,UT之间没有调用关系和前后关系
repeatable,不与外界环境耦合,可随时执行
粒度
只对单个的类进行测试,不检查跨类或跨系统的交互
只对公开的接口进行测试
维护
新增代码需要补充单元测试
变更代码需要修正单元测试
编写单元测试的BCDE原则
border,边界测试,特殊取值、特殊时间点、数据顺序等
correct,正确的输入,并得到预期的结果
design,与设计文档结合,编写单元测试
error,强制错误信息输入(非法数据,异常流程),并得到预期结果
覆盖率
语句覆盖率达到70%,分支覆盖率达到100%
3.利用Mock的一个例子
假如有person,dao和service三个类,其中dao是个接口,service依赖于dao,具体如下:
public class Person { private final int id; private final String name; public Person(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public String getName() { return name; } }
public interface IPersonDao { Person getPerson(int id); boolean update(Person person); }
public class PersonService { private final IPersonDao personDao; public PersonService(IPersonDao personDao) { this.personDao = personDao; } public boolean update(int id, String name) { Person person = personDao.getPerson(id); if (person == null) { return false; } Person personUpdate = new Person(person.getId(), name); return personDao.update(personUpdate); } }
利用mock来测试
1 import org.junit.Test; 2 import org.junit.Before; 3 import org.junit.After; 4 import static org.junit.Assert.*; 5 import static org.mockito.Mockito.*; 6 7 import org.mockito.Mock; 8 import org.mockito.Mockito.*; 9 import org.mockito.MockitoAnnotations; 10 11 import java.util.List; 12 13 /** 14 * PersonService Tester. 15 * 16 * @author17 * @since 07/01/201918 * @version 1.0 19 */ 20 public class PersonServiceTest { 21 22 private IPersonDao mockDao; 23 private PersonService personService; 24 25 @Before 26 public void before() throws Exception { 27 mockDao = mock(IPersonDao.class); 28 when(mockDao.getPerson(1)).thenReturn(new Person(1,"mst")); 29 when(mockDao.update(isA(Person.class))).thenReturn(true); 30 31 personService = new PersonService(mockDao); 32 } 33 34 @After 35 public void after() throws Exception { 36 } 37 38 /** 39 * 40 * Method: update(int id, String name) 41 * 42 */ 43 @Test 44 public void testUpdate() throws Exception { 45 boolean result = personService.update(1,"md"); 46 47 assertTrue(result); 48 49 //验证是否执行过一次getPerson(1) 50 verify(mockDao,times(1)).getPerson(eq(1)); 51 //验证是否执行过一次update 52 verify(mockDao,times(1)).update(isA(Person.class)); 53 } 54 55 @Test 56 public void testUpdateNotFind() throws Exception { 57 boolean result = personService.update(2, "md"); 58 59 assertFalse( result); 60 61 //验证是否执行过一次getPerson(1) 62 verify(mockDao, times(1)).getPerson(eq(2)); 63 //验证是否执行过一次update 64 verify(mockDao, never()).update(isA(Person.class)); 65 } 66 67 68 @Mock 69 List list; 70 71 public PersonServiceTest(){ 72 MockitoAnnotations.initMocks(this); 73 } 74 75 @Test 76 public void testList(){ 77 when(list.add(isA(Object.class))).thenReturn(true); 78 list.add(2); 79 list.add(5); 80 boolean re = list.add(1); 81 assertTrue(re); 82 } 83 84 }
其中演示了两种mock对象的构造,一种是手动构造,如22和27行,一种是注解构造,如69和72行。
在before方法中进行了依赖的mock和stub操作,进而在testUpdate中利用相应的依赖和stub进行测试逻辑的执行。