2013-11-26 46 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可能有用的传递函数作为参数的技术。 –