如果您今天正在编程,那么您很可能听说过单元测试或测试驱动的开发过程。我还没有遇到一个既没有听说过又没有听说过单元测试并不重要的程序员。在随意的讨论中,大多数程序员似乎认为单元测试非常重要。

但是,当我开始使用代码并问“单元测试在哪里?”时,我得到了一个完全不同的故事。我最近在网上问我的程序员朋友为什么不这样做,以及为什么其他程序员不这样做呢?不要编写单元测试。当我问程序员或IT经理同样的问题时,我经常听到的第一答案是:“我没有时间”或类似的问题。通常会出现这样的论点,即使用单元测试编写应用程序要比不使用单元测试编写时间长20%,并且“我们受到时间限制”。

我的建议–当我们尝试解决时间不足的问题时,也许我们可以在娱乐性上做出一些贡献。

在实践中

我正在为一个应用程序设计原型,该应用程序将允许用户输入有关房屋装修项目的信息,然后与朋友共享该项目的材料和工具信息。然后,朋友可以承诺贷款或购买项目中所需的一些材料或工具。基本上是用于家庭装修项目的“登记处”。

测试将在采用Project对象的方法上进行,遍历该项目的工具列表以查看该工具是否已经被承诺,并创建一个未被承诺的工具列表。然后,它将把该列表传递给将查询每个工具当前价格的服务。

原型是用Grails完成的,但是我们将用Java编写此方法:

public List<Tool> neededToolList(Project project) {
final List<Tool> retList = new ArrayList<>(); if (project.getTools() == null || project.getTools().isEmpty()) {
return retList;
} for (Tool tool : project.getTools()) {
if (!tool.getPromise().isPromised()) {
retList.add(tool);
}
} List<Tool> tools = lookupService.updateToolList(retList);
return tools;
}

单个单元测试可能类似于:

@Test
public void testNeededToolList() {
Tools _instance = new Tools(); Project project = new Project(); Promise promise = new Promise();
promise.setProject(project);
promise.setPromised(false);
Promise promise2 = new Promise();
promise2.setProject(project);
promise2.setPromised(true); List<Tool> tools = new ArrayList<>();
List<Tool> lookupTools = new ArrayList<>();
Tool tool1 = new Tool();
tool1.setName("table saw");
tool1.setStoreId("T001");
tool1.setPromise(promise);
tools.add(tool1);
lookupTools.add(tool1);
Tool tool2 = new Tool();
tool2.setName("pneumatic nail guns");
tool2.setStoreId("T027");
tool2.setPromise(promise2);
tools.add(tool2);
project.setTools(tools); List<Tool> mockedTools = new ArrayList<>();
Tool mockedTool1 = new Tool();
mockedTool1.setPromise(promise);
mockedTool1.setName("table saw");
mockedTool1.setStoreId("T001");
mockedTool1.setPrice(129.0);
mockedTools.add(mockedTool1); lookupService = Mockito.mock(LookupServiceImpl.class);
Mockito.when(lookupService.updateToolList(lookupTools)).thenReturn(mockedTools);
_instance.setLookupService(lookupService); List<Tool> returnedTools = _instance.neededToolList(project); assertTrue(returnedTools.size() == 1);
for(Tool tool : returnedTools) {
assertEquals(129.0, tool.getPrice(), 0.01);
}
}

这是一个简单的测试,并且只有一个。需要针对几种情况编写测试,例如空值。例如,如果StoreID不存在怎么办?

输入Groovy

在之前的文章中,我已经介绍了我的好朋友Groovy编程语言。让我们看看是否可以进行Groovy测试。

Groovy带来了许多语法上的捷径,这些捷径有助于加快编写代码(包括测试)的速度。让我们看一下在Groovy中重写该测试的可能方法。

class GroovyToolsTest extends GroovyTestCase {
def lookupService = [
updateToolList : {List<Tool> toolList ->
toolList.each {
if(it.storeId == "T001") {
it.price = 129.0
}
}
return toolList
}
] as LookupService void testNeededToolList() {
def _instance = new Tools()
def project = new Project()
project.tools = [
new Tool(name: "table saw", storeId: "T001", promise: new Promise(project: project, promised: false)),
new Tool(name: "pneumatic nail guns", storeId: "T027", promise: new Promise(project: project, promised: true))
] _instance.lookupService = lookupService def returnedList = _instance.neededToolList(project)
returnedList.size() == 1
returnedList.each {
if(it.storeId == "T001") {
assert it.price == 129.0
}
}
println "done"
}
}

我们看到的第一件事是Groovy为我们提供了一种很棒的Mocking代码机制,它使我们能够做的比我在Mocking框架中所能做的还要多。在模拟框架中,我通常为期望返回的数据创建一个新对象。在这里,我实际上是将数据更改为服务应该返回的内容。

切记:我不是在测试服务,所以模拟服务应该返回我期望服务返回的值。

我还发现可以在一个调用中创建对象并加载数据的功能(与创建Bean和调用每个setter相对)更容易编写,读取和复制为模板,以创建更多内容。Groovy提供了几种处理列表的方法,使之成为快速开发和维护测试的出色语言。

如果您想对单元测试有所不同,那么还有Spock测试框架。它具有更广泛的语言,使其更具行为驱动的外观,但仍使用上一示例中的所有Groovy Goodness。

class ToolsSpec extends Specification {
def lookupService = [
updateToolList : {List<Tool> toolList ->
println "mocked service"
toolList.each { tool ->
if(tool.storeId == "T001")
tool.price = 129.0
}
return toolList
}
] as LookupService def "Lookup needed tool list"() {
given:"Create instance"
def _instance = new Tools()
def project = new Project()
project.tools = [
[name: "table saw", storeId: "T001", promise: [project: project, promised: false] as Promise] as Tool,
[name: "pneumatic nail guns", storeId: "T027", promise: [project: project, promised: true] as Promise] as Tool,
] as List<Tool>; _instance.lookupService = lookupService expect:"Tool List"
def returnedList = _instance.neededToolList(project)
returnedList.size() == 1
returnedList.each {
if(it.storeId == "T001") {
assert it.price == 129.0
}
} } }

请注意,我使用了一种不同的语法为Tool创建测试数据对象。这是标准的Groovy功能,它允许程序员将映射转换为具体的类,并且在先前的示例中也可以使用。当您习惯阅读Groovy时,这可能比新的Object语法更容易阅读。

在这两个示例中,语法“糖”更紧密的代码并不是唯一的好处。测试失败的输出也会有所不同,并且会更有帮助

在第一个示例中,测试失败的输出为:

java.lang.AssertionError: expected:<128.0> but was:<129.0>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
at org.junit.Assert.assertEquals(Assert.java:553)
at org.junit.Assert.assertEquals(Assert.java:683)
at org.projectregistry.services.ToolsTest.testNeededToolList(ToolsTest.java:93)
....

Groovy和Spock测试的输出如下所示:

Assertion failed: 

assert it.price == 128.0
| | |
| 129.0 false
org.projectregistry.model.Tool@5e59238b at org.codehaus.groovy.runtime.InvokerHelper.assertFailed(InvokerHelper.java:399)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.assertFailed(ScriptBytecodeAdapter.java:648)
at org.projectregistry.services.GroovyToolsTest$_testNeededToolList_closure2.doCall(GroovyToolsTest.groovy:34)
...

Groovy输出中提供了更多信息,这反过来又使您可以更快地进行修复。

代码项目

因此,随着可以节省语法和输出的时间,并希望通过一种新的和不同的语言来增加编程乐趣,我希望每个人都可以尝试Groovy和/或Spock来克服惯性,这种惯性会阻止程序员进行单元测试。

学习如何简单。Groovy和Spock都有据可查的文档,仅通过搜索即可获得许多资源。在各种社交媒体上也有一个非常活跃和乐于助人的社区,我相信很乐意提供帮助。

技术类文章精选

非技术文章精选

人生苦短?试试Groovy进行单元测试的更多相关文章

  1. Groovy+Spock单元测试

    一.导入依赖 Spock是基于JUnit的单测框架,提供一些更好的语法,结合Groovy语言,可以写出更为简洁的单测. <!-- groovy依赖 --> <dependency&g ...

  2. 新学习的语言Groovy

    什么是 Groovy? Groovy 是 JVM 的一个替代语言 —替代 是指可以用 Groovy 在 Java 平台上进行 Java 编程,使用方式基本与使用 Java 代码的方式相同.在编写新应用 ...

  3. Spring Boot中使用MyBatis注解配置详解(1)

    之前在Spring Boot中整合MyBatis时,采用了注解的配置方式,相信很多人还是比较喜欢这种优雅的方式的,也收到不少读者朋友的反馈和问题,主要集中于针对各种场景下注解如何使用,下面就对几种常见 ...

  4. Spring Boot中使用MyBatis注解配置详解

    传参方式 下面通过几种不同传参方式来实现前文中实现的插入操作. 使用@Param 在之前的整合示例中我们已经使用了这种最简单的传参方式,如下: @Insert("INSERT INTO US ...

  5. Spring Boot教程(三十九)使用MyBatis注解配置详解(2)

    增删改查 MyBatis针对不同的数据库操作分别提供了不同的注解来进行配置,在之前的示例中演示了@Insert,下面针对User表做一组最基本的增删改查作为示例: public interface U ...

  6. Spring Boot 2.x基础教程:使用MyBatis访问MySQL

    之前我们已经介绍了两种在Spring Boot中访问关系型数据库的方式: 使用spring-boot-starter-jdbc 使用spring-boot-starter-data-jpa 虽然Spr ...

  7. Groovy单元测试框架spock数据驱动Demo

    spock是一款全能型的单元测试框架. 上次文章分享了spock框架的基础功能的使用,在此基础上,我根据自己写的Groovy的封装方法.数据驱动以及一些Groovy的高级语法做了一些尝试.发现还是有一 ...

  8. Groovy单元测试框架spock基础功能Demo

    spock是一款全能型的单元测试框架. 最近在做单元测试框架的调研和尝试,目前确定的方案框架包括是:spock,Junit,Mockito以及powermock.由于本身使用Groovy的原因,比较钟 ...

  9. 工作3年,还不会写单元测试?新技能get!

    历史遗留代码不敢重构? 每次改代码都要回归所有逻辑? 提测被打回? 在近期的代码重构的过程中,遇到了各式各样的问题.比如调整代码顺序导致bug,取反操作逻辑丢失,参数校验逻辑被误改等. 上线前需要花大 ...

随机推荐

  1. C++ std::vector 基本用法

    #include <iostream> #include <vector> using namespace std; int main() { // 初始化的方式 std::v ...

  2. [译]Vulkan教程(10)交换链

    [译]Vulkan教程(10)交换链 Vulkan does not have the concept of a "default framebuffer", hence it r ...

  3. phpredis 报错 “Function Redis::setTimeout() is deprecated” 解决方法

    项目在本地开发过程中抛出异常: Function Redis::setTimeout() is deprecated 找到出错代码: <?php use Illuminate\Support\F ...

  4. goroutine,channel

    Go语言中有个概念叫做goroutine, 这类似我们熟知的线程,但是更轻. 以下的程序,我们串行地去执行两次loop函数: package main import "fmt" f ...

  5. 常用类-Excel-使用Aspose.Cells插件

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Xm ...

  6. 简单的PHP上传图片实例

    分享一个简单的PHP上传图片实例,本实例主要介绍了上传图片的一些限制判断和上传图片的方法. 首先我们在form表单加上上传附件#file,上传按钮#imgbut,记得给form 表单加上multipa ...

  7. Python有参函数的使用

    1.给定验证码长度n,生成随机验证码,验证码由数字.字母组成(参考chr()内置方法) 程序代码如下: import random def create_check_code(n): check_co ...

  8. OpenCV:图像的颜色空间转换

    导包: import numpy as np import cv2 import matplotlib.pyplot as plt def show(image): plt.imshow(image) ...

  9. tomcat修改进程名称

    1.window平台: 打开tomcat_home\bin\setclasspath.bat文件,找到set _RUNJAVA=”%JRE_HOME%\bin\java”这一行. 将该行注释掉 ,然后 ...

  10. 解决brew update很慢

    一般遇到这种问题,就跟墙有关啦,需要更换源. 用清华的源就非常好,去清华镜像的官网看一下说明,https://mirrors.tuna.tsinghua.edu.cn/help/homebrew/ $ ...