使用 Junit 比较文本文件

53

我正在使用以下代码在JUnit中比较文本文件:

public static void assertReaders(BufferedReader expected,
          BufferedReader actual) throws IOException {
    String line;
    while ((line = expected.readLine()) != null) {
        assertEquals(line, actual.readLine());
    }

    assertNull("Actual had more lines then the expected.", actual.readLine());
    assertNull("Expected had more lines then the actual.", expected.readLine());
}

这种比较文本文件的方法好吗?有更好的选择吗?


1
我为你在问题中提供的解决方案点了个赞。由于这是一个较旧的帖子,JUnit add ons已经过时(可能与JUnit 4不兼容),我不喜欢Apache Utils,并且考虑到你的assertReaders方法非常简洁,我不认为有必要包含整个新的工具库。你的方法一开始就能正常工作 - 谢谢! - PMorganCA
9个回答

52

这里有一种简单的方法来检查文件是否完全相同:

assertEquals("The files differ!", 
    FileUtils.readFileToString(file1, "utf-8"), 
    FileUtils.readFileToString(file2, "utf-8"));

假设 file1file2File 实例,FileUtils 来自于 Apache Commons IO

你无需编写太多代码来维护,这总是有利的。 :) 如果你已经在项目中使用了 Apache Commons,那么这很容易。但是没有像mark's solution里面那样详细、友好的错误信息。

编辑:
嘿,更仔细地查看 FileUtils API,其实有一种更简单的方法

assertTrue("The files differ!", FileUtils.contentEquals(file1, file2));

作为额外的奖励,该版本适用于所有文件,而不仅仅是文本文件。


4
“assertTrue”形式简洁,但当失败时相对无用。至少“assertEquals”方法会显示不同的字符。 - Stephen
3
现在,我建议您使用Google Guava而不是Commons IO来将文件读取为字符串:Files.toString(file1, Charset.forName("UTF-8"))。在这种情况下,区别不大,但总的来说,Guava是一个更干净、文档更好、并且正在积极维护的库。 - Jonik
6
自从Java 7之后,你可以很简单地将文本文件作为字符串读取,而无需使用任何外部库:new String(Files.readAllBytes(Paths.get("/path/to/file.txt")), StandardCharsets.UTF_8) - Jonik

31

junit-addons 对此有很好的支持:FileAssert

它会提供一些异常,例如:

junitx.framework.ComparisonFailure: aa Line [3] expected: [b] but was:[a]

3
请注意,Maven Central仓库中的最新版本是2003年发布的1.4版本,我不确定它是否与最新版本兼容。 - kohlerfc

27

很棒的概述!如果Spring有包含某些内容,那么这就省去了我的网络搜索。 ;) - JBA

21

截至2015年,我会推荐AssertJ,这是一个优雅且全面的断言库。对于文件,您可以针对另一个文件进行断言:

@Test
public void file() {
    File actualFile = new File("actual.txt");
    File expectedFile = new File("expected.txt");
    assertThat(actualFile).hasSameTextualContentAs(expectedFile);
}

或者反对内联字符串:

@Test
public void inline() {
    File actualFile = new File("actual.txt");
    assertThat(linesOf(actualFile)).containsExactly(
            "foo 1",
            "foo 2",
            "foo 3"
    );
}

失败消息也非常详细。如果有不同的行,你会收到以下信息:

java.lang.AssertionError: 
File:
  <actual.txt>
and file:
  <expected.txt>
do not have equal content:
line:<2>, 
Expected :foo 2
Actual   :foo 20

如果其中一个文件有更多的行,则会得到以下结果:

java.lang.AssertionError:
File:
  <actual.txt>
and file:
  <expected.txt>
do not have equal content:
line:<4>,
Expected :EOF
Actual   :foo 4

2
方法hasContentEqualTo已被弃用。请使用hasSameContentAs代替。 - Stephan

11

使用java.nio.file API简单比较两个文件的内容。

byte[] file1Bytes = Files.readAllBytes(Paths.get("Path to File 1"));
byte[] file2Bytes = Files.readAllBytes(Paths.get("Path to File 2"));

String file1 = new String(file1Bytes, StandardCharsets.UTF_8);
String file2 = new String(file2Bytes, StandardCharsets.UTF_8);

assertEquals("The content in the strings should match", file1, file2);

或者如果您想逐行比较:

List<String> file1 = Files.readAllLines(Paths.get("Path to File 1"));
List<String> file2 = Files.readAllLines(Paths.get("Path to File 2"));

assertEquals(file1.size(), file2.size());

for(int i = 0; i < file1.size(); i++) {
   System.out.println("Comparing line: " + i)
   assertEquals(file1.get(i), file2.get(i));
}

7

我建议使用Assert.assertThat和Hamcrest Matcher(JUnit 4.5或更高版本 - 甚至可能是4.4)。

最终的代码可能类似于:

assertThat(fileUnderTest, containsExactText(expectedFile));

我的匹配器在哪里:

class FileMatcher {
   static Matcher<File> containsExactText(File expectedFile){
      return new TypeSafeMatcher<File>(){
         String failure;
         public boolean matchesSafely(File underTest){
            //create readers for each/convert to strings
            //Your implementation here, something like:
              String line;
              while ((line = expected.readLine()) != null) {
                 Matcher<?> equalsMatcher = CoreMatchers.equalTo(line);
                 String actualLine = actual.readLine();
                 if (!equalsMatcher.matches(actualLine){
                    failure = equalsMatcher.describeFailure(actualLine);
                    return false;
                 }
              }
              //record failures for uneven lines
         }

         public String describeFailure(File underTest);
             return failure;
         }
      }
   }
}

Matcher的优点:

  • 组合和重用
  • 可在正常代码以及测试中使用
    • 集合
    • 可用于模拟框架
    • 可用作通用谓词函数
  • 非常好的日志记录能力
  • 可与其他匹配器和描述相结合,失败描述准确而精确

缺点:

  • 显然吧?对于这种特定情况,这比assert或junitx更冗长
  • 您可能需要包含hamcrest库才能获得最大的好处

5

FileUtils真的是一个好东西。这里有另一种简单方法可以检查文件是否完全相同。

assertEquals(FileUtils.checksumCRC32(file1), FileUtils.checksumCRC32(file2));

虽然assertEquals()提供的反馈比assertTrue()更多,但checksumCRC32()的结果是一个长整型,所以可能并没有什么帮助。


1
+1,我猜这对于非常大的文件可能会很有用(当您只关心文件是否不同而不是差异是什么时)。 - Jonik

4

如果“expected”比“actual”多了几行,您将在稍后的“assertNull”之前失败assertEquals。

不过修复起来相当容易:

public static void assertReaders(BufferedReader expected,
    BufferedReader actual) throws IOException {
  String expectedLine;
  while ((expectedLine = expected.readLine()) != null) {
    String actualLine = actual.readLine();
    assertNotNull("Expected had more lines then the actual.", actualLine);
    assertEquals(expectedLine, actualLine);
  }
  assertNull("Actual had more lines then the expected.", actual.readLine());
}

我喜欢你的答案不依赖于任何第三方库,但这段代码无法编译。变量“actual”的作用域仅限于while循环,所以最终的assertNull语句将无法编译。 - buzz3791
@buzz3791:不,actualLine 的作用范围仅限于 while 循环中。而 actual 的作用范围是整个方法。 - Jon Skeet

0
这是我自己实现的equalFiles,您的项目不需要添加任何库。
private static boolean equalFiles(String expectedFileName,
        String resultFileName) {
    boolean equal;
    BufferedReader bExp;
    BufferedReader bRes;
    String expLine ;
    String resLine ;

    equal = false;
    bExp = null ;
    bRes = null ;

    try {
        bExp = new BufferedReader(new FileReader(expectedFileName));
        bRes = new BufferedReader(new FileReader(resultFileName));

        if ((bExp != null) && (bRes != null)) {
            expLine = bExp.readLine() ;
            resLine = bRes.readLine() ;

            equal = ((expLine == null) && (resLine == null)) || ((expLine != null) && expLine.equals(resLine)) ;

            while(equal && expLine != null)
            {
                expLine = bExp.readLine() ;
                resLine = bRes.readLine() ; 
                equal = expLine.equals(resLine) ;
            }
        }
    } catch (Exception e) {

    } finally {
        try {
            if (bExp != null) {
                bExp.close();
            }
            if (bRes != null) {
                bRes.close();
            }
        } catch (Exception e) {
        }

    }

    return equal;

}

并且使用它只需使用常规的 AssertTrue JUnit 方法

assertTrue(equalFiles(expected, output)) ;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接