测试驱动开发实践—从testList开始
【内容指引】
运行单元测试;
装配一条数据;
模拟更多数据测试列表;
测试无搜索列表;
测试标准查询;
测试高级查询。
一、运行单元测试
我们以文档分类(Category)这个领域类为例,示范如何通过编写测试用例来驱动代码开发。首先我们可以打开Category的单元测试初始化代码CategoryControllerTest.java:

上面是通过macOS操作系统下“IntelliJ IDEA”打开项目的界面,首先我们在“CategoryControllerTest”上点鼠标右键,选择Run 'CategoryControllerTest'来运行Category的单元测试类:

我们看到该单元测试类中的四个测试方法均未通过测试。第一个运行的测试方法是testList,所以,我们将首先从testList这个方法开始。

二、装配一条数据
从现在开始,我们在Category的单元测试类代码CategoryControllerTest中,从第一行代码开始,从上往下,根据“//TODO”的提示,逐步完成测试用例的编写。第一个“//TODO”的任务提示出现在@Before注解的setUp()方法中。该方法将会在后续每个测试方法(testList,testSave,testView,testDelete)运行前均会运行一次:
// 使用JUnit的@Before注解可在测试开始前进行一些初始化的工作
@Before
public void setUp() throws JsonProcessingException {
/**---------------------测试用例赋值开始---------------------**/
//TODO 参考实际业务中新增数据所提供的参数,基于"最少字段和数据正确的原则",将下面的null值换为测试参数
c1 = new Category();
c1.setProjectId(null);
c1.setName(null);
c1.setSequence(null);
c1.setCreatorUserId(1);
categoryRepository.save(c1);
/**---------------------测试用例赋值结束---------------------**/<span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 获取mockMvc对象实例</span>
mockMvc =<span style="line-height:1.5;"> MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}</span></pre><div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy" style="line-height:1.5;"><a title="复制代码" style="color:rgb(0,0,0);text-decoration:underline;border:none;" target="_blank"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border:none;"></a></span></div></div><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"> </p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083405374-327132711.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">在setUp()方法中,我们向数据库添加一条数据。默认为领域类Category中的所有字段赋值(如果含有审计字段,仅为创建者creatorUserId赋值)。代码如下:</p><div class="cnblogs_code" style="border:1px solid rgb(204,204,204);color:rgb(0,0,0);font-family:'Courier New';font-size:12px;"><div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy" style="line-height:1.5;"><a title="复制代码" style="color:rgb(0,0,0);text-decoration:underline;border:none;" target="_blank"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border:none;"></a></span></div><pre style="font-family:'Courier New';"> <span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 使用JUnit的@Before注解可在测试开始前进行一些初始化的工作</span>
@Before
public void setUp() throws JsonProcessingException {
/---------------------测试用例赋值开始---------------------/
c1 = new Category();
c1.setProjectId(1L);
c1.setName("文档分类一");
c1.setSequence(1);
c1.setCreatorUserId(1);
categoryRepository.save(c1);
/---------------------测试用例赋值结束---------------------/<span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 获取mockMvc对象实例</span>
mockMvc =<span style="line-height:1.5;"> MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}</span></pre><div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy" style="line-height:1.5;"><a title="复制代码" style="color:rgb(0,0,0);text-decoration:underline;border:none;" target="_blank"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border:none;"></a></span></div></div><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"> </p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><span style="color:rgb(51,153,102);"><span>最佳实践</span></span><br>在这里建议不要给所有字段赋值,而是<span>本着<span style="color:rgb(51,153,102);">最少字段</span>和<span style="color:rgb(51,153,102);">数据正确</span>的原则</span>。<br>所谓<span style="color:rgb(51,153,102);"><span>最少字段赋值原则</span></span>,是指,最终客户端添加数据的表单页面有几个字段是必须赋值,那么就给这几个字段赋值。以添加文档分类为例,form表单上需要给分类名称(name)和排序(sequence)赋值,但是提交表单时可能从Session或隐藏控件中提供该分类所属的项目(projectId)和操作人(operator)。所以这四个字段就是最少字段。如果表单中有一些字段是非必填字段,那么就不用赋值。</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">所谓<span style="color:rgb(51,153,102);"><span>数据正确原则</span></span>,是因为我们假设通过新增数据方法而插入数据库的数据都是合法数据,对于不合法数据的校验是新增数据方法的职责,而不是列表查询方法的职责。我们这里是通过JPA接口直接保存到数据库的,并未采用服务实现层的save方法。在文档分类这个业务中,正确的数据的基本要求:项目ID应该是大于0的Long型数据;文档名称不能为空,不能超过十位长度;排序应为大于0的整数,不能是字符,操作者ID应为大于0的数据。</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">现在再次运行测试,看testList的setUp()方法是否报错:</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083335239-1305683027.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083325543-1143482125.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><span style="color:rgb(51,153,102);"><span>异常分析</span></span><br>现在出错的代码是第108行,处于testList()方法体内,而setUp()方法运行在testList()方法之前。这也意味着setUp()方法没问题了。</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">如果我们给setUp()中输入了合理的值,但是该方法仍然出错该怎么做?<br>以我的经验,就到该领域类“Cagegory.java”中调整下各字段的默认值:</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><span style="color:rgb(51,153,102);"><span>最佳实践</span></span><br>一般领域类中的字段,对于非必填值的字段的处理方法:<br>日期型:允许null值即可;<br>布尔型:输入一个默认值,true或false,根据字段含义确定;<br>数值型:输入一个默认值,整数型的输入0,非整数型的输入0.0,但如果业务规则有特殊定义的,输入特定默认数值;<br>字符型:输入空字符串为默认值,因为如果存入的是null值,无法被上面JPA接口中标准查询和高级查询方法查出来。</p><h3 style="font-size:16px;line-height:1.5;color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;"><a name="t2"></a>三、模拟更多数据测试列表</h3><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">将代码定位到testList方法:</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083408316-626611690.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">在上图中给出了添加第二条数据的代码模版,可复制该段代码多份,依次改为c3、c4...以向数据库插入多条数据,充分测试无查询列表、标准查询和高级查询。</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><span>最佳实践</span><br>前面基于<span>最少字段和数据正确的原则</span>模拟实际业务中创建数据的参数构建了一条数据,一般而言,我们还需要模拟出“经过修改过的数据”(给更多字段赋值),对于启用删除审计的领域类,还应该模拟出非物理删除的数据。</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">模拟数据前代码:</p><div class="cnblogs_code" style="border:1px solid rgb(204,204,204);color:rgb(0,0,0);font-family:'Courier New';font-size:12px;"><div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy" style="line-height:1.5;"><a title="复制代码" style="color:rgb(0,0,0);text-decoration:underline;border:none;" target="_blank"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border:none;"></a></span></div><pre style="font-family:'Courier New';"> <span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">TODO 建议借鉴下面的测试用例赋值模版构造更多数据以充分测试"无搜索列表"、"标准查询"和"高级查询"的表现 </span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">提示:构建"新增数据"提示:根据新增数据时客户端实际能提供的参数,依据"最少字段和数据正确的原则"构建
</span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">提示:构建"修改过的数据"提示:根据修改数据时客户端实际能提供的参数构建
</span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">提示:可以构建"非物理删除的数据"</span>
<span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">---------------------测试用例赋值开始---------------------*</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span>
<span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">TODO 将下面的null值换为测试参数</span>
Category c2 = <span style="color:rgb(0,0,255);line-height:1.5;">new</span><span style="line-height:1.5;"> Category();
c2.setProjectId(</span><span style="color:rgb(0,0,255);line-height:1.5;">null</span><span style="line-height:1.5;">);
c2.setName(</span><span style="color:rgb(0,0,255);line-height:1.5;">null</span><span style="line-height:1.5;">);
c2.setSequence(</span><span style="color:rgb(0,0,255);line-height:1.5;">null</span><span style="line-height:1.5;">);
c2.setCreatorUserId(</span>2<span style="line-height:1.5;">);
</span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">提示:构造"修改过的数据"时需要给"最近修改时间"和"最近修改者"赋值
</span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">c2.setLastModificationTime(new Date());
</span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">c2.setLastModifierUserId(1);
</span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">提示:构造"非物理删除的数据"时需要给"已删除"、"删除时间"和"删除者"赋值
</span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">c2.setIsDeleted(true);
</span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">c2.setDeletionTime(new Date());
</span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">c2.setDeleterUserId(1);</span>
categoryRepository.save(c2);
/---------------------测试用例赋值结束---------------------/
模拟数据后代码:
// 添加分类:用例2(分类名称与装配数据中的分类名称有部分关键字相同)
/---------------------测试用例赋值开始---------------------/
Category c2 = new Category();
c2.setProjectId(1L);
c2.setName("文档分类二");
c2.setSequence(2);
c2.setCreatorUserId(2);
categoryRepository.save(c2);
/**---------------------测试用例赋值结束---------------------/<span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 添加分类:用例3(分类名称与用例1和用例2完全不同)</span>
<span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">---------------------测试用例赋值开始---------------------*</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span><span style="line-height:1.5;">
Category c3 </span>= <span style="color:rgb(0,0,255);line-height:1.5;">new</span><span style="line-height:1.5;"> Category();
c3.setProjectId(</span>1L<span style="line-height:1.5;">);
c3.setName(</span>"项目资料归档"<span style="line-height:1.5;">);
c3.setSequence(</span>3<span style="line-height:1.5;">);
c3.setCreatorUserId(</span>2<span style="line-height:1.5;">);
categoryRepository.save(c3);
</span><span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">---------------------测试用例赋值结束---------------------*</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span> <span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 添加分类:用例4(名称与用例1一样,但是所属项目不同)</span>
<span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">---------------------测试用例赋值开始---------------------*</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span><span style="line-height:1.5;">
Category c4 </span>= <span style="color:rgb(0,0,255);line-height:1.5;">new</span><span style="line-height:1.5;"> Category();
c4.setProjectId(</span>2L<span style="line-height:1.5;">);
c4.setName(</span>"文档分类一"<span style="line-height:1.5;">);
c4.setSequence(</span>1<span style="line-height:1.5;">);
c4.setCreatorUserId(</span>2<span style="line-height:1.5;">);
categoryRepository.save(c4);
</span><span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">---------------------测试用例赋值结束---------------------*</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span> <span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 修改分类:用例5</span>
<span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">---------------------测试用例赋值开始---------------------*</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span><span style="line-height:1.5;">
Category c5 </span>= <span style="color:rgb(0,0,255);line-height:1.5;">new</span><span style="line-height:1.5;"> Category();
c5.setProjectId(</span>1L<span style="line-height:1.5;">);
c5.setName(</span>"被修改过的文档分类"<span style="line-height:1.5;">);
c5.setSequence(</span>4<span style="line-height:1.5;">);
c5.setCreatorUserId(</span>2<span style="line-height:1.5;">);
c5.setLastModificationTime(</span><span style="color:rgb(0,0,255);line-height:1.5;">new</span><span style="line-height:1.5;"> Date());
c5.setLastModifierUserId(</span>1<span style="line-height:1.5;">);
categoryRepository.save(c5);
</span><span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">---------------------测试用例赋值结束---------------------*</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span> <span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 删除分类:用例6</span>
<span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">---------------------测试用例赋值开始---------------------*</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span><span style="line-height:1.5;">
Category c6 </span>= <span style="color:rgb(0,0,255);line-height:1.5;">new</span><span style="line-height:1.5;"> Category();
c6.setProjectId(</span>1L<span style="line-height:1.5;">);
c6.setName(</span>"被删除过的文档分类"<span style="line-height:1.5;">);
c6.setSequence(</span>5<span style="line-height:1.5;">);
c6.setCreatorUserId(</span>2<span style="line-height:1.5;">);
c6.setLastModificationTime(</span><span style="color:rgb(0,0,255);line-height:1.5;">new</span><span style="line-height:1.5;"> Date());
c6.setLastModifierUserId(</span>1<span style="line-height:1.5;">);
c6.setIsDeleted(</span><span style="color:rgb(0,0,255);line-height:1.5;">true</span><span style="line-height:1.5;">);
c6.setDeletionTime(</span><span style="color:rgb(0,0,255);line-height:1.5;">new</span><span style="line-height:1.5;"> Date());
c6.setDeleterUserId(</span>1<span style="line-height:1.5;">);
categoryRepository.save(c6);
</span><span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">---------------------测试用例赋值结束---------------------*</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span></pre><div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy" style="line-height:1.5;"><a title="复制代码" style="color:rgb(0,0,0);text-decoration:underline;border:none;" target="_blank"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border:none;"></a></span></div></div><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"> </p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">现在再次运行“UserControllerTest”单元测试:</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083341353-596498146.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">我们看到现在异常定位到177行代码,在“测试无搜索列表”中,说明上面构造五条测试数据的代码已能通过。</p><h2 style="font-size:21px;line-height:1.5;color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;"><a name="t3"></a>四、测试无搜索列表</h2><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">此时,我们在setUp()方法中构造了一条数据,在testList中构造了三条新增数据、一条修改过的数据和一条非物理删除的数据,共六条数据,加载项目ID为1的文档分类时,排除用例6(已非物理删除)和用例4(不属于该项目的文档分类),我们加载列表时应返回4条数据,所以我们期望返回的数据数量应为4。</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">修改前代码:</p><div class="cnblogs_code" style="border:1px solid rgb(204,204,204);color:rgb(0,0,0);font-family:'Courier New';font-size:12px;"><div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy" style="line-height:1.5;"><a title="复制代码" style="color:rgb(0,0,0);text-decoration:underline;border:none;" target="_blank"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border:none;"></a></span></div><pre style="font-family:'Courier New';"> <span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">
* 测试无搜索列表
</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span> <span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">---------------------测试用例赋值开始---------------------*</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span>
<span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">TODO 将下面的null值换为测试参数</span>
Pageable pageable=<span style="color:rgb(0,0,255);line-height:1.5;">new</span> PageRequest(0,10, Sort.Direction.DESC,"categoryId"<span style="line-height:1.5;">); </span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 期望获得的结果数量(默认有两个测试用例,所以值应为"2L",如果新增了更多测试用例,请相应设定这个值)</span>
expectResultCount = <span style="color:rgb(0,0,255);line-height:1.5;">null</span><span style="line-height:1.5;">;
</span><span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">---------------------测试用例赋值结束---------------------*</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span> <span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 直接通过dao层接口方法获得期望的数据</span>
Page<Category> pagedata =<span style="line-height:1.5;"> categoryRepository.findByProjectIdAndIsDeletedFalse(c1.getCategoryId(), pageable);
expectData </span>= JsonPath.read(Obj2Json(pagedata),"$"<span style="line-height:1.5;">).toString(); MvcResult mvcResult </span>=<span style="line-height:1.5;"> mockMvc
.perform(
MockMvcRequestBuilders.get(</span>"/category/list/projectId=1"<span style="line-height:1.5;">)
.accept(MediaType.APPLICATION_JSON)
)
</span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 打印结果</span>
.andDo(print())
// 检查状态码为200
.andExpect(status().isOk())
// 检查返回的数据节点
.andExpect(jsonPath("$.pagedata.totalElements").value(expectResultCount))
.andExpect(jsonPath("$.dto.keyword").isEmpty())
.andExpect(jsonPath("$.dto.projectId").value(1))
.andReturn();</span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 提取返回结果中的列表数据及翻页信息</span>
responseData = JsonPath.read(mvcResult.getResponse().getContentAsString(),"$.pagedata"<span style="line-height:1.5;">).toString(); System.out.println(</span>"=============无搜索列表期望结果:" +<span style="line-height:1.5;"> expectData);
System.out.println(</span>"=============无搜索列表实际返回:" +<span style="line-height:1.5;"> responseData); Assert.assertEquals(</span>"错误,无搜索列表返回数据与期望结果有差异",expectData,responseData);</pre><div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy" style="line-height:1.5;"><a title="复制代码" style="color:rgb(0,0,0);text-decoration:underline;border:none;" target="_blank"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border:none;"></a></span></div></div><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"> </p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><span>赋值说明</span><br>1>.领域中含有“sequence”字段时,通常是要根据该字段升序陈列数据;<br>2>.将“expectResultCount”赋值为4(因为是Long型,所以输入为"4L");<br>3>.另外,大部分情况下请求无搜索类别的网址是不需要带参数的,但本例请求的列表需要根据项目(projectId)筛选数据,仅显示某项目的文档分类,所以实际业务中请求列表的网址应该是“/category/list/projectId=XX”;</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">修改后代码如下:</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083340706-242416191.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">再次运行“CategoryControllerTest”单元测试:</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083344878-1256780792.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><span>异常分析</span><br>现在异常指向无搜索列表的最后一句断言,期望返回的json数据和实际返回的json数据不一致,在控制台看看打印的两个数据,发现只是数据排序不一致导致的。</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">现在可以打开控制器层代码,将控制器中列表的方法代码改一下。修改前:</p><div class="cnblogs_code" style="border:1px solid rgb(204,204,204);color:rgb(0,0,0);font-family:'Courier New';font-size:12px;"><div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy" style="line-height:1.5;"><a title="复制代码" style="color:rgb(0,0,0);text-decoration:underline;border:none;" target="_blank"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border:none;"></a></span></div><pre style="font-family:'Courier New';"> <span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">
* 文档分类
* GET: /category/list
* </span><span style="color:rgb(128,128,128);line-height:1.5;">@param</span><span style="color:rgb(0,128,0);line-height:1.5;"> pageable
* </span><span style="color:rgb(128,128,128);line-height:1.5;">@param</span><span style="color:rgb(0,128,0);line-height:1.5;"> dto
* </span><span style="color:rgb(128,128,128);line-height:1.5;">@return</span>
<span style="color:rgb(0,128,0);line-height:1.5;">*/</span><span style="line-height:1.5;">
@GetMapping(</span>"/list"<span style="line-height:1.5;">)
</span><span style="color:rgb(0,0,255);line-height:1.5;">public</span> Map<String, Object> list(@PageableDefault(sort = { "categoryId" }, direction =<span style="line-height:1.5;"> Sort.Direction.DESC) Pageable pageable, CategoryDTO dto){
Map</span><String, Object> map =<span style="line-height:1.5;"> Maps.newHashMap(); Page</span><Category> pagedata =<span style="line-height:1.5;"> categoryService.getPageData(dto,pageable);
map.put(</span>"dto"<span style="line-height:1.5;">,dto);
map.put(</span>"pagedata"<span style="line-height:1.5;">,pagedata); </span><span style="color:rgb(0,0,255);line-height:1.5;">return</span><span style="line-height:1.5;"> map;
}</span></pre><div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy" style="line-height:1.5;"><a title="复制代码" style="color:rgb(0,0,0);text-decoration:underline;border:none;" target="_blank"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border:none;"></a></span></div></div><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"> </p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">稍作调整:</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083343097-429124761.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">再次执行测试:</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083348236-616728557.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">异常代码定位到“测试标准查询”部分了,说明“无查询列表”部分的代码测试已通过了。</p><h2 style="font-size:21px;line-height:1.5;color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;"><a name="t4"></a>五、测试标准查询</h2><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">标准查询时搜索框会将关键字(keyword)作为参数传到Rest控制器接口。<br>当前“测试标准查询”的代码如下:</p><div class="cnblogs_code" style="border:1px solid rgb(204,204,204);color:rgb(0,0,0);font-family:'Courier New';font-size:12px;"><div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy" style="line-height:1.5;"><a title="复制代码" style="color:rgb(0,0,0);text-decoration:underline;border:none;" target="_blank"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border:none;"></a></span></div><pre style="font-family:'Courier New';"> <span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">
* 测试标准查询
</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span> <span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">---------------------测试用例赋值开始---------------------*</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span>
<span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;">TODO 将下面的null值换为测试参数</span>
dto = <span style="color:rgb(0,0,255);line-height:1.5;">new</span><span style="line-height:1.5;"> CategoryDTO();
dto.setKeyword(</span><span style="color:rgb(0,0,255);line-height:1.5;">null</span><span style="line-height:1.5;">);
dto.setProjectId(c1.getProjectId()); pageable</span>=<span style="color:rgb(0,0,255);line-height:1.5;">new</span> PageRequest(0,10, Sort.Direction.DESC,"categoryId"<span style="line-height:1.5;">); </span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 期望获得的结果数量</span>
expectResultCount = <span style="color:rgb(0,0,255);line-height:1.5;">null</span><span style="line-height:1.5;">;
</span><span style="color:rgb(0,128,0);line-height:1.5;">/**</span><span style="color:rgb(0,128,0);line-height:1.5;">---------------------测试用例赋值结束---------------------*</span><span style="color:rgb(0,128,0);line-height:1.5;">*/</span><span style="line-height:1.5;"> String keyword </span>=<span style="line-height:1.5;"> dto.getKeyword().trim(); </span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 直接通过dao层接口方法获得期望的数据</span>
pagedata =<span style="line-height:1.5;"> categoryRepository.findByNameContainingAllIgnoringCaseAndProjectIdAndIsDeletedFalse(keyword, dto.getProjectId(), pageable);
expectData </span>= JsonPath.read(Obj2Json(pagedata),"$"<span style="line-height:1.5;">).toString(); mvcResult </span>=<span style="line-height:1.5;"> mockMvc
.perform(
MockMvcRequestBuilders.get(</span>"/category/list"<span style="line-height:1.5;">)
.param(</span>"keyword"<span style="line-height:1.5;">,dto.getKeyword())
.accept(MediaType.APPLICATION_JSON)
)
</span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 打印结果</span>
.andDo(print())
// 检查状态码为200
.andExpect(status().isOk())
// 检查返回的数据节点
.andExpect(jsonPath("$.pagedata.totalElements").value(expectResultCount))
.andExpect(jsonPath("$.dto.keyword").value(dto.getKeyword()))
.andExpect(jsonPath("$.dto.name").isEmpty())
.andReturn();</span><span style="color:rgb(0,128,0);line-height:1.5;">//</span><span style="color:rgb(0,128,0);line-height:1.5;"> 提取返回结果中的列表数据及翻页信息</span>
responseData = JsonPath.read(mvcResult.getResponse().getContentAsString(),"$.pagedata"<span style="line-height:1.5;">).toString(); System.out.println(</span>"=============标准查询期望结果:" +<span style="line-height:1.5;"> expectData);
System.out.println(</span>"=============标准查询实际返回:" +<span style="line-height:1.5;"> responseData); Assert.assertEquals(</span>"错误,标准查询返回数据与期望结果有差异",expectData,responseData);</pre><div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy" style="line-height:1.5;"><a title="复制代码" style="color:rgb(0,0,0);text-decoration:underline;border:none;" target="_blank"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border:none;"></a></span></div></div><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"> </p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><span>第一个标准查询的测试目标</span><br>projectId为1,关键字为“文档分类”,这个关键字在用例1、用例2、用例4、用例5、用例6均有出现,用例3不含该,排除用例4(projectId值为2),排除用例6(已删除),所以应返回3条结果:</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083351394-798516556.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><span>第二个标准查询的测试目标</span><br><span>现在我们将上面的代码复制一份</span>,改为标准查询的第二种测试。projectId为1,关键字为空,所有用例都应满足,排除用例4(projectId值为2),排除用例6(已删除),所以应返回4条结果:</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083356612-1704565523.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><span>第三个标准查询的测试目标</span><br>projectId设为1,关键字为“资料归档”,应全部不满足:</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083358534-861594200.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><span>第四个标准查询的测试目标</span><br>projectId设为2,关键字为“资料归档”,应仅用例3满足,所以返回1条数据:</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083401128-1993290455.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">现在运行单元测试,各....位....观....众!!!...</p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;"><img src="https://images2018.cnblogs.com/blog/1340917/201804/1340917-20180406083359501-1374709692.jpg" alt="" style="border:none;"></p><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">发现testList方法已变绿,说明list方法已通过测试!<br>如果测试工程师提供更多有价值的测试用例,可以继续添加测试代码。</p><h2 style="font-size:21px;line-height:1.5;color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;"><a name="t5"></a>六、测试高级查询</h2><p style="color:rgb(51,51,51);font-family:Verdana, Arial, Helvetica, sans-serif;font-size:14px;">本例“文档分类”没有高级查询接口,故无法演示。但是本项目的领域类文档(Document)有高级查询接口,有兴趣的同学可以下载Github源码参考。<br></p><p>Github代码获取:<a href="https://github.com/MacManon/top_cloudev_doc" rel="nofollow" style="color:rgb(0,0,0);text-decoration:underline;" target="_blank">https://github.com/MacManon/top_cloudev_doc</a></p><p>http://www.tjx3222.cn/<br>http://www.ksy2010.cn/<br>http://www.uva7574.cn/<br>http://www.epq3151.cn/<br>http://www.ofi3691.cn/<br>http://www.hwh7329.cn/<br>http://www.qta0411.cn/<br>http://www.oni7878.cn/<br>http://www.xlp3436.cn/<br>http://www.trm9066.cn/<br>http://www.wrq6652.cn/<br>http://www.oph4016.cn/<br>http://www.eis6490.cn/<br>http://www.xfq2422.cn/<br>http://www.tlj5370.cn/<br>http://www.pqi7094.cn/<br>http://www.wky2337.cn/<br>http://www.veo5593.cn/<br>http://www.lpn9733.cn/<br>http://www.hxp7546.cn/<br>http://www.uht8250.cn/<br>http://www.erg7887.cn/<br>http://www.zrg4662.cn/<br>http://www.gmw7502.cn/<br>http://www.omk7549.cn/<br>http://www.cck2829.cn/<br>http://www.ujn9355.cn/<br>http://www.mgj9519.cn/<br>http://www.rig2765.cn/<br>http://www.irw9322.cn/<br>http://www.psd6888.cn/<br>http://www.pnl2718.cn/<br>http://www.com0678.cn/<br>http://www.oku7898.cn/<br>http://www.tas9765.cn/<br>http://www.vgz0064.cn/<br>http://www.nld6227.cn/<br>http://www.hco5930.cn/<br>http://www.oqs1669.cn/<br>http://www.nny5237.cn/<br>http://www.tyc6381.cn/<br>http://www.qmi0117.cn/<br>http://www.ylz8420.cn/<br>http://www.vha4867.cn/<br>http://www.frm7103.cn/<br>http://www.alo1362.cn/<br>http://www.phv0172.cn/<br>http://www.ppb5307.cn/<br>http://www.nzx0496.cn/<br>http://www.kwb7023.cn/<br>http://www.jyr2443.cn/<br>http://www.dbz8514.cn/<br>http://www.cok3166.cn/<br>http://www.rws5532.cn/<br>http://www.buw8082.cn/<br>http://www.hce3357.cn/<br>http://www.pjv6550.cn/<br>http://www.wlc0063.cn/<br></p> </div>
原文地址:https://blog.csdn.net/shunqixing/article/details/79832317
测试驱动开发实践—从testList开始的更多相关文章
- 测试驱动开发实践2————从testList开始
内容指引 1.测试之前 2.改造dto层代码 3.改造dao层代码 4.改造service.impl层的list方法 5.通过testList驱动domain领域类的修改 一.测试之前 在" ...
- 测试驱动开发实践3————从testList开始
[内容指引] 运行单元测试: 装配一条数据: 模拟更多数据测试列表: 测试无搜索列表: 测试标准查询: 测试高级查询. 一.运行单元测试 我们以文档分类(Category)这个领域类为例,示范如何通过 ...
- 测试驱动开发实践 - Test-Driven Development(转)
一.前言 不知道大家有没听过“测试先行的开发”这一说法,作为一种开发实践,在过去进行开发时,一般是先开发用户界面或者是类,然后再在此基础上编写测试. 但在TDD中,首先是进行测试用例的编写,然后再进行 ...
- 测试驱动开发实践 - Test-Driven Development
一.前言 不知道大家有没听过“测试先行的开发”这一说法,作为一种开发实践,在过去进行开发时,一般是先开发用户界面或者是类,然后再在此基础上编写测试. 但在TDD中,首先是进行测试用例的编写,然后再进行 ...
- 测试驱动开发实践4————testSave之新增文档分类
[内容指引] 1.确定"新增文档分类"的流程及所需的参数 2.根据业务规则设计测试用例 3.为测试用例赋值并驱动开发 一.确定"新增文档分类"的流程及所需的参数 ...
- 测试驱动开发实践3————testSave之新增用户
内容指引 1.确定新增用户的业务规则 2.根据业务规则设计测试用例 3.为测试用例赋值并驱动开发 一.确定新增用户的规则 1.注册用户允许通过"用户名+密码"."手机号+ ...
- 测试驱动开发实践5————testSave之修改文档分类
[内容指引] 1.确定"修改文档分类"的微服务接口及所需的参数 2.设计测试用例及测试用例合并 3.为测试用例赋值并驱动开发 上一篇我们通过17个测试用例完成了"新增文档 ...
- TDD(测试驱动开发)培训录
2014年我一直从事在敏捷实践咨询项目,这也是我颇有收获的一年,特别是咨询项目的每一点改变,不管是代码质量的提高,还是自组织团队的建设,都能让我们感到欣慰.涉及人的问题都是复杂问题,改变人,改变一个组 ...
- 测试驱动开发(TDD)的思考
极限编程 敏捷开发是一种思想,极限编程也是一种思想,它与敏捷开发某些目标是一致的.只是实现方式不同.测试驱动开发是极限编程的一部分. 1.极限编程这个思路的来源 Kent Beck先生最早在其极限编程 ...
随机推荐
- Limboy:自学 iOS 开发的一些经验
不知不觉作为 iOS 开发也有两年多的时间了,记得当初看到 OC 的语法时,愣是被吓了回去,隔了好久才重新耐下心去啃一啃.啃了一阵,觉得大概有了点概念,看到 Cocoa 那么多的 Class,又懵了, ...
- hdu 3466 01背包变形【背包dp】
http://acm.hdu.edu.cn/showproblem.php?pid=3466 有两个物品P,Q,V分别为 3 5 6, 5 10 5,如果先dp第一个再dp第二个,背包容量至少要为3+ ...
- Android依赖别的包时,出现的问题
项目和依赖的项目一定要在同一个文件夹下,不然会出现这种问题
- find 使用搜集
find:-atime +n/-n:表示访问或执行时间大于或小于n天的文件-ctime +n/-n:表示写入.更改inode属性的时间大于或小于n天的文件-mtime +n/-n:表示写入时间大于或小 ...
- 基于Qt框架的GUI控制台——qtconsole
- HDU-2859_Phalanx
Phalanx Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Subm ...
- Spring AOP 的@Aspect
Spring AOP 的@Aspect 转自:http://blog.csdn.net/tanghw/article/details/3862987 从Spring 2.0开始,可以使用基于sch ...
- etcd 在超大规模数据场景下的性能优化
作者 | 阿里云智能事业部高级开发工程师 陈星宇(宇慕) 概述 etcd是一个开源的分布式的kv存储系统, 最近刚被cncf列为沙箱孵化项目.etcd的应用场景很广,很多地方都用到了它,例如kuber ...
- 伪静态的实现方法:IIS环境下配置
URL 静态化可以提高搜索引擎抓取,开启本功能需要对 Web 服务器增加相应的 Rewrite 规则,且会轻微增加服务器负担.本教程讲解如何在 IIS 环境下配置各个产品的 Rewrite 规则. 下 ...
- mysql统计信息相关
最近RDS FOR MYSQL5.6的统计信息有问题,一些表明明的数据,但统计信息里去显示为空表,导致执行计划出错,查询效率很低,所以查看下相关的信息. -- 查看服务器系统变量,实际上使用的变量的值 ...