TA的每日心情 | 无聊 5 天前 |
---|
签到天数: 77 天 连续签到: 1 天 [LV.6]测试旅长
|
2#
楼主 |
发表于 2018-6-15 15:31:58
|
只看该作者
- <p>步骤 2: 添加java源码 </p><p>
- </p><p> 类Person.java:</p><p>
- </p><p>[java] view plain copy</p><p>package mockitodemo; </p><p> </p><p>public class Person </p><p>{ </p><p> private final Integer personID; </p><p> private final String personName; </p><p> public Person( Integer personID, String personName ) </p><p> { </p><p> this.personID = personID; </p><p> this.personName = personName; </p><p> } </p><p> public Integer getPersonID() </p><p> { </p><p> return personID; </p><p> } </p><p> public String getPersonName() </p><p> { </p><p> return personName; </p><p> } </p><p>} </p><p> 接口PersonDAO.java:</p><p>[java] view plain copy</p><p>package mockitodemo; </p><p> </p><p>public interface PersonDao </p><p>{ </p><p> public Person fetchPerson( Integer personID ); </p><p> public void update( Person person ); </p><p>} </p><p> 类PersonService.java:</p><p>[java] view plain copy</p><p>package mockitodemo; </p><p> </p><p>public class PersonService </p><p>{ </p><p> private final PersonDao personDao; </p><p> public PersonService( PersonDao personDao ) </p><p> { </p><p> this.personDao = personDao; </p><p> } </p><p> public boolean update( Integer personId, String name ) </p><p> { </p><p> Person person = personDao.fetchPerson( personId ); </p><p> if( person != null ) </p><p> { </p><p> Person updatedPerson = new Person( person.getPersonID(), name ); </p><p> personDao.update( updatedPerson ); </p><p> return true; </p><p> } </p><p> else </p><p> { </p><p> return false; </p><p> } </p><p> } </p><p>} </p>
复制代码
步骤 3: 添加单元测试类.
接下来为类PersonService.java创建单元测试用例。我们使用JUnit 4.x和Mockito 1.9.5。可以设计测
试用例类PersionServiceTest.java为如下,代码中有详细注释说明:
- <p>
- </p><p>[java] view plain copy</p><p>package mockitodemo; </p><p> </p><p>import org.junit.After; </p><p>import org.junit.AfterClass; </p><p>import org.junit.Before; </p><p>import org.junit.BeforeClass; </p><p>import org.junit.Test; </p><p>import static org.junit.Assert.*; </p><p>import org.mockito.Mock; </p><p>import org.mockito.MockitoAnnotations; </p><p>import org.mockito.ArgumentCaptor; </p><p>import static org.mockito.Mockito.*; </p><p> </p><p>/** </p><p> * PersonService的单元测试用例 </p><p> * </p><p> * @author jackzhou </p><p> */ </p><p>public class PersonServiceTest { </p><p> </p><p> @Mock </p><p> private PersonDao personDAO; // 模拟对象 </p><p> private PersonService personService; // 被测类 </p><p> </p><p> public PersonServiceTest() { </p><p> } </p><p> </p><p> @BeforeClass </p><p> public static void setUpClass() { </p><p> } </p><p> </p><p> @AfterClass </p><p> public static void tearDownClass() { </p><p> } </p><p> </p><p> // 在@Test标注的测试方法之前运行 </p><p> @Before </p><p> public void setUp() throws Exception { </p><p> // 初始化测试用例类中由Mockito的注解标注的所有模拟对象 </p><p> MockitoAnnotations.initMocks(this); </p><p> // 用模拟对象创建被测类对象 </p><p> personService = new PersonService(personDAO); </p><p> } </p><p> </p><p> @After </p><p> public void tearDown() { </p><p> } </p><p> </p><p> @Test </p><p> public void shouldUpdatePersonName() { </p><p> Person person = new Person(1, "Phillip"); </p><p> // 设置模拟对象的返回预期值 </p><p> when(personDAO.fetchPerson(1)).thenReturn(person); </p><p> // 执行测试 </p><p> boolean updated = personService.update(1, "David"); </p><p> // 验证更新是否成功 </p><p> assertTrue(updated); </p><p> // 验证模拟对象的fetchPerson(1)方法是否被调用了一次 </p><p> verify(personDAO).fetchPerson(1); </p><p> // 得到一个抓取器 </p><p> ArgumentCaptor<Person> personCaptor = ArgumentCaptor.forClass(Person.class); </p><p> // 验证模拟对象的update()是否被调用一次,并抓取调用时传入的参数值 </p><p> verify(personDAO).update(personCaptor.capture()); </p><p> // 获取抓取到的参数值 </p><p> Person updatePerson = personCaptor.getValue(); </p><p> // 验证调用时的参数值 </p><p> assertEquals("David", updatePerson.getPersonName()); </p><p> // asserts that during the test, there are no other calls to the mock object. </p><p> // 检查模拟对象上是否还有未验证的交互 </p><p> verifyNoMoreInteractions(personDAO); </p><p> } </p><p> </p><p> @Test </p><p> public void shouldNotUpdateIfPersonNotFound() { </p><p> // 设置模拟对象的返回预期值 </p><p> when(personDAO.fetchPerson(1)).thenReturn(null); </p><p> // 执行测试 </p><p> boolean updated = personService.update(1, "David"); </p><p> // 验证更新是否失败 </p><p> assertFalse(updated); </p><p> // 验证模拟对象的fetchPerson(1)方法是否被调用了一次 </p><p> verify(personDAO).fetchPerson(1); </p><p> // 验证模拟对象是否没有发生任何交互 </p><p> verifyZeroInteractions(personDAO); </p><p> // 检查模拟对象上是否还有未验证的交互 </p><p> verifyNoMoreInteractions(personDAO); </p><p> } </p><p> </p><p> /** </p><p> * Test of update method, of class PersonService. </p><p> */ </p><p> @Test </p><p> public void testUpdate() { </p><p> System.out.println("update"); </p><p> Integer personId = null; </p><p> String name = "Phillip"; </p><p> PersonService instance = new PersonService(new PersonDao() { </p><p> </p><p> @Override </p><p> public Person fetchPerson(Integer personID) { </p><p> System.out.println("Not supported yet."); </p><p> return null; </p><p> } </p><p> </p><p> @Override </p><p> public void update(Person person) { </p><p> System.out.println("Not supported yet."); </p><p> } </p><p> }); </p><p> boolean expResult = false; </p><p> boolean result = instance.update(personId, name); </p><p> assertEquals(expResult, result); </p><p> // TODO review the generated test code and remove the default call to fail. </p><p> fail("The test case is a prototype."); </p><p> } </p><p>} </p>
复制代码
这里setUpClass()、tearDownClass()、setUp()、tearDown()称为测试夹具(Fixture),就是测试运
行程序(test runner)在运行测试方法之前进行初始化、或之后进行回收资源的工作。JUnit 4之前是通
过setUp、tearDown方法完成。在JUnit 4中,仍然可以在每个测试方法运行之前初始化字段和配置环境
,当然也是通过注解完成。在JUnit 4中,通过@Before标注setUp方法;@After标注tearDown方法。在一
个测试类中,甚至可以使用多个@Before来注解多个方法,这些方法都是在每个测试之前运行。说明一
点,一个测试用例类可以包含多个打上@Test注解的测试方法,在运行时,每个测试方法都对应一个测
试用例类的实例。@Before是在每个测试方法运行前均初始化一次,同理@Ater是在每个测试方法运行
完毕后均执行一次。也就是说,经这两个注解的初始化和注销,可以保证各个测试之间的独立性而互不
干扰,它的缺点是效率低。另外,不需要在超类中显式调用初始化和清除方法,只要它们不被覆盖,测
试运行程序将根据需要自动调用这些方法。超类中的@Before方法在子类的@Before方法之前调用(与
构造函数调用顺序一致),@After方法是子类在超类之前运行。
这里shouldUpdatePersonName()、shouldNotUpdateIfPersonNotFound()和testUpdate()都是测试P
ersonService的update()方法,它依赖于PersonDao接口。前两者使用了模拟测试。testUpdate()则没有
使用模拟测试。下面是测试结果:
图4 测试结果点击打开链接
可以看出,使用模拟测试的两个测试成功了,没有使用模拟测试的testUpdate()失败。对于模拟
测试,在测试用例类中要先声明依赖的各个模拟对象,在setUp()中用MockitoAnnotations.initMocks()
初始化所有模拟对象。在进行模拟测试时,要先设置模拟对象上方法的返回预期值,执行测试时会调
用模拟对象上的方法,因此要验证这些方法是否被调用,并且传入的参数值是否符合预期。对于testU
pdate()测试,我们需要自己创建测试PersonService.update()所需的所有PersonDao数据,因为我们只
知道公开的PersonDao接口,其具体实现类(比如从数据库中拿真实的数据,或写入到数据库中)可
能由另一个团队在负责,以适配不同的数据库系统。这样的依赖关系无疑使单元测试比较麻烦,而要
拿真正PersonDao实现来进行测试,那也应该是后期集成测试的任务,把不同的组件集成到一起在真
实环境中测试。有了模拟测试框架,就可以最大限度地降低单元测试时的依赖耦合性。
|
|