【问题标题】:Groovy application classes and Java Unit testsGroovy 应用程序类和 Java 单元测试
【发布时间】:2010-11-11 03:22:27
【问题描述】:

我有一个由 Maven 构建的应用程序,它是一个混合 Groovy 和 Java 项目。

使用 GMaven 插件 (v1.3),我可以轻松地针对 Java 和 Groovy 类运行 Groovy 测试。在构建应用程序期间,我的 java 类与声明 GroovyObject 方法的增强存根文件链接。

但是,如果我在 Java 中针对应用程序 Groovy 代码编写测试并尝试从 GroovyObject 调用方法,则会出现编译时失败。

有什么解决方法吗? GMaven 是否有任何配置参数可以实现这一点?

谢谢。

这是我的 pom 中的 build.plugins 内容:

             <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <includes>
                            <include>target/generated-sources/groovy-stubs/main</include>
                        </includes>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.codehaus.gmaven</groupId>
                    <artifactId>gmaven-plugin</artifactId>
                    <version>1.3</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>generateStubs</goal>
                                <goal>generateTestStubs</goal>

                                <goal>compile</goal>
                                <goal>generateTestStubs</goal>
                                <goal>testCompile</goal>
                            </goals>
                            <configuration>
                                <!-- providerSelection probably defaults to 1.7 now -->
                                <providerSelection>1.7</providerSelection>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>

这是java测试类:

public class JavaGroovyTest extends TestCase {
      @Test
      public void testGroovyClasses(){
        Model m = new Model();  //Model is an application class written in Groovy
        assertNotNull(m);
        assertEquals(4,m.getMetaClass().getProperties().size());
      }
}

这是编译器的输出:

[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Compilation failure
/Users/mkohout/Documents/trunk/src/test/java/JavaGroovyTest.java:[17,24] cannot find symbol
symbol  : method getMetaClass()
location: class com.q.Model

【问题讨论】:

  • 你看到了哪些编译时失败?
  • 毕竟,在实践中,不能从静态编译的 Java 访问大部分 Groovy“运行时元方法”。 - 充其量,四个(!)GroovyObject 方法是可访问的。 - 有其他运行时 Groovy 方法的变通方法,但这是不好的黑客攻击。

标签: maven-2 groovy


【解决方案1】:

将 groovy 对象声明为 GroovyObject。示例:

import groovy.lang.GroovyObject

public class JavaGroovyTest extends TestCase {
    @Test
    public void testGroovyClasses(){
        GroovyObject m = new Model();  //Model is an application class written in Groovy
        assertNotNull(m);
        assertEquals(4,m.getMetaClass().getProperties().size());
    }
}

编辑:更长的解释

groovy 编译器将getMetaClass 方法添加到类中,但将其标记为synthetic。这是作为“实现细节”生成的方法和字段的内部 JVM 标志,并且不应对代码可见。您可以使用 javap 验证这一点:

$ javap -verbose Model | grep -A18 getMetaClass\(\)
public groovy.lang.MetaClass getMetaClass();
  Code:
   Stack=2, Locals=1, Args_size=1
   0:   aload_0
   1:   getfield        #42; //Field metaClass:Lgroovy/lang/MetaClass;
   4:   dup
   5:   ifnull  9
   8:   areturn
   9:   pop
   10:  aload_0
   11:  dup
   12:  invokevirtual   #28; //Method $getStaticMetaClass:()Lgroovy/lang/MetaClass;
   15:  putfield        #42; //Field metaClass:Lgroovy/lang/MetaClass;
   18:  aload_0
   19:  getfield        #42; //Field metaClass:Lgroovy/lang/MetaClass;
   22:  areturn
   23:  nop
   Synthetic: true

不过,您可以通过将其转换为 groovy.lang.GroovyObject 接口来解决此问题,该接口声明了 getMetaClass 方法(这次不是合成的)。

这可能不是一个很好的解决方案,但话虽如此,首先在 java 中探索 groovy 元类可能是不明智的。如果在您的测试中不能只使用 groovy,我会考虑使用普通的 java 可访问方法从 groovy 类中公开您需要的元类信息。

【讨论】:

  • 嗯.. 这将适用于这个特定的测试,但对于其他(现实的)测试,我会使用模型之外的字段等等。我想在访问这些方法时可以强制转换为 GroovyObject,但这似乎是一种 hack。 IntelliJ 的智能感知似乎正在解决对模型的常规方法的调用......我怎样才能让 maven 做同样的事情?
【解决方案2】:

简短回答:您无法从 Java 访问 Groovy “元方法”。

长答案:

DefaultGroovyMethods.getMetaClass(..) 不是一个可以静态编译成Java字节码的方法。

[更正: 与此同时,Don 发布了一个答案,建议投到GroovyObject。这是正确的,这样你应该能够像这样调用 Groovy 元方法:

List<Book> books = (List<Book>) 
    ((GroovyObject) Book).invokeMethod(
    "findAllByAuthorAndTitle", new String[] {"author", "title"})

(或类似的)。 - 然而,这对于日常编程来说是不切实际的。 ]

看看DefaultGroovyMethods ApiDocs。每个方法的第一个参数是对象的运行时类型。相应方法的其余部分构成要在 Groovy 代码中使用的方法签名。您已经熟悉其中的许多内容。

所有这些“运行时元方法”都静态编译GroovyObject((几乎)任何Groovy对象都派生自该类型),但是,当调用时,动态调度 在运行时 - 通常使用GroovyObject.invokeMethod(String, Object) method

因此,您的 Java 代码调用了一个在编译时根本不存在的方法。 - 但是,如果 Groovy 代码 not 编译为 Java 字节码,它怎么能引用该方法呢? - Groovy (Java) 字节码不直接引用方法(构造函数、属性等),而是构建在运行时调用以进行动态调度的结构。

例如,您用 Groovy 编写的测试类将编译成这样(为清楚起见,缩短了):

public class JavaGroovyTest extends TestCase
  implements GroovyObject
{
  public JavaGroovyTest()
  {
    JavaGroovyTest this;
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    MetaClass tmp12_9 = $getStaticMetaClass(); this.metaClass = ((MetaClass)ScriptBytecodeAdapter.castToType(tmp12_9, $get$$class$groovy$lang$MetaClass())); tmp12_9;
  }
  public void testGroovyClasses() { CallSite[] arrayOfCallSite = $getCallSiteArray(); Model m = arrayOfCallSite[0].callConstructor($get$$class$Model());
    arrayOfCallSite[1].callStatic($get$$class$JavaGroovyTest(), m);
    arrayOfCallSite[2].callStatic($get$$class$JavaGroovyTest(), $const$0, arrayOfCallSite[3].call(arrayOfCallSite[4].call(arrayOfCallSite[5].call(m)))); return;
  }
} 

当执行此代码时,Groovy 运行时将执行成百上千甚至数十亿次查找 ;-) 以便最终不直接调用方法,而是通过类似反射的 invokeMethod(..) 调用。

Groovy 编程语言的大部分(包括构建器和其他库)严重依赖meta programming 的概念,可以在compile-timeruntime 上实现。

不幸的是,Groovy 更喜欢后者,尽管所有动态添加的特性编译为 Java 字节码,并且可以被 Java 代码直接访问。

【讨论】:

  • 感谢您的深入回答。总的来说,我对 Groovy 和动态开发非常满意(我职业生涯的最后 3 年一直是 Groovy/JRuby),但现在我(主要)回到了 Java 世界。也许我误解了 GMaven 的工作原理,但是从他们的网站(gmaven.codehaus.org/Building+Groovy+Projects),我读到“存根类文件用于在编译时解析类,在运行时将使用已编译的 Groovy 类”,这意味着我可以从 Java 调用这些方法。事实上,在应用程序非测试代码中,我已经做到了,并且运行良好。
  • 当我在测试中设置断点时(注释掉对 getMetaClass() 的调用),我可以毫无困难地评估“m.getMetaClass()”......这让我相信getMetaClass 不需要生成 groovy 类型的调用站点。
  • 如果查看类文件,您会看到 getMetaClass 被定义为常规方法,可从 java:javap Model | grep getMetaClass 访问。然而,groovy 用综合属性标记它,所以 javac 假装它不存在。转换到 GroovyObject 接口可以解决这个问题。
猜你喜欢
  • 1970-01-01
  • 2011-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-21
  • 1970-01-01
  • 2017-08-05
  • 1970-01-01
相关资源
最近更新 更多