TestNG源代码分析 --- 依赖管理的实现(二)

来源:互联网 发布:淘宝类目 编辑:程序博客网 时间:2024/06/05 04:02

转自: http://blog.csdn.net/dm_vincent/article/details/7641570

在上一篇文章中,留下了一些的问题:

  • Graph对象中的一些字段是怎么被初始化的?在使用Graph对象的topologicalSort方法的时候,需要用到这些字段,比如m_nodes以及m_independentNodes这两个集合,它们分别存放的是所有的节点的引用以及所有独立节点的引用。
  • Graph对象是如何使用的,即方法调用栈的上层是如何调用Graph中的topologicalSort方法的。
  • 关于环路检测算法的实现,用于在发现循环依赖的时候,检测出具体的循环依赖路径。

 

本文就对上述的几个问题作出解释:

 

环路检测算法

先来看看算法的实现:

[java] view plain copy
 print?
  1. public class Tarjan<T> {  
  2.   int m_index = 0;  
  3.   private Stack<T> m_s;  
  4.   Map<T, Integer> m_indices = Maps.newHashMap();  
  5.   Map<T, Integer> m_lowlinks = Maps.newHashMap(); //  
  6.   private List<T> m_cycle;    
  7.   public Tarjan(Graph<T> graph, T start){  
  8.     m_s = new Stack<T>();  
  9.     run(graph, start);  
  10.   }  
  11.   private void run(Graph<T> graph, T v) {  
  12.     m_indices.put(v, m_index);  
  13.     m_lowlinks.put(v, m_index);  
  14.     m_index++;  
  15.     m_s.push(v);  
  16.     for (T vprime : graph.getPredecessors(v)){  
  17.       if (! m_indices.containsKey(v prime)) {  
  18.         run(graph, vprime);  
  19.         int min = Math.min(m_lowlinks.get(v),m_lowlinks.get(vprime));  
  20.         m_lowlinks.put(v, min);  
  21.       }  
  22.       else if (m_s.contains(v prime)) {  
  23.         m_lowlinks.put(v,Math.min(m_lowlinks.get(v), m_indices.get(vprime)));  
  24.       }  
  25.     }  
  26.     if (m_lowlinks.get(v) == m_indices.get(v)){  
  27.       m_cycle = Lists.newArrayList();  
  28.       T n;  
  29.       do {  
  30.         n = m_s.pop();  
  31.         m_cycle.add(n);  
  32.       } while (! n.equals(v));  
  33.     }  
  34.   }  
  35.   public List<T> getCycle() {  
  36.     return m_cycle;  
  37.   }  
  38. }  

这里实现的实际上是Tarjan判断强连通子图的算法,因为对于有向cycle,它一定是强连通的,所以在我们的场景中使用这个算法是没问题的,但是在细节上,上面的实现存在一点小瑕疵,即最后只能maintain一个cycle,如果在依赖关系中存在多个cycle的话,是无法将它们全部记录下来的。当然,这个算法的实现是为了提示用户存在循环依赖,而不是为了输出所有的循环依赖。有兴趣的可以查看维基百科中对于Tarjan SCC算法的描述。

http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm

 

另外,在上面的实现中,和Tarjan SCC算法的实现也不是完全一致的,比如,是对当前node的所有前驱结点进行检查,而不是像Tarjan SCC中,是对所有的可达节点进行检查。这样做也是为了加快算法的执行速度,即在存在循环依赖的情况下,尽量减少循环的次数,只需要保证至少能够检测到一个环即可。

 

Graph对象的初始化以及使用

我们再来回顾一下那个stacktrace


可以发现,调用Graph中的拓扑排序方法的是MethodHelper类中的静态方法topologicalSort,而后者又被该类中的几个静态方法调用,所以,为了弄清楚Graph中的数据是如何准备的,我们需要对这个类进行探究:

MethodHelper.topologicalSort方法

[java] view plain copy
 print?
  1.  private static Graph<ITestNGMethod> topologicalSort(ITestNGMethod[] methods,  
  2.       List<ITestNGMethod> sequentialList, List<ITestNGMethod> parallelList) {  
  3.     Graph<ITestNGMethod> result = new Graph<ITestNGMethod>();  // 首先创建一个Graph类,Graph类中只有一个默认的constructor  
  4.     if (methods.length == 0) {    
  5.       return result;   //如果传入的methods数组长度为0,直接返回了空的graph  
  6.     }  
  7.     // Create the graph  
  8.     for (ITestNGMethod m : methods) {  
  9.       result.addNode(m);    //对于每个方法instance,添加到graph中  
  10.       List<ITestNGMethod> predecessors = Lists.newArrayList();  //获得该方法依赖的方法,获得该方法依赖的group名字,返回的都是string数组  
  11.       String[] methodsDependedUpon = m.getMethodsDependedUpon();  
  12.       String[] groupsDependedUpon = m.getGroupsDependedUpon();  
  13.       if (methodsDependedUpon.length > 0) {   //如果存在依赖的方法  
  14.         ITestNGMethod[] methodsNamed =  
  15.           MethodHelper.findDependedUponMethods(m,methods);  // 通过该静态方法找到相应的method instances  
  16.         for (ITestNGMethod pred : methodsNamed){  
  17.          predecessors.add(pred);   //将找到的依赖方法添加到pred list中  
  18.         }  
  19.       }  
  20.       if (groupsDependedUpon.length > 0) {  
  21.         for (String group : groupsDependedUpon){ // 对于每个组,找到组中的方法  
  22.           ITestNGMethod[] methodsThatBelongToGroup =  
  23.             MethodGroupsHelper.findMethodsThatBelongToGroup(m, methods, group);  
  24.           for (ITestNGMethod pred : methodsThatBelongToGroup) {  
  25.             predecessors.add(pred); // 将找到的依赖方法添加到pred list中  
  26.           }  
  27.         }  
  28.       }  
  29.       for (ITestNGMethod predecessor : predecessors) {  
  30.         result.addPredecessor(m, predecessor);  // 将pred list中的方法instance添加到graph中  
  31.       }  
  32.     }  
  33.     result.topologicalSort();    //调用Graph的TS方法  
  34.     sequentialList.addAll(result.getStrictlySortedNodes());    //对于存在依赖关系的方法需要顺序运行  
  35.     parallelList.addAll(result.getIndependentNodes());    //对于不存在任何依赖的方法可以并发运行  
  36.     return result;  
  37. }  

以上代码的几个关键步骤:

  • 对于每个ITestNGMethod对象的操作:
    • 添加到Graph中,通过addNode方法
    • 创建一个list,用来维护该方法依赖的方法
    • 获得该方法依赖的所有方法,添加到上一步创建的list
      • 根据dependsOnMethod找到依赖方法
      • 根据dependsOnGroup找到依赖方法
    • 将上一步修改后的list添加到graph中,通过addPredecessor方法
  • 待所有的ITestNGMethod对象都被处理完毕后,调用Graph对象的拓扑排序方法
  • 如果拓扑排序没有出现错误,获取结果,分别添加到SequentialParallel list

 

在上面的分析中,出现了SequentialList以及Parallel List这两个集合,它们分别用于顺序执行和并发执行。并发执行是TestNG中一个很重要,同时也十分新颖的功能。我们总是希望最大限度的提高程序的并发度,对于测试用例的运行,也不例外。由于硬件的发展,并发/并行计算是未来的趋势之一。TestNG中对于并发运行功能的实现,以后会有介绍。

 

在上一篇文章中,介绍了Graph类的工作原理,但是对于其中数据的来源和准备,当时我们暂时忽略了,那么现在我们可以详细探究一下,Graph中的数据是如何准备的:

主要通过两个方法:

addNode以及addPredecessor

由于Graph是一个泛型类,这里的参数都用T来表示类型,为了方便理解,不妨把这个T就当成TestNG中的用来表示方法的ITestNGMethod接口类型。

[java] view plain copy
 print?
  1. public void addNode(T tm) {  
  2.   ppp("ADDING NODE " + tm + "" + tm.hashCode());  
  3.   m_nodes.put(tm, new Node<T>(tm)); // Initially, all the nodes are put in theindependent list as well  
  4. }  

该方法的实现十分简单,就是向m_nodes集合中添加一个entry,注意这个entry的类型是<MethodNode<Method>>

[java] view plain copy
 print?
  1. public void addPredecessor(T tm, T predecessor) {  
  2.   Node<T> node = findNode(tm);  // 首先看看是否能够得到tm对象对应的Node  
  3.   if (null == node) {  
  4.     throw new TestNGException("Non-existing node: " + tm); //如果没有找到,明显是发生了错误,代表这个tm在m_nodes中根本就不存在  
  5.   }  
  6.   else {  
  7.     node.addPredecessor(predecessor);  // 这里在node上调用了addPredecessor方法  
  8.     addNeighbor(tm, predecessor);   //这个方法的作用暂时还没有看到,搜索了整个workspace都没有找到相应的用途,估计是作者为了扩展什么功能而预留的  
  9.     // Remove these two nodes from theindependent list  
  10.     if (null == m_independentNodes) {  // 如果独立节点集合还没有初始化  
  11.       m_independentNodes = Maps.newHashMap();  
  12.       for (T k : m_nodes.keySet()) { // 将现有的所有方法instance以及对应的Node对象添加到独立map  
  13.         m_independentNodes.put(k,m_nodes.get(k));    
  14.       }  
  15.     }  // 移除非独立的节点,包括tm对象本身以及它依赖的节点,因此该集合中最后剩下的就是完全独立的节点了  
  16.     m_independentNodes.remove(predecessor);             
  17.     m_independentNodes.remove(tm);     
  18.     ppp("  REMOVED " + predecessor + " FROMINDEPENDENT OBJECTS");  
  19.   }  
  20. }  

然后我们再看看上面方法的调用者

MethodHelper.sortMethods方法

[java] view plain copy
 print?
  1. private static List<ITestNGMethod> sortMethods(boolean forTests,  
  2.     List<ITestNGMethod>allMethods, IAnnotationFinder finder) {  
  3.   List<ITestNGMethod>sl = Lists.newArrayList();  // 用来保存只能顺序执行的methods  
  4.   List<ITestNGMethod>pl = Lists.newArrayList(); // 用来保存可以并行执行的methods  
  5.   ITestNGMethod[] allMethodsArray =allMethods.toArray(new ITestNGMethod[allMethods.size()]);  
  6.   
  7.    // 下面这个if块的功能在于:对属于beforeXXX系列的configuration方法,在调用顺序上进行修正  
  8.   if (!forTests && allMethodsArray.length > 0) {   
  9.     ITestNGMethod m = allMethodsArray[0];  
  10.     boolean before = m.isBeforeClassConfiguration()  
  11.         || m.isBeforeMethodConfiguration() || m.isBeforeSuiteConfiguration()  
  12.         || m.isBeforeTestConfiguration();    //查看该config方法是不是beforeXXX方法  
  13.     MethodInheritance.fixMethodInheritance(allMethodsArray, before);  
  14.   }  // 调用之前的topologicalSort方法进行拓扑排序,将sequential list和parallel list构造好  
  15.   topologicalSort(allMethodsArray, sl, pl);   
  16.   List<ITestNGMethod> result = Lists.newArrayList();  
  17.   result.addAll(sl);  
  18.   result.addAll(pl);  // 将sl和pl中的ref全部添加到result中,该result集合也就表示了所有需要执行的方法  
  19.   return result;  
  20. }  

因此,在TestNG对于依赖关系检测的拓扑排序中,主要有两个功能:

  • 检测依赖关系的正确性,即不存在任何形式的循环依赖
  • 在保证正确性的前提下,将方法分类,分成只能顺序运行的方法以及可以并发运行的方法

 

以上,就是对TestNG中依赖关系相关核心代码的分析。其核心思想还是使用拓扑排序来建立依赖关系。在以后的系列文章中,还会介绍TestNG是如何实现并发运行测试方法,以及一些其他内容,比如,TestNG的几个常用的扩展点,Method Selector机制,各种Listener等等。


0 0
原创粉丝点击