人生苦短?試試Groovy進行單元測試


如果您今天正在編程,那么您很可能聽說過單元測試或測試驅動的開發過程。我還沒有遇到一個既沒有聽說過又沒有聽說過單元測試並不重要的程序員。在隨意的討論中,大多數程序員似乎認為單元測試非常重要。

但是,當我開始使用代碼並問“單元測試在哪里?”時,我得到了一個完全不同的故事。我最近在網上問我的程序員朋友為什么不這樣做,以及為什么其他程序員不這樣做呢?不要編寫單元測試。當我問程序員或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都有據可查的文檔,僅通過搜索即可獲得許多資源。在各種社交媒體上也有一個非常活躍和樂於助人的社區,我相信很樂意提供幫助。

技術類文章精選

非技術文章精選


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM