如何在 Java 项目中包含一个资源文件以仅与 new File() 一起使用?

How do I include a resource file in Java project to be used with just new File()?

我正在使用 Java 为 Pig 编写 UDF。它工作正常,但 Pig 没有给我分离环境的选项。我的 Pig 脚本正在做的是从 IP 地址获取地理位置。

这是我在地理位置部分的代码。

private static final String GEO_DB = "GeoLite2-City.mmdb";
private static final String GEO_FILE = "/geo/" + GEO_DB;

 public Map<String, Object> geoData(String ipStr) {
        Map<String, Object> geoMap = new HashMap<String, Object>();

        DatabaseReader reader = new DatabaseReader.Builder(new File(GEO_DB)).build();
            // other stuff
    }

GeoLite2-City.mmdb 存在于 HDFS 中,这就是为什么我可以使用 /geo/GeoLite2-City.mmdb 从绝对路径引用的原因。

但是,我无法通过我的 JUnit 测试做到这一点,或者我必须在我的本地计算机和 Jenkins 上创建 /geo/GeoLite2-City.mmdb,这并不理想。我正在尝试找出一种方法让我的测试在使用 new File(GEO_DB) 而不是 getClass().getResourceAsStream('./geo/GeoLite2-City.mmdb') 因为

getClass().getResourceAsStream('./geo/GeoLite2-City.mmdb')

在 Hadoop 中不起作用。

如果我 运行 Junit 测试它会失败,因为我的本地机器上没有 /geo/GeoLite2-City.mmdb

有没有办法克服这个问题?我只是希望我的测试通过而不更改使用 getClass().getResourceAsStream 的代码,我不能 if/else 解决这个问题,因为 Pig 没有给我一种传递参数的方法,或者我可能丢失了东西。

这是我的 JUnit 测试

@Test
@Ignore
public void shouldGetGeoData() throws Exception {
    String ipTest = "128.101.101.101";

    Map<String, Object> geoJson = new LogLine2Json().geoData(ipTest);

    assertThat(geoJson.get("lLa").toString(), is(equalTo("44.9759")));
    assertThat(geoJson.get("lLo").toString(), is(equalTo("-93.2166")));

}

如果我从资源文件夹中读取数据库文件,它会起作用。这就是为什么我有@Ignore

你不知道。您的问题体现了术语上的矛盾。资源不是文件,也不存在于文件系统中。您可以 将文件与 JAR 分开分发并将其用作 File 将其包含在 JAR 中并将其用作一种资源。不是都。你要拿定主意。

此外,您的整个代码看起来都无法测试。

每次在生产代码中直接调用 new 时,都会防止依赖注入;从而使测试代码变得更加困难。

重点是不要在生产代码中调用 new File()。 相反,您可以使用为您提供 "ready to use" DatabaseReader 对象的工厂。然后你可以测试你的工厂做正确的事;并且您可以在测试此代码时模拟该工厂(return 模拟数据库 reader)。

所以,那个文件实例只是您 "testing problems" 此处的顶部。

老实说:不要先编写生产代码。做TDD:先写测试用例;您很快就会了解到,您在此处展示的此类生产代码确实很难测试。当您应用 TDD 时,您从 "test perspective" 开始,您将创建真正可测试的生产代码。

您必须使文件位置可配置。例如。通过构造函数注入它。例如。您可以创建一个仅用于测试的非默认构造函数。

public class LogLine2Json {
  private static final String DEFAULT_GEO_DB = "GeoLite2-City.mmdb";
  private static final String DEFAULT_GEO_FILE = "/geo/" + GEO_DB;

  private final String geoFile;

  public LogLine2Json() {
    this(DEFAULT_GEO_FILE);
  }

  LogLine2Json(String geoFile) {
    this.geoFile = geoFile;
  }

  public Map<String, Object> geoData(String ipStr) {
    Map<String, Object> geoMap = new HashMap<String, Object>();

    File file = new File(geoFile);
    DatabaseReader reader = new DatabaseReader.Builder(file).build();
    // other stuff
  }
}

现在您可以从该资源创建一个文件并在您的测试中使用该文件。

public class LogLine2JsonTest {
    @Rule
    public final TemporaryFolder folder = new TemporaryFolder();

    @Test
    public void shouldGetGeoData() throws Exception {
      File dbFile = copyResourceToFile("/geo/GeoLite2-City.mmdb");
      String ipTest = "128.101.101.101";

      LogLine2Json logLine2Json = new LogLine2Json(dbFile.getAbsolutePath())
      Map<String, Object> geoJson = logLine2Json.geoData(ipTest);

      assertThat(geoJson.get("lLa").toString(), is(equalTo("44.9759")));
      assertThat(geoJson.get("lLo").toString(), is(equalTo("-93.2166")));
    }

    private File copyResourceToFile(String name) throws IOException {
      InputStream resource = getClass().getResourceAsStream(name);
      File file = folder.newFile();
      Files.copy(resource, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
      return file;
    }
}

TemporaryFolder 是一个 JUnit 规则,它删除之后测试期间创建的每个文件。

您可以使用 hasToString 匹配器修改断言。如果测试失败,这将为您提供更详细的信息。 (而且你必须 read/write 更少的代码。)

assertThat(geoJson.get("lLa"), hasToString("44.9759"));
assertThat(geoJson.get("lLo"), hasToString("-93.2166"));