若是您今天正在编程,那么您极可能据说过单元测试或测试驱动的开发过程。我尚未遇到一个既没有据说过又没有据说过单元测试并不重要的程序员。在随意的讨论中,大多数程序员彷佛认为单元测试很是重要。java
可是,当我开始使用代码并问“单元测试在哪里?”时,我获得了一个彻底不一样的故事。我最近在网上问个人程序员朋友为何不这样作,以及为何其余程序员不这样作呢?不要编写单元测试。当我问程序员或IT经理一样的问题时,我常常听到的第一答案是:“我没有时间”或相似的问题。一般会出现这样的论点,即便用单元测试编写应用程序要比不使用单元测试编写时间长20%,而且“咱们受到时间限制”。程序员
个人建议–当咱们尝试解决时间不足的问题时,也许咱们能够在娱乐性上作出一些贡献。编程
我正在为一个应用程序设计原型,该应用程序将容许用户输入有关房屋装修项目的信息,而后与朋友共享该项目的材料和工具信息。而后,朋友能够承诺贷款或购买项目中所需的一些材料或工具。基本上是用于家庭装修项目的“登记处”。promise
测试将在采用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中重写该测试的可能方法。学习
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都有据可查的文档,仅经过搜索便可得到许多资源。在各类社交媒体上也有一个很是活跃和乐于助人的社区,我相信很乐意提供帮助。