惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
V
Visual Studio Blog
The GitHub Blog
The GitHub Blog
Apple Machine Learning Research
Apple Machine Learning Research
J
Java Code Geeks
T
Tailwind CSS Blog
大猫的无限游戏
大猫的无限游戏
Jina AI
Jina AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hugging Face - Blog
Hugging Face - Blog
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
人人都是产品经理
人人都是产品经理
H
Heimdal Security Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
Cyberwarzone
Cyberwarzone
Google DeepMind News
Google DeepMind News
雷峰网
雷峰网
Hacker News: Ask HN
Hacker News: Ask HN
Webroot Blog
Webroot Blog
Microsoft Azure Blog
Microsoft Azure Blog
MyScale Blog
MyScale Blog
A
About on SuperTechFans
V2EX - 技术
V2EX - 技术
小众软件
小众软件
博客园 - Franky
博客园 - 司徒正美
P
Privacy International News Feed
爱范儿
爱范儿
U
Unit 42
博客园 - 叶小钗
The Hacker News
The Hacker News
C
Check Point Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Simon Willison's Weblog
Simon Willison's Weblog
N
News and Events Feed by Topic
D
Docker
T
Threatpost
MongoDB | Blog
MongoDB | Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
H
Help Net Security
L
LINUX DO - 最新话题
Security Latest
Security Latest
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
A
Arctic Wolf
Spread Privacy
Spread Privacy

美团技术团队

美团 · 技术团队 美团 · 技术团队 美团 · 技术团队 美团 · 技术团队 美团 · 技术团队 美团 · 技术团队 美团发布基于 N-gram 全新模型:嵌入扩展新范式,实现轻量化 MoE 高效进化 2025美团技术年货,「马」上到来 多维创新打造强泛化智能体模型,LongCat-Flash-Thinking-2601技术报告发布 美团 EvoCUA 刷新开源 SOTA,会用电脑还会持续进化的智能体! 美团 LongCat-Flash-Thinking-2601 发布,工具调用能力登顶开源 SOTA! KuiTest:基于大模型通识的 UI 交互遍历测试 AAAI 2026 | 美团技术团队学术论文精选 2025 | 美团技术团队热门技术文章汇总 美团 LongCat-Video-Avatar 正式发布,实现开源 SOTA 级拟真表现 大模型剪枝新范式:先浓缩,再剪枝——DenoiseRotator技术解读 LongCat 上线 AI 生图!精准高效,AI 创作不设限 美团发布 LongCat-Image 图像生成模型,编辑能力登顶开源 SOTA AI Coding与单元测试的协同进化:从验证到驱动 R-HORIZON:探索长程推理边界,复旦NLP&美团LongCat联合提出LRMs能力评测新框架 美团 LongCat 发布 AMO-Bench:突破 AIME 评测饱和困境,重新定义 LLM 数学上限 美团 LongCat Interaction 团队发布大模型交互系统技术报告 WOWService 美团 LongCat 团队发布全模态一站式评测基准 UNO-Bench 美团开源LongCat-Audio-Codec,高效语音编解码器助力实时交互落地 NeurIPS 2025 | 美团技术团队论文精选 LongCat-Flash-Omni正式发布并开源:开启全模态实时交互时代 美团 LongCat 团队发布 VitaBench:基于复杂生活场景的交互式 Agent 评测基准 LongCat-Video 视频生成模型正式发布,探索世界模型的第一步 ICCV 2025 | 美团论文精选及多模态推理竞赛冠军方法分享 从0到1建设美团数据库容量评估系统 可验证过程奖励在提升大模型推理效率中的探索与实践 LongCat-Flash-Thinking 正式发布,更强、更专业,保持极速! 开源 | InfiniteTalk:无限长虚拟人视频生成的新范式 美团正式发布并开源 LongCat-Flash-Chat,动态计算开启高效 AI 时代 美团 M17 团队开源 Meeseeks 评测集:揭秘大模型的“听话”能力 可信实验白皮书系列08:开放式分析引擎 | 附PDF合集 美团智能头盔研发实践系列02:软件功能篇 美团智能头盔研发实践系列01:硬件设计篇 ACL 2025 | 美团技术团队论文精选 美团开源OIBench与CoreCodeBench:揭示大模型编程能力的真实水平 可信实验白皮书系列07:高阶实验工具 开源 | MeiGen-MultiTalk:基于单张照片实现多人互动演绎 可信实验白皮书系列06:观察性研究 JDK高版本特性总结与ZGC实践 可信实验白皮书系列05:准实验 可信实验白皮书系列04:随机轮转实验 可信实验白皮书系列03:随机对照实验 可信实验白皮书系列02:AB实验基础 可信实验白皮书系列01:从0到1的方法论与实践指南 MTGR:美团外卖生成式推荐Scaling Law落地实践 OR算法+ML模型混合推理框架架构演进 ICLR&CVPR 2025美团技术团队论文精选 行为正则化与顺序策略优化结合的离线多智能体学习算法 预测技术在美团弹性伸缩场景的探索与应用 美团技术年货 | 600+页电子书,算法、工程、测试、数据、安全系列大合集 鸿蒙应用签名实操及机制探究 2024 | 美团技术团队热门技术文章汇总 AutoConsis:UI内容一致性智能检测 CIKM 2024 | 美团技术团队精选论文解读 大前端:如何突破动态化容器的天花板? KDD 2024 OAG-Challenge Cup赛道三项冠军技术方案解读 新一代实验分析引擎:驱动履约平台的数据决策 大众点评技术部包揽KDD 2024 OAG-Challenge Cup赛道全部3项冠军 ACL 2024 | 美团技术团队精选论文解读 KDD 2024 | 美团技术团队精选论文解读 基本功 | 一文讲清多线程和多线程同步 小程序可测性能力建设与实践 SIGIR 2024 | 美团技术团队精选论文解读 Spark向量化计算在美团生产环境的实践 CVPR 2024 | 美团技术团队精选论文解读 基于多模态信息抽取的菜品知识图谱构建 DDD在大众点评交易系统演进中的应用 美团外卖基于GPU的向量检索系统实践 美团大规模KV存储挑战与架构实践 基于接口数据变异的App健壮性测试实践 美团技术年货 | 600+页电子书,前端、后端、算法、测试、运维系列大合集 美团RASP大规模研发部署实践总结 2023 | 美团技术团队热门技术文章汇总 美团到店终端从标准化到数字化的演进之路 AIOps在美团的探索与实践——事件管理篇 美团技术博客十周年,感谢一路相伴 基于UI交互意图理解的异常检测方法 如何利用「深度上下文兴趣网络」提升点击率? 基于模式挖掘的可靠性治理探索 代码变更风险可视化系统建设与实践 美团多场景建模的探索与实践 MJDK 如何实现压缩速率的 5 倍提升? 如何提供一个可信的AB测试解决方案 KDD 2023 | 美团技术团队精选论文解读 美团前端研发框架Rome实践和演进趋势 斩获CVPR 2023竞赛2项冠军|美团街景理解中视觉分割技术的探索与应用 MySQL自治平台建设的内核原理及实践(下) CVPR 2023 | 美团技术团队精选论文解读 超大规模数据库集群保稳系列之三:美团数据库容灾体系建设实践 超大规模数据库集群保稳系列之二:数据库攻防演练建设实践 Robust 2.0:支持Android R8的升级版热修复框架 超大规模数据库集群保稳系列之一:高可用系统 一次「找回」TraceId的问题分析与过程思考 基于AI+数据驱动的慢查询索引推荐 SOTA!目标检测开源框架YOLOv6 3.0版本来啦
Emma使用与分析
ameng · 2014-04-30 · via 美团技术团队

什么是Emma

EMMA 是一个开源、面向 Java 程序测试覆盖率收集和报告工具。

它通过对编译后的 Java 字节码文件进行插装,在测试执行过程中收集覆盖率信息,并通过支持多种报表格式对覆盖率结果进行展示。 EMMA 所使用的字节码插装不仅保证 EMMA 不会给源代码带来“脏代码”,还确保 EMMA 摆脱了源代码的束缚,这一特点使 EMMA 应用于功能测试成为了可能。

如何使用

emma现在可以通过命令行,ant,maven,Jenkins等方式使用,这里只介绍通过maven和Jenkins来集成emma测试。

在Maven中的使用

直接运行mvn emma:emma,即可。

maven集成emma,需要两个插件,maven-surefire-plugin和emma-maven-plugin,如果之前没有安装,那么maven会自动下载这两个插件。

emma依赖于surefire的配置,默认执行src/test/java的junit测试。为了方便使用,最好在自己的pom里配置maven-surefire-plugin插件。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.8.1</version>
    <configuration>
        <skipTests>false</skipTests>
        <junitArtifactName>junit:junit</junitArtifactName>
        <includes>
            <include>**/*Test.java</include>
        </includes>
        <excludes>
            <exclude>**/*_Roo_*</exclude>
        </excludes>
    </configuration>
</plugin>

这样指定 maven-surefire-plugin 的版本为2.8.1,<skipTests>false</skipTests>不跳过测试,<include>**/*Test.java</include> 只测试以 Test.java 为文件名结尾的文件,而且不测试<exclude>**/*_Roo_*</exclude> 文件名包含_Roo_的文件。

更多的配置可以去查看maven-surefire-plugin的配置说明

在Jenkins中的使用

在Jenkins系统管理的插件管理页面,添加Jenkins Emma plugin插件。

在项目配置中,加入emma:emma即可使用。

emma_1

emma_2

因为测试需要很长时间,而package命令会自动执行测试,所以有时候我们不想所有项目都测试。可以使用如下方案:系统配置两个分支,A分支用于开发,B分支用于上线。我们希望只要A分支进行emma测试,而在B分支不用测试方便快速上线。配置如下

在项目的pom.xml中,<skipTests>false</skipTests>默认不跳过测试,在B项目中配置 clean -U compile package -Dmaven.test.skip=true ,用来跳过测试。

查看测试报告

本地测试查看:

生成的报告是以html存储,默认的位置是${项目目录}/target/site/emma,打开index.html可以查看。

里面有类覆盖率,方法覆盖率,块覆盖率,行覆盖率,

emma_3

选中其中的java文件还可以查看具体的代码覆盖率

emma_4

绿色为有测试的,红色的是测试未覆盖的。

Jenkins 测试查看:

在项目主页中查看

emma_5

这里会有项目的测试覆盖率曲线。x轴是版本变化,y轴是测试覆盖率。

点进图片进入本版本的详细测试报告。具体的形式和本地测试报告差不多,只是 jenkins测试报告没有具体的代码测试详情。

工作原理

emma现在有两种工作方式,on-the-fly模式和offline模式:

emma_6

On the fly 模式往加载的类中加入字节码,在程序运行中,用 EMMA 实现的classLoader 替代应用默认的 Custom classLoader,动态加载类文件,并向类中加入一些统计测试的字节码,这样运行结束后,测试数据也就通过这些临时加入的字节码分析出来。

Offline 模式在类被加载前,在编译生成的class文件中加入字节码。

On the fly 模式比较方便,缺点也比较明显:

emma_11

它不能为被 boot class loader 加载的类生成覆盖率报告;而且,J2EE的classLoader和EMMA的classLoader都是同一类Custom classLoader,在j2ee项目启动过程中,必须选择应用容器(tomcat、Weblogic等等)相应的classLoader,从而无法使用emma的classLoader。同时,jenkins必须配合mvn的框架才能运行emma相关命令,而mvn框架只支持offline模式,所以如果想使用jenkins来做测试报告的话,就无法使用on the fly模式。

在官方文档里也有说明:

As convenient as the on-the-fly mode is, in many cases it is not sufficient. For example, running a commercial J2EE container in a custom instrumenting classloader is practically impossible. Certain (bad) coding practices also fail for code executing in a custom classloader. This on-the-fly instrumentation mode is handy for light-weight testing of main() test methods, individual classes, and small- to- mid-size programs. emmarun also works well with Swing applications.

这时,我们只能求助于 Offline 模式。下面用maven的运行方式来介绍一下。

通过在maven中执行,我们可以看出emma工作时主要运行以下几个步骤

emma_7

  1. 字节码插装并生成插装的元信息文件coverage.em
  2. 运行测试
  3. 每次当 JVM 停止时,内存中记录的执行信息将被清除并被保存到 “coverage.ec” 的文件中。
  4. 生成测试报告。

插装字节码

emma执行是最重要的就是插装字节码:

emma_8

emma循环调用handleFile()方法来遍历目录下所有以’.class’结尾的文件,然后使用 classParser类得到要插装的组件

ClassDef class_table () throws IOException {
    m_table = new ClassDef ();
    magic ();
    version ();
     
    if (DEBUG) System.out.println (s_line);
     
    constant_pool ();
     
    if (DEBUG) System.out.println (s_line);
     
    access_flags ();
    this_class ();
    super_class ();
     
    if (DEBUG) System.out.println (s_line);
     
    //得到所有接口
    interfaces ();
    if (DEBUG) System.out.println (s_line);
     
    //得到所有字段
    fields ();
    if (DEBUG) System.out.println (s_line);
     
    //得到所有方法
    methods ();
    if (DEBUG) System.out.println (s_line);
     
    //得到所有attribute
    attributes ();
    if (DEBUG) System.out.println (s_line);
     
    return m_tabl e;
}

offline模式的插装会生成全新的class文件,默认放在target/generated-classes下。以下是原java文件和插装后的class反编译的java文件。

public class EmmaMain2 {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
     
    //junit调用publicTest()进行测试
    public void publicTest(){
        logger.info("this is a public method");
        logger.info("我是分隔符------------------------------------------------------------------");
        for(int i =1 ;i<10;i++){
            privateTest();
            if(i==4){
                continue;
            }
            if(i==3){
                break;
            }
            //永远不会执行到这一步,所以protectedTest()并没有被覆盖
            if(i==5){
                protectedTest();
                return;
            }
        }
    }
     
    protected void protectedTest(){
        logger.info("this is a protected method");
    }
     
    private void privateTest(){
        logger.info("this is a private method");
    }
}

插装字节码之后反编译的代码

public class EmmaMain2
{
  private Logger logger = LoggerFactory.getLogger(getClass());
  private static final boolean[][] $VRc;
  private static final long serialVersionUID = -6204774612524021426L;
  public EmmaMain2()
  {
    arrayOfBoolean[0] = true;
  }
  public void publicTest()
  {
    boolean[][] tmp3_0 = $VRc; if (tmp3_0 == null) tmp3_0; boolean[] arrayOfBoolean = $VRi()[1]; this.logger.info("this is a public method");
    this.logger.info("我是分隔符------------------------------------------------------------------");
    int i = 1; arrayOfBoolean[0] = true;
    tmpTernaryOp = tmp3_0;
    do
    {
      privateTest();
      arrayOfBoolean[1] = true; if (i == 4) { arrayOfBoolean[2] = true;
      } else
      {
        arrayOfBoolean[3] = true; if (i == 3) { arrayOfBoolean[4] = true;
          break;
        }
        arrayOfBoolean[5] = true; if (i == 5) {
          protectedTest(); arrayOfBoolean[6] = true;
          return;
        }
      }
      i++; arrayOfBoolean[7] = true; arrayOfBoolean[8] = true; } while (i < 10);
    arrayOfBoolean[9] = true;
  }
  protected void protectedTest()
  {
    boolean[][] tmp3_0 = $VRc; if (tmp3_0 == null) tmp3_0; boolean[] arrayOfBoolean = $VRi()[2]; this.logger.info("this is a protected method");
    arrayOfBoolean[0] = true;
  }
  private void privateTest()
  {
    boolean[][] tmp3_0 = $VRc; if (tmp3_0 == null) tmp3_0; boolean[] arrayOfBoolean = $VRi()[3]; this.logger.info("this is a private method");
    arrayOfBoolean[0] = true;
  }
  static
  {
    boolean[] arrayOfBoolean = $VRi()[4];
    arrayOfBoolean[0] = true;
  }
  private static boolean[][] $VRi()
  {
    boolean[][] tmp9_6 = (EmmaMain2.$VRc = new boolean[5]);
    tmp9_6[0] = new boolean[1];
    boolean[][] tmp15_9 = tmp9_6;
    tmp15_9[1] = new boolean[10];
    boolean[][] tmp22_15 = tmp15_9;
    tmp22_15[2] = new boolean[1];
    boolean[][] tmp28_22 = tmp22_15;
    tmp28_22[3] = new boolean[1];
    boolean[][] tmp34_28 = tmp28_22;
    tmp34_28[4] = new boolean[1];
    boolean[][] tmp40_34 = tmp34_28;
    //将类信息加载到内存中。
    RT.r(tmp40_34, "com/impulse/test/emma/EmmaMain2", -5598510326399570528L);
    return tmp40_34;
  }
}

反编译有些问题,但我们可以看出,emma在每个方法的入口和出口和转移指令之前如return、break、continue都加入了监测代码,并在最后把代码的执行情况通过RT.r()方法加载到内存的m_coverageMap中。

public static void r (final boolean [][] coverage, final String classVMName, final long stamp)
   {
       // note that we use class names, not the actual Class objects, as the keys here. This
       // is not the best possible solution because it is not capable of supporting
       // multiply (re)loaded classes within the same app, but the rest of the toolkit
       // isn't designed to support this anyway. Furthermore, this does not interfere
       // with class unloading.
       final ICoverageData cdata = getCoverageData (); // need to use accessor for JMM reasons
       // ['cdata' can be null if a previous call to dumpCoverageData() disabled data collection]
        
       if (cdata != null)
       {
           synchronized (cdata.lock ())
           {
               // TODO: could something useful be communicated back to the class
               // by returning something here [e.g., unique class ID (solves the
               // issues of class name collisions and class reloading) or RT.class
               // (to prevent RT reloading)]
                
               cdata.addClass (coverage, classVMName, stamp);
           }
       }
   }
   public void addClass (final boolean [][] coverage, final String classVMName, final long stamp)
   {
       m_coverageMap.put (classVMName, new DataHolder (coverage, stamp));
   }

所以当我们只测试publicTest()时,虽然publicTest()调用了protectedTest(),但由于我们通过条件语句的控制,使得protectedTest()永远不会被执行,因此在转移指令时加监控是必要的,我们可以在生成的报告中看出,

emma_9

emma能够检测出那些虽然调用但没有执行到的代码。

收集覆盖率信息

emma会检测jvm的运行情况,当通过命令行调用reset或者虚拟机停止(一般是测试完成时),emma会将测试的覆盖率信息通过 dumpCoverageData()方法导出成实体文件。默认为coverage-*.ec文件。

static void dumpCoverageData (final ICoverageData cdata, final boolean useSnapshot,
                                 final File outFile, final boolean merge)
   {
       try
       {
           if (cdata != null)
           {
               // use method-scoped loggers everywhere in RT:
               final Logger log = Logger.getLogger ();
               final boolean info = log.atINFO ();
                
               final long start = info ? System.currentTimeMillis () : 0;
               {
                   final ICoverageData cdataView = useSnapshot ? cdata.shallowCopy () : cdata;
                    
                   synchronized (Object.class) // fake a JVM-global critical section when multilply loaded RT's write to the same file
                   {
                       //在这里生者覆盖率信息文件,cdataView是CoverageData型,有一个重要的成员变量就是上面说的m_coverageMap
                       DataFactory.persist (cdataView, outFile, merge);
                   }
               }
               if (info)
               {
                   final long end = System.currentTimeMillis ();
                    
                   log.info ("runtime coverage data " + (merge ? "merged into" : "written to") + " [" + outFile.getAbsolutePath () + "] {in " + (end - start) + " ms}");
               }
           }
       }
       catch (Throwable t)
       {
           // log
           t.printStackTrace ();
            
           // TODO: do better chaining in JRE 1.4+
           throw new RuntimeException (IAppConstants.APP_NAME + " failed to dump coverage data: " + t.toString ());
       }
   }

DataFactory.persist (cdataView, outFile, merge); cdataView是CoverageData型,有一个重要的成员变量就是上面说的m_coverageMap,没错,就是在这里把存在内存中的测试覆盖率信息持久化到文件中。

生成测试报告

AbstractReportGenerator是个抽象工厂,根据参数不同而产生不同的 ReportGenerator。

public static IReportGenerator create (final String type)
    {
        if ((type == null) || (type.length () == 0))
            throw new IllegalArgumentException ("null/empty input: type");
         
        // TODO: proper pluggability pattern here
         
        if ("html".equals (type))
            return new com.vladium.emma.report.html.ReportGenerator ();
        else if ("txt".equals (type))
            return new com.vladium.emma.report.txt.ReportGenerator ();
        else if ("xml".equals (type))
            return new com.vladium.emma.report.xml.ReportGenerator ();
        else // TODO: error code
            throw new EMMARuntimeException ("no report generator class found for type [" + type + "]");
    }

emma_10

public
abstract class AbstractItemVisitor implements IItemVisitor
{
    // public: ................................................................
    //概要覆盖信息
    public Object visit (final AllItem item, final Object ctx)
    {
        return ctx;
    }
    //包测试覆盖信息
    public Object visit (final PackageItem item, final Object ctx)
    {
        return ctx;
    }
    //源文件测试覆盖信息
    public Object visit (final SrcFileItem item, final Object ctx)
    {
        return ctx;
    }
    //在html中没有
    public Object visit (final ClassItem item, final Object ctx)
    {
        return ctx;
    }
    //在html没有
    public Object visit (final MethodItem item, final Object ctx)
    {
        return ctx;
    }
}

三种ReportGenerator都实现了IReportGenerator接口的process方法来到处报告,而process方法又分别调用了各种重载的visit()方法。当maven生成html测试报告是,只用了生成概要覆盖信息、源文件测试覆盖信息、包测试覆盖信息的方法。

参考资料