【问题标题】:How to test an interpreter using JUnit?如何使用 JUnit 测试解释器?
【发布时间】:2011-12-03 20:29:39
【问题描述】:

我正在使用 JUnit 框架从 Java 中的某种编程语言为解释器编写测试。为此,我创建了大量的测试用例,其中大多数包含测试语言中的代码 sn-ps。由于这些 sn-ps 通常很小,因此可以方便地将它们嵌入 Java 代码中。但是,Java 不支持多行字符串文字,这使得代码 sn-ps 由于转义序列和拆分较长字符串文字的必要性而有点晦涩,例如:

String output = run("let a := 21;\n" +
                    "let b := 21;\n" +
                    "print a + b;");
assertEquals(output, "42");

理想情况下,我想要这样的东西:

String output = run("""
    let a := 21;
    let b := 21;
    print a + b;
""");
assertEquals(output, "42");

一种可能的解决方案是将代码 sn-ps 移动到外部文件,并从相应的测试用例中引用每个文件。然而,这增加了很大的维护负担。

另一种解决方案是使用不同的 JVM 语言来编写测试,例如支持多行字符串文字的 Scala 或 Jython。这将为项目添加一个新的依赖项,并需要移植现有的测试。

有没有其他方法可以在不增加太多维护的同时保持测试代码sn-ps的清晰?

【问题讨论】:

  • 请编辑这个问题,因为它不是关于解释器,而是关于多行字符串。

标签: java testing junit interpreter multilinestring


【解决方案1】:

将测试用例移动到文件中过去对我有用,它也是一个解释器:

  1. 创建了一个 XML 文件,其中包含要解释的 sn-ps 以及预期的结果。这是一个相当简单的 XML 定义,一个测试元素列表,主要包含 testIDvalueexpected resulttype 和一个 description
  2. 只实现了一个读取文件并循环遍历其内容的 JUnit 测试,以防失败,我们使用 testIDdescription 记录失败的测试。

这主要是因为我们对解释器有一个定义良好的通用接口,就像你的 run 方法一样,所以重构仍然是可能的。在我们的例子中,这并没有增加维护工作,事实上我们可以通过向 XML 文件中添加更多元素来轻松创建新测试。

也许这不是使用单元测试的最佳方式,但对我们来说效果很好。

【讨论】:

  • 非常感谢您分享您的经验。我考虑过类似的方法,但似乎只有一个加载和执行所有内容的 JUnit 测试会使调试变得更加困难。当您有许多 JUnit 测试用例时,您可以在失败的测试用例中放置一个断点,然后使用 IDE 轻松地重新执行它。但是你如何用一个测试用例来做呢?
  • @vitaut:你说得对,这有点笨拙。我们通过定义由循环方法调用的 1 或 2 个内部方法以编程方式解决了这个问题。如果我没记错的话,我们还有多个 XML 文件和一个用于 XML 内的每个测试用例的开关,以启用/禁用某些测试 - 因此您可以手动将测试执行减少到一个用例,或者您可以只复制所需的测试值如果您需要调试器,则进入另一个测试类并从您的 IDE 手动执行它。这是 3 或 4 年前的事了,所以还没有提供使用其他语言的选项。
  • @vitaut,您可以使用的一个小技巧是添加一个标记字符(如感叹号)作为您要调试的测试名称/标题的第一个字符,并在其中添加几行你的循环就像if (name.startsWith("!")) { log.debug("Debugging " + name); } 并将断点放在日志行上。这样,您可以通过添加/删除一个 ! 来选择要调试的测试。
【解决方案2】:

既然您在谈论其他 JVM 语言,您是否考虑过 Groovy?您必须添加一个外部依赖项,但仅在编译/测试时(您不必将其放入生产包中),并且它提供多行字符串。在您的情况下还有一个主要优势:它的语法向后兼容 Java(这意味着您不必重写测试)!

【讨论】:

    【解决方案3】:

    我过去曾这样做过。我做了类似于 home 建议的事情,我使用了包含测试及其预期结果的外部文件,但使用了 @Parameterized 测试运行器。

    @RunWith(Parameterized.class)
    public class ParameterTest {
        @Parameters
        public static List<Object[]> data() {
            List<Object[]> list = new LinkedList<Object[]>();
            for (File file : new File("/temp").listFiles()) {
                list.add(new Object[]{file.getAbsolutePath(), readFile(file)});
            }
    
            return list;
        }
    
        private static String readFile(File file) {
            // read file 
            return "file contents";
        }
    
        private String filename;
        private String contents;
    
        public ParameterTest(String filename, String contents) {
            this.filename = filename;
            this.contents = contents;
        }
    
        @Test
        public void test1() {
            // here we test something
        }
    
        @Test
        public void test2() {
            // here we test something
        }
    }
    

    在这里,我们为 /temp 中的每个文件运行一次test1()test2(),并带有文件名和文件内容的参数。测试类被实例化并为您添加到使用@Parameters 注释的方法中的列表中的每个项目调用。

    使用此测试运行程序,您可以在失败时重新运行特定文件;大多数 IDE 支持重新运行单个失败的测试。 @Parameterized 的缺点是没有任何方法可以明智地识别测试,以便名称出现在 Eclipse JUnit 插件中。你得到的只有 0、1、2 等。但至少你可以重新运行失败的测试。

    正如 home 所说,良好的日志记录对于正确识别失败的测试和帮助调试非常重要,尤其是在 IDE 之外运行时。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-13
      • 1970-01-01
      相关资源
      最近更新 更多