3.2 资源选择器
面对所提供的测试环境,测试用例开发者需要通过特定的方法来选择所需测试资源。一种简单的做法是,让测试用例开发者自己遍历测试资源池,再用大量的循环语句和条件判断语句来获取所需测试资源。这是一个典型的反面教材,本节我们将会介绍资源选择器的概念,通过封装一系列的方法来方便测试用例开发者选择所需测试资源。
3.2.1 设计资源选择器的目的
通常在测试用例中,我们需要对被测对象进行一些预判断,比如被测对象是否存在,是否满足测试要求,所需要的测试设备是否足够等。然后,从测试资源池中选择合适的资源进行测试。
先来看一个混合测试设备的应用场景。在系统测试中,一个场景会有很多不同类型的测试设备。比如,某一测试平台上有很多类型的手机终端——Android的、Apple的,等等。其中有一个测试用例是针对某个特定设备进行测试的,需要Android的手机,并且其操作系统的版本要小于8.0版本,如果这个测试用例是健壮的,就需要从测试资源中过滤出满足上述条件的手机资源,实现代码如下:
再来看一个WIFI AP设备的测试场景:获取一个WIFI AP作为被测设备,以及三个STATION作为测试设备,通过管理WIFI AP上所连接的测试仪表接口和STATION上所连接的测试仪表接口来发送测试流量。
根据上面的需求,我们定义如表3-1所示的流程。
表3-1
在表3-1的流程中,只有所有条件都满足才能继续测试,否则这个测试用例就将被标为跳过。具体实现代码如下:
上述代码中有大量的if和for相互嵌套,层次非常深,难以理解和维护。我们是否有一种更好的方法,能使测试用例开发者根据条件选择所需资源呢?
接下来,我们设计一个资源选择器,给测试用例的开发者提供一个统一的接口,使用户可以通过调用资源选择器的方法来获取想要的测试资源。具体方法是,提交获取资源的请求,资源选择器处理这个请求,并返回所需的测试资源。
在提交获取资源的请求前,我们要确定三个静态条件:
• 资源类型:所请求的资源类型。
• 资源名称:所请求的资源名称。
• 请求数量:所请求的资源数量。
如果请求资源的方法为collect_device,那么我们可以将这三个静态条件作为参数,分别是resource_type(资源类型)、resource_name(资源名称)、resource_count(请求数量),通过这些参数对资源进行获取。比如调用collect_device(resource_type=”AP”)方法,就能获取所有类型为AP的资源,同样我们可以通过collect_device(resource_type=”AP”,resource_count=3)来限制数量。
下面,我们为ResourcePool类添加两个新方法,用于资源获取,代码如下:
上面讲的三个静态条件比较简单,接下来我们还要讲一些复杂的条件。比如在本节获取满足条件的手机资源的案例中,请求的资源类型是手机,限制条件是Android版本要小于8.0版本。再比如,在WIFI AP的测试案例中,除了请求特定资源,还需要根据请求的特定资源和其连接的拓扑关系,获取相应的连接拓扑对端的设备(比如A P所连接的S TA设备、A P所连接的数据流量测试端口、S TA所连接的数据流量测试端口等)。
由于这些请求条件和具体的资源属性相关,我们不能将其预定义在collec_device方法的参数中,而是要通过某种方法来封装这些资源获取的限制条件。
3.2.2 资源限制条件机制
对于限制条件,不同的测试资源会有不同的定义,所以无法用一个简单的表达式来定义,也无法用一组变量来描述,更何况不同的测试团队会根据他们自己的产品定义专属的测试资源,所以限制条件也必须被设计成可扩展的。
我们引入限制(Constraint)的概念,这个“限制”是一个名词,这意味着一个“限制”可以被描述成一个类,所有的限制条件都有一个方法——是否满足(is_meet),以此来判断某个资源是否满足此限制。所以“限制”的设计类似于一个过滤器,将符合条件的设备保留,去除不符合条件的设备,限制条件的过滤流程如图3-7所示。
图3-7 限制条件的过滤流程
3.2.2.1 限制类的设计和实现
我们设计一个“限制”的基类,包含一些抽象的方法,提供给测试用例设计者来扩展自己的资源限制条件,具体实现代码如下:
如上述代码所示,这个“限制”基类是所有Constraint的基类,description用来存放对该限制的描述信息。抽象方法is_meet用来判断传入的资源对象是否满足条件,所以子类通过该方法对设备进行判断,以确定是否满足条件。
我们来设计几个简单的限制条件,以3.2.1节中手机选择的两个限制条件(手机操作系统类型,手机操作系统版本)为例,代码如下:
从类的命名上看,PhoneMustBeAndroidConstraint类很好理解,被测手机必须是Android手机,用户将必要的参数作为资源限制子类的属性,在实例化的过程中,将这些属性赋值,在is_meet方法中进行具体的判断,最后返回True或False,表示资源是否满足该限制条件。在该类中,version和version_op作为可选的属性,除了判断资源类型是否为Android,还可以对Android的版本进行判断。
在处理多条件判断的时候,我们甚至可以将Constraint嵌套使用,比如3.2.1节中第二个无线AP测试的例子,我们首先用3.1.2节中所设计的资源描述方法建立一个测试拓扑,代码如下:
上述代码描述了如图3-8所示的拓扑结构。
图3-8 一个AP测试的拓扑结构
接着,我们根据案例中所需要的判断,定义限制条件:
• AP必须有STA连接,并且规定连接的数量的大小。
• AP必须有流量测试仪表的连接,并且规定流量仪表端口速率的大小。
• S TA必须有流量测试仪表的连接,并且规定流量测试仪表端口速率的大小。
总结一下,我们可以定义如下类来描述这些限制条件:
• ApMustHaveStaConnected.
• DeviceMustHaveTrafficGeneratorConnected.
• TrafficGeneratorSpeedMustGraterThan.
我们从最简单的TrafficGeneratorSpeedMustGraterThan类来实现,代码如下:
从上述代码中可以看到,在这个限制条件方法中传入的资源类型必须是端口,通过端口判断其所处的设备是否为流量测试仪器,然后判断其速率是否大于等于限制条件所规定的速率。
接下来,我们来实现DeviceMustHaveTrafficGeneratorConnected类,代码如下:
从上述代码中可以看到,该方法的speed属性是之前定义的TrafficGeneratorSpeedMustGraterThan类的实例(当然也可以是其他限制类型)。port_count是与测试仪表相连接的端口数。在is_meet方法的具体实现中遍历所有ETH类型的端口,判断每一个远端端口是否满足测试仪表端口类型的条件,如果speed属性被设置了期望的限制条件,则使用speed属性中的资源限制类实例的is_meet方法,来判断该端口的速率是否满足条件。
最后,我们实现ApMustHaveStaConnected类,代码如下:
如上代码所示,sta_constraints表示STA设备必须满足的限制条件,sta_count表示至少需要多少STA连接在AP上。在is_meet方法的实现中,对AP的WIFI端口上的远端端口所处设备类型进行判断,如果是STA类型,则通过sta_constraints中提供的资源限制类实例,逐一判断该设备是否符合所有的限制条件。
下面我们对上述资源限制条件类做一下测试,代码如下:
我们定义了6个限制条件类的实例,代码注释中已经说明了这些实例的作用,代码的运行结果为True、True、False、True、False、True。所以我们再次回顾3.2.1节AP测试环境中资源获取的判断过程,在实现了constraint机制之后,我们就可以使用这段测试代码中的constraint4和constraint6实例来判断测试资源对象,从而实现获取满足条件的AP功能。
3.2.2.2 资源获取方法的改造
在设计了限制条件类之后,我们就可以对ResourcePool类中的collect_device方法进行改造,将限制条件类作为参数传入该方法,具体代码如下:
当我们获取相应类型的资源设备之后,将该资源和传入的限制条件进行逐一判断,只有满足所有条件才作为符合条件的资源返回。所以我们可以继续扩展3.2.2.1节中的测试代码,将获取3.2.1节的AP环境的实现代码修改如下:
如上代码所示,通过这种将条件封装成类的方法,极强地增加了代码的可读性,我们在设计Constraint类的时候,尽可能地使用自然语言来描述,这样在测试用例中挑选资源的代码的可读性将会非常强。而这些条件类的封装又极强地增加了代码的可复用性,一些公共的限制条件可以下沉到业务代码的底层,提供给不同的开发者甚至不同团队来使用和维护。
3.2.3 资源获取路由
上节中,我们通过Constraint机制获取了符合条件的设备,并通过AP测试环境获取了资源。但是除了AP,我们还需要和AP连接的测试仪表端口、与AP相连的STA设备及其所连接的端口。换句话说,如果我们想获取相关资源还要写新的代码,即便这个资源已经满足了所有的条件——这显然又增加了工作量。
继续来看图3-8,假设我们找到了符合条件的AP1,事实上符合条件的STA设备及流量测试仪的端口,在资源限制条件类的is_meet方法的执行过程中也被相应地确定了。
下面我们来讲解资源查找路由的拓扑,如图3-9所示。
图3-9 资源查找路由的拓扑
在图3-9中,为了简化操作,我们针对一个资源来获取其所符合条件的连接拓扑。在这个连接拓扑中,假设我们获取了设备1,限制条件是“设备1必须与设备5相连”,并且在测试过程中我们同时需要用到设备1和设备5,这种情况是设备到设备的直接连接。
下面我们考虑两种情况:
第一,假设获取设备1的条件是“设备1与设备7相连”,暂不考虑中间隔了多少其他设备。并且,测试中我们需要用到设备1和设备7,以及所有中间经过的设备。
第二,设备1必须与设备X连接,设备X必须与设备7连接。
这两种情况都需要返回设备1和设备7之间的所有设备或端口。
笔者把这种情况称为资源获取路由。既然我们在做资源查询条件判断的时候,已经确定了符合条件的资源,那么我们是不是可以在确定资源限制的过程中,就将这些符合条件的设备通过某种方法返回给测试用例开发者呢?
3.2.3.1 Constraint类的扩展
Constraint类的is_meet方法用于判断当前资源是否符合其限制条件,如果is_meet方法是对对端的设备进行判断的,那么在判断的过程中我们可以得到满足相应条件的资源。所以我们可以将Constraint类进行扩展,以获取满足条件的连接,具体实现代码如下:
ConnectionConstraint类继承自Constraint类,并且添加了新的方法get_connection,用于返回满足条件的对端端口。
我们以3.2.2.1节中的DeviceMustHaveTrafficGeneratorConnected类为例,修改代码如下:
我们把原来的判断逻辑从is_meet方法中移至get_connection方法中,然后将符合条件的远端端口返回。而is_meet方法巧妙地使用get_connection的返回来判断是否符合条件。这是一种简单的情况,通过Constraint获取被请求设备的直接连接。
我们以同样的方法来改造ApMustHaveStaConnected类,但这个类相对复杂些,因为我们可能给所连接的STA设备设置connection限制条件,比如针对3.2.1节的WIFI AP测试案例,我们使用了DeviceMustHaveTrafficGeneratorConnected这个connection限制赋值给sta_constraint属性。这样,如果给ApMustHaveStaConnected添加了get_connection方法,那么返回的connection中,就必须添加DeviceMustHaveTrafficGeneratorConnected的get_connection方法返回的结果,具体实现代码如下:
在构造方法__init__中,我们将传入的sta_constraints进行分类,分成普通的限制(Constraint)和连接限制(ConnectionConstraint),然后在get_connection操作中对普通的限制进行判断,判断对端设备是否满足一般限制条件,当所有的一般限制条件满足后,再判断连接限制,并返回所有连接限制get_connection所返回的连接,最后将这些返回的连接一起返回。
这种复杂的获取路由的方法并没有绝对的connection返回的格式,比如这个类中我们定义返回的是一个元组类型的列表,每个元组的第一个元素表示找到的S TA的端口,第二个元素表示该S TA满足条件的所有连接的对端端口。
3.2.3.2 资源获取路由的方法实现
我们在3.2.2.2节中实现的collect_device方法可以获取单个资源,接下来我们设计另一个获取资源的方法——collect_connection_route方法,代码如下:
该方法由于用来获取连接,所以constraints参数必须全部为连接限制。遍历所有的连接限制,判断每个连接限制是否返回了所需要的连接端口,然后将所有的端口返回。
我们继续以3.2.2节中测试资源限制类的代码来说明这个方法的使用:
输出结果如下:
虽然我们获取了所有的connection信息,但是由于列表中每个条目的类型都不一致,所以处理起来会很麻烦。
笔者建议,在这种情况下,不要使用过长的路由来设计限制条件,或者每次只使用一种限制条件,然后进行多次判断,相关代码如下:
输出结果如下: