首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何使用Mockito模拟Java Path API?

如何使用Mockito模拟Java Path API?
EN

Stack Overflow用户
提问于 2015-08-28 13:01:24
回答 1查看 10.1K关注 0票数 6

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

但是,为了实现这一点,我需要模拟许多方法(并且还需要创建大量的模拟)。这种情况在我的测试类中多次发生。因此,我开始考虑设置一个简单的API来注册路径和声明相关行为。

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

代码语言:javascript
运行
复制
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 */
        }
    }
}

考试班:

代码语言:javascript
运行
复制
 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
         /* ... */
     }
 }

我有许多这类的类/测试,而模拟设置可能更棘手,因为静态方法可以通过路径API调用3-6个非静态方法。为了避免大多数冗余代码,我进行了重构测试,但是随着Path API使用量的增加,我的简单API往往非常有限。所以现在又是重构的时候了。

然而,我所想到的逻辑似乎很糟糕,需要大量代码才能用于基本用法。我想要简化API模拟(不管是Java路径API还是非Java路径API)的方式都是基于以下原则:

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

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

是否有直接从Mockito或其他方法来解决这个问题的方法?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2015-08-28 14:45:50

你的问题是你违反了Single Responsibility Principle

你有两个顾虑:

  1. 找到并找到一个文件,得到一个InputStream
  2. 处理文件。
    • 实际上,这很可能也会被分解成子关注点,但这不在这个问题的范围之内。

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

代码语言:javascript
运行
复制
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 */
    }
  }
}
代码语言:javascript
运行
复制
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。Java在您的StreamProcessor 测试中不需要任何代码。

此外,如果您正在以一种常见的方式访问文件,则只需进行一次测试就可以确保该行为有效。您还可以使StreamProcessor成为一个接口,然后在代码库的不同部分为不同类型的文件执行不同的任务,同时将不同的StreamProcessor传递到文件API中。

你在评论中说:

听起来不错,但我不得不忍受大量的遗留代码。我开始引入单元测试,不想重构太多的“应用程序”代码。

最好的办法就是我上面所说的。但是,如果您希望进行最小数量的更改来添加测试,请执行以下操作:

旧代码:

代码语言:javascript
运行
复制
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 */
  }
}

新代码:

代码语言:javascript
运行
复制
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 */
  }
}
代码语言:javascript
运行
复制
public class StreamProcessor {
  public void process(InputStream in) throws IOException {
    /* file content handling */
    /* just cut-paste the other code */
  }
}

这是我上面描述的最不具侵略性的方法。我最初描述的方式更好,但很明显,这是一个更复杂的重构。这种方式几乎不涉及任何其他代码更改,但将允许您编写测试。

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32271703

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档