在Java应用程序中嵌入迷你语言的好方法/工具是什么?

时间:2012-08-10 20:07:00

标签: java interpreter dsl evaluation

我正在尝试解决以下问题:

  • 我有一个Java应用程序(不是由我编写),其目的是获取2个报告文件(只是以逗号分隔的表输出),并将每个文件的每一行和每一列按单元格进行比较 - 基本上,用于回归测试。

  • 我想通过以下功能来增强它:

    • 假设我做了一次软件更改,导致C1列中的所有值都增加了100%。

    • 比较特定列“C1”时,软件当前将报告C1中100%的值发生变化。

    • 我想要做的是能够配置回归测试仪,而不是简单地比较“两个文件中的第1行中的C1是相同的”,而是应用预先配置的比较规则/公式到现场,例如“确保文件#2中的C1值比文件#1中的C1值大2倍”。通过这种方式,我不仅可以抑制100%的假冒回归错误,即每行的C1列不匹配,而且还可以捕获任何真正的错误,其中C1列使用新软件并不是100%更大。

以前在Perl中对此类功能进行编码时,解决方案非常简单 - 只需将代码自定义每列比较器配置为存储在配置文件中的Perl哈希,哈希键为列和哈希值是Perl子程序,用于比较我想要的任何复杂方式的2个值。

显然,这种方法不适用于Java,因为我不能用Java编写自定义比较器逻辑,并且让Java在不同的运行时间内评估/编译/执行这些比较器。

这意味着我需要提出一些特定领域的可解释语言,我的不同之处就是解释/评估/执行。

由于我对Java生态系统和库不是很熟悉,所以我问:

为可配置的比较器逻辑实现此DSL有什么好的解决方案?

我的要求是:

  • 解决方案必须“像啤酒一样免费”

  • 解决方案必须“收缩包裹”。例如。一个现有的库,我可以简单地放入我的代码,添加配置文件,并让它工作。

    需要我编写自己的BNF语法并提供通用语法分析器的东西,我必须编写自己的解释器。

  • 就数据处理和语法丰富而言,解决方案必须具有相当的灵活性。 E.g:

    • 你至少应该能够从DSL中传入 - 和引用/地址 - 整个数据行作为哈希

    • 它应该具有相当完整的语法;至少做基本的字符串操作(连接,子字符串,理想情况下一些级别的正则表达式匹配和替换);基本算术,包括对浮点数#s进行abs(val1 - val2) > tolerance的能力;和基本的流量控制/逻辑,如条件和理想的循环。

  • 解决方案应该相当快,并且必须是可扩展的。例如。比较100x100大小的文件不应该花10分钟与10-20个自定义列。

如果重要,目标环境是Java 1.6

3 个答案:

答案 0 :(得分:4)

有多种动态JVM编程语言可以轻松集成到Java应用程序中。我认为值得研究Groovy和/或Scala

另一种可能的选择是使用XTextXTend创建您自己的DSL。

答案 1 :(得分:3)

每当谈到Java中的动态功能时,我都会想出Janino,一个迷人的运行时和内存中的编译器。在您的情况下,它将为您提供类似于普通Java的eval(...)的内容,请参阅:http://docs.codehaus.org/display/JANINO/Basic#Basic-expressionevaluator

这里的要点是您的测试配置没有DSL,但您可以使用普通的Java语法在测试配置中编写自定义表达式。

下面提出的解决方案无法满足的唯一要求是您可以在配置文件中解决整行问题。该解决方案假设您编写了一个Java测试类,它逐个值地逐个测试数据(或更好的逐对),并使用您配置的表达式来比较单个值。因此动态部分是表达式,静态部分是测试数据的迭代。

然而,所需的代码非常小而且简单,如下所示:

Config File(Java属性语法,键是列名,值是测试表达式):

# custom expression for column C1
C1  = a == 2 * b           
# custom expression for column C4
C4  = a == b ^ 2           
# custom expression for column C47
C47 = Math.abs(a - b) < 1  

测试代码草图:

// read config file into Properties
Properties expressions = Properties.load(....);

// Compile expressions, this could also be done lazily
HashMap<String, ExpressionEvaluator> evals = new HashMap<String, ExpressionEvaluator>();
for (String column : expressions.stringPropertyNames()) {
    String expression = expressions.getProperty(column);
    ExpressionEvaluator eval = new ExpressionEvaluator(
        expression,                  // expression
        boolean.class,               // expressionType
        new String[] { "a", "b" },   // parameterNames
        new Class[] { int.class, int.class } // parameterTypes, depends on your data
    );
    evals.put(column, eval);
}

// Now for every value pair (a, b) check if a custom expression is defined 
// for the according column and if yes execute: 
Boolean correct = (Boolean) theEvalForTheColumn.evaluate(
    new Object[] { a, b }          // parameterValues
);
if (!correct) throw Exception("Wrong values...");

正如在Janino页面上所说,编译表达式的性能非常好(它们是真正的java字节代码),只有编译才会减慢进程。因此,如果您有许多自定义表达式,它可能会成为一个问题,但它应该可以随着越来越多的值进行扩展。第h

答案 2 :(得分:0)

不需要嵌入语言。将比较器定义为接口。

您可以使用class.forName(name)加载在运行时定义接口的类, 其中name可以通过命令行参数或任何其他方便的方式指定。

您的比较器类看起来像

class SpecialColumn3 implements ColumnCompare
{ boolean compare(String a,String b) {...}
}