2013-11-26 66 views
5

我想知道是否有人找到一種方法來存儲/模擬lambda中的邏輯而不使lambda的可見性?Java 8的單元測試Lambdas

public List<Item> processFile(String fileName) { 
    // do some magic.. 
    Function<String, List<String>> reader = (fileName) -> { 
     List<String> items = new ArrayList<>(); 
     try (BufferedReader br = new BufferedReader(new FileReader(fileName))) { 
      String output; 
      while ((output = br.readLine()) != null) { 
      items.add(output); 
      } 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    return items; 
    }; 

    List<String> lines = reader.apply("file.csv"); 
    // do some more magic.. 
} 
+0

一個lambda可以像其他任何東西一樣加載上下文(如果你正在尋找它的內部存根)。但要注意,沒有捕獲變量的lambda最有可能是單身。 – aepurniet

回答

9

我想說的規則是,如果一個lambda表達式是如此複雜,你覺得有必要模擬出它的位,這可能是太複雜了。它應該被分解成更小的部分組合在一起,或者模型需要進行調整以使其更易於合成。

我會說,Andrey Chaschev's answer這表明參數化依賴是一個很好的,可能適用於某些情況。所以,+1。一個可以繼續這一進程,並分解加工成更小的碎片,就像這樣:

public List<Item> processFile(
    String fileName, 
    Function<String, BufferedReader> toReader, 
    Function<BufferedReader, List<String>> toStringList, 
    Function<List<String>, List<Item>> toItemList) 
{ 
    List<String> lines = null; 
    try (BufferedReader br = toReader.apply(fileName)) { 
     lines = toStringList.apply(br); 
    } catch (IOException ioe) { /* ... */ } 

    return toItemList.apply(lines); 
} 

一對夫婦的意見這一點,雖然。首先,由於各種lambda投擲討厭IOExceptions,並且Function類型沒有被聲明爲拋出該異常,所以這不起作用。第二個是你必須傳遞給這個函數的lambdas是可怕的。儘管這是行不通的(因爲檢查的例外),但我把它寫出來了:

void processAnActualFile() { 
    List<Item> items = processFile(
     "file.csv", 
     fname -> new BufferedReader(new FileReader(fname)), 
       // ERROR: uncaught IOException 
     br -> { 
      List<String> result = new ArrayList<>(); 
      String line; 
      while ((line = br.readLine()) != null) { 
       result.add(line); 
      } 
      return result; 
     },  // ERROR: uncaught IOException 
     stringList -> { 
      List<Item> result = new ArrayList<>(); 
      for (String line : stringList) { 
       result.add(new Item(line)); 
      } 
      return result; 
     }); 
} 

呃!我想我已經發現了新的代碼味道:

如果你必須在lambda中編寫for-loop或while循環,那麼你做錯了什麼。

這裏有一些事情正在進行。首先,I/O庫實際上由緊密耦合的不同實現部分(InputStream,Reader,BufferedReader)組成。試圖將它們分開是沒有用的。事實上,圖書館已經發展,因此有一些便利工具(例如NIO Files.readAllLines)能夠爲您處理一大堆腿部工作。

更重要的一點是設計函數在它們之間傳遞值的集合(列表)並構造這些函數實際上是錯誤的方法。它導致每個函數都必須在它內部編寫一個循環。我們真正想要做的是編寫函數,每個函數都在一個值上運行,然後讓Java 8中的新Streams庫負責我們的聚合。

從評論「做更多的魔法」所描述的代碼中提取的關鍵功能是將List<String>轉換成List<Item>。我們想抽取轉換一個StringItem計算,像這樣:

class Item { 
    static Item fromString(String s) { 
     // do a little bit of magic 
    } 
} 

一旦你有了這個,那麼你就可以讓流和NIO庫做了一堆的工作適合你:

public List<Item> processFile(String fileName) { 
    try (Stream<String> lines = Files.lines(Paths.get(fileName))) { 
     return lines.map(Item::fromString) 
        .collect(Collectors.toList()); 
    } catch (IOException ioe) { 
     ioe.printStackTrace(); 
     return Collections.emptyList(); 
    } 
} 

(請注意,這個簡短方法的更多一半是用於處理IOException。)

現在,如果你想做一些單元測試,你真正需要測試的是一點魔法。所以,你把它包裝成不同的流管道,像這樣:

void testItemCreation() { 
    List<Item> result = 
     Arrays.asList("first", "second", "third") 
       .stream() 
       .map(Item::fromString) 
       .collect(Collectors.toList()); 
    // make assertions over result 
} 

(其實,即使這是不完全正確你想要編寫單元測試單行轉換成一個單一的Item不過。也許你有一些測試數據的地方,所以你將它轉換爲這樣的項目列表,然後在該列表中的結果條目的關係,使全球的斷言。)


我徘徊這與您如何拆分lambda的原始問題相去甚遠。請原諒我放縱自己。

原始示例中的lambda非常不幸,因爲Java I/O庫非常麻煩,並且NIO庫中有新的API將示例轉換爲單行內容。

但是,這裏的教訓是,不是編寫處理聚合的函數,而是編寫處理各個值的函數,並讓流處理聚合。通過這種方式,您可以通過以不同方式將流管線插入到一起來測試,而不是通過模擬複雜lambda的位來進行測試。

3

我不知道如果這就是你在問什麼,但你可以提取拉姆達即一個lambda到其他類或者是它作爲參數傳遞。在下面的例子我嘲笑讀者創作:

public static void processFile(String fileName, Function<String, BufferedReader> readerSupplier) { 
    // do some magic.. 
    Function<String, List<String>> reader = (name) -> { 
     List<String> items = new ArrayList<>(); 
     try(BufferedReader br = readerSupplier.apply(name)){ 
      String output; 
      while ((output = br.readLine()) != null) { 
       items.add(output); 
      } 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

     return items; 
    }; 

    List<String> lines = reader.apply(fileName); 
    // do some more magic.. 
} 

public static void main(String[] args) { 
    // mocked call 
    processFile("file.csv", name -> new BufferedReader(new StringReader("line1\nline2\n"))); 

    //original call 
    processFile("1.csv", name -> { 
     try { 
      return new BufferedReader(new FileReader(name)); 
     } catch (FileNotFoundException e) { 
      throw new RuntimeException(e); 
     } 
    }); 
} 
+0

+1可能有用的傳遞函數作爲參數的技術。 –