繁体   English   中英

如何使用Mockito模拟Java Path API?

[英]How do I mock Java Path API with Mockito?

Java Path API是Java File API的更好替代,但是静态方法的大量使用使得很难使用Mockito进行模拟。 从我自己的类中,我注入一个FileSystem实例,在单元测试期间将其替换为模拟。

但是,我需要模拟许多方法(并创建许多模拟)以实现此目的。 在我的测试课程中,这种情况发生了很多次。 因此,我开始考虑设置一个简单的API来注册Path-s并声明相关的行为。

例如,我需要检查流打开时的错误处理。 主班:

class MyClass {
    private FileSystem fileSystem;

    public MyClass(FileSystem fileSystem) {
        this.fileSystem = fileSystem;
    }

    public void operation() {
        String filename = /* such way to retrieve filename, ie database access */
        try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
            /* file content handling */
        } catch (IOException e) {
            /* business error management */
        }
    }
}

测试班:

 class MyClassTest {

     @Test
     public void operation_encounterIOException() {
         //Arrange
         MyClass instance = new MyClass(fileSystem);

         FileSystem fileSystem = mock(FileSystem.class);
         FileSystemProvider fileSystemProvider = mock(FileSystemProvider.class);
         Path path = mock(Path.class);
         doReturn(path).when(fileSystem).getPath("/dir/file.txt");
         doReturn(fileSystemProvider).when(path).provider();
         doThrow(new IOException("fileOperation_checkError")).when(fileSystemProvider).newInputStream(path, (OpenOption)anyVararg());

         //Act
         instance.operation();

         //Assert
         /* ... */
     }

     @Test
     public void operation_normalBehaviour() {
         //Arrange
         MyClass instance = new MyClass(fileSystem);

         FileSystem fileSystem = mock(FileSystem.class);
         FileSystemProvider fileSystemProvider = mock(FileSystemProvider.class);
         Path path = mock(Path.class);
         doReturn(path).when(fileSystem).getPath("/dir/file.txt");
         doReturn(fileSystemProvider).when(path).provider();
         ByteArrayInputStream in = new ByteArrayInputStream(/* arranged content */);
         doReturn(in).when(fileSystemProvider).newInputStream(path, (OpenOption)anyVararg());

         //Act
         instance.operation();

         //Assert
         /* ... */
     }
 }

我有许多此类的类/测试,模拟设置可能会更加棘手,因为静态方法可能会通过Path API调用3-6个非静态方法。 我已经重构测试以避免大多数冗余代码,但是随着我的Path API使用量的增加,我的简单API往往非常有限。 因此,再次该重构。

但是,我正在考虑的逻辑很难看,并且需要大量的基本用法代码。 我希望简化API模拟(无论是否为Java Path API)的方式基于以下原则:

  1. 创建实现接口或将类扩展为模拟的抽象类。
  2. 实现我不想模拟的方法。
  3. 调用“部分模拟”时,我想执行(按优先顺序):显式模拟的方法,已实现的方法,默认答案。

为了实现第三步,我考虑创建一个Answer ,用于查找实现的方法并回退到默认答案。 然后在模拟创建时传递此Answer的实例。

是否有现有的方法可以直接从Mockito或其他解决问题的方法来实现这一目标?

您的问题是您违反了“ 单一责任原则”

您有两个问题:

  1. 查找并找到文件,获取InputStream
  2. 处理文件。
    • 实际上,这很可能也应该细分为一些子问题,但这不在此问题的范围内。

您试图用一种方法完成这两项工作,这迫使您要做大量的额外工作。 而是将工作分为两个不同的类。 例如,如果代码是这样构造的:

class MyClass {
  private FileSystem fileSystem;
  private final StreamProcessor processor;

  public MyClass(FileSystem fileSystem, StreamProcessor processor) {
    this.fileSystem = fileSystem;
    this.processor = processor;
  }

  public void operation() {
    String filename = /* such way to retrieve filename, ie database access */
    try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
        processor.process(in);
    } catch (IOException e) {
        /* business error management */
    }
  }
}
class StreamProcessor {
  public StreamProcessor() {
    // maybe set dependencies, depending on the need of your app
  }

  public void process(InputStream in) throws IOException {
    /* file content handling */
  }
}

现在,我们将职责分为两个地方。 InputStream您要测试的所有业务逻辑工作的类,仅需要一个输入流。 实际上,我什至不会嘲笑它,因为它只是数据。 您可以根据需要以任何方式加载InputStream ,例如使用您在问题中提到的ByteArrayInputStream StreamProcessor测试中不需要Java Path API的任何代码

此外,如果您以通用方式访问文件,则只需进行一项测试即可确保该行为有效。 您还可以使StreamProcessor成为接口,然后在代码库的不同部分中,对不同类型的文件执行不同的工作,同时将不同的StreamProcessor传递到文件API中。


您在评论中说:

听起来不错,但我必须忍受大量旧代码。 我开始介绍单元测试,并且不想重构太多的“应用程序”代码。

最好的方法是我上面说的。 但是,如果要进行最少的更改以添加测试,则应执行以下操作:

旧代码:

public void operation() {
  String filename = /* such way to retrieve filename, ie database access */
  try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
    /* file content handling */
  } catch (IOException e) {
    /* business error management */
  }
}

新代码:

public void operation() {
  String filename = /* such way to retrieve filename, ie database access */
  try (InputStream in = Files.newInputStream(fileSystem.getPath(filename))) {
    new StreamProcessor().process(in);
  } catch (IOException e) {
    /* business error management */
  }
}
public class StreamProcessor {
  public void process(InputStream in) throws IOException {
    /* file content handling */
    /* just cut-paste the other code */
  }
}

这是做我上面描述的最少的侵入方法。 我描述的原始方法更好 ,但是显然它涉及的重构更多。 这种方法应该几乎不涉及其他任何代码更改,但是将允许您编写测试。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM