观察使用selenium进行自动化测试的过程,咱们能够将它概述为:html
在实际的代码中,咱们所须要作的只有构建WebDriver和经过WebDriver提供的接口控制浏览器这两件事情。对于后者,既然咱们的目标是各浏览器的兼容,那么对于全部的浏览器,调用的WebDriver的接口都应该是一致的。因此这部分测试代码应该被共享,惟一的区别应该是构建WebDriver的过程。python
咱们是基于django的测试框架,django的测试框架的本质是使用python的unittest。python的unittest是基于TestCase这个类的,也就是一个类一族测试样例。上文中提到的两件实际代码中咱们作的事情就是以TestCase为单位被编写的。每当一个TestCase类被初始化时,咱们就为它构建一个WebDriver,而后在这个类中的每一个测试样例中经过WebDriver来控制浏览器。这就导出一个结论:一个类一个WebDriver、一套控制逻辑。django
注意到咱们但愿控制逻辑在各浏览器的测试中是相同的,但各浏览器使用的WebDriver是不一样的。浏览器
这就很天然地会想到类的继承,一个基类写控制逻辑,而后每一个浏览器派生这个基类,重载构建WebDriver的逻辑。但要注意到,咱们有大量的TestCase类,若是每一个TestCase都须要为它编写一族继承类的代码,并且这些代码还几乎如出一辙,仅仅只是由于代码逻辑上须要因此才写的话,那太累了。app
因此,咱们最后的需求是:有一族基类(控制逻辑),还有一族函数(构建WebDriver),若是将派生当作乘的话,咱们就是想要他两的笛卡尔积。也就是每一个基类都有一族派生类,每一个派生类对应于给基类添加那一族函数中的一个函数。框架
核心的概念是使用python类对象的思想在模块被加载时进行动态继承。这须要用到types module,python提供了该模块使得动态构建类对象变得简单。咱们只需简单地调用types.new_class函数,并在参数中指定基类就能够动态地从该基类中继承一个新类。函数
在继承获得一个新类后咱们须要为它添加它的继承因子,也就是给它添加方法。这也很是简单,在new_class的exec_body中指定就好了。新的方法会在new_class执行中被添加到被添加到新类的__dict__中,也就是命名空间中。测试
>>> import types >>> def f(self): print("F")
>>> A = types.new_class("A", exec_body=lambda ns: ns.update({"nf":f}))
>>> a = A()
>>> a.nf()
F
>>> A.__dict__
mappingproxy({'nf': <function f at 0x00000227B0AD1C80>, 'module': 'types', 'dict': <attribute 'dict' of 'A' objects>, 'weakref': <attribute 'weakref' of 'A' objects>, 'doc': None})code
对于咱们的问题,也就只须要两个循环,一个循环遍历基类,一个循环遍历继承因子,而后在循环中构建新类便可。htm
不过遍历基类自己也是一个问题。回归咱们的原来的问题,咱们的基类是分布在各文件中的大量TC(TestCase,下略),且这些TC之间自己就存在继承结构。方便起见,咱们将基类的遍历作成一个根据继承关系的递归过程。使用全部TC的共同基类FrontBasicTC做为递归的根,而后递归遍历全部子类。
这里又会涉及一个问题,上一章中提到了咱们会动态继承获得新类,这样若是递归遍历子类的话就会致使无穷递归问题了。因此咱们在动态继承时要往新类的命名空间中加上一个标记,当在递归遍历时发现这个标记就中止继续对这个类进行递归。
注意到咱们须要访问FrontBasicTC的全部子类,咱们使用的是__subclasses__方法,若一个py文件没有被执行过(也就是import过)的话,那么该方法就不会识别到该py文件中的子类。因此咱们须要在test_factory.py的一开始就将全部基类都在的目录template下的全部模块都import进来。为此咱们将template做为一个包,而后在它的__init__.py文件中添加了__all__方法,用于from template import *
。
这原本不该该是个问题,但我为了更无脑方便地开发,作了一些小trick。由于如今所使用的全部继承因子都是类方法,因此我作成了读入继承因子们所在的模块,而后遍历该模块的属性,对于为类方法的属性,将其看作继承因子进行操做。
这样的好处是不用维护有哪些继承因子的列表,简单地加上原本就要加的@classmethod装饰器就能够自动被识别。
如前文所述,咱们本质上使用的是python的unittest做为测试框架。虽然咱们可以在一个模块中动态继承新类了,但还未将这些新类添加到这个模块test_factory的命名空间中,这就致使python的UnitTestRunner识别不到这些新类。
为此咱们在test_factory中将__dir__和__getattr__重载,前者返回这个模块的属性的名字的列表,后者根据名字获取属性对象。咱们用生成的新类做为test_factory模块的属性,在__dir__和__getattr__中对应返回。
注:这里的作法可能不太好,由于是针对UnitTestRunner所作的欺骗性工做,当python的unittest发生改变时可能会失效,并且还会致使没法读取test_factory的相似__name__这些元属性。