zoukankan      html  css  js  c++  java
  • unittest suite集合实现原理

    Testsuite继承BaseTestSuite其实内部的东西不是太多--生成suite集合的逻辑主要如下-我这里没有扒源码-因为他最终生成的TestsSuite关联的模块比较多--如果贴源码出来---我能写出来--但是没法看。。所以就模拟了一下场景--写的不是太好--不过主要内容就这么点东西
    <case.testss.A tests=[<testss.test_C testMethod=test_1>, <testss.test_C testMethod=test_2>, <testss.test_C testMethod=test_3>]>
    当初我看到这种集合的时候,只知道他是一个列表-但是整个人都是懵逼的---特别是以下一句 位于BaseTestSuite下面的run方法
    1 def run(self, result):
    2     for index, test in enumerate(self):
    3         if result.shouldStop:
    4             break
    5         test(result)   #就这一句----test为什么还可以传值,那时候也很少看源码只知道用。。。最近在写平台--感觉不了解内部原理-有点僵硬-所以决定干unittest源码
    6         if self._cleanup:
    7             self._removeTestAtIndex(index)
    8     return result
    1 def run(self, result):
    2     for index, test in enumerate(self):
    3         if result.shouldStop:
    4             break
    5         test(result)   #就这一句----test为什么还可以传值,那时候也很少看源码只知道用。。。最近在写平台--感觉不了解内部原理-有点僵硬-所以决定干unittest源码
    6         if self._cleanup:
    7             self._removeTestAtIndex(index)
    8     return re在撸它之前得先了解一个内置函数__repr__
    首先我们都知道object是一个对象---先明确这点
    我的理解它的作用是几乎自定义对象显示名称--具体的解释-百度--一大堆
    class C(object):
        def test_1(self):
            pass
        def test_2(self):
            pass
        def test_3(self):
            pass
    if __name__ == "__main__":
        print(test_C)    #<class '__main__.test_C'>   class是一个类--以及所在模块和类名
        print(test_C())  #<__main__.test_C object at 0x000000000213C7B8>  所在模块.类名 对象 内存地址
        
    
    class C(object):
        def __repr__(self):  #加上这么一句
            return "<%s at '假装是一个内存地址'>" %("%s.%s"%(self.__class__.__module__,self.__class__.__qualname__))
        def test_1(self):
            pass
        def test_2(self):
            pass
        def test_3(self):
            pass
    if __name__ == "__main__":
        print(test_C)    #<class '__main__.test_C'>   class是一个类--以及所在模块和类名
        print(test_C())  #<__main__.test_C at '假装是一个内存地址'>  这个对象名称就编程我们自定义的了    
        
    #然后看了这个例子我们明白了---__repr__这个方法这里就是修改对象的展示格式的。。。。

    然后在看这个

    unitest它是怎么找到用例的--然后生成一个TestSuite集合,现在大部分人都是用discover--尽管不是discover几乎也是这条线路

    找用例全流程discover会把【目录】放到环境变量-方便后面_get_modul_from_namet通过文件名称找模块--实际上就是之前把目录加入到环境变量了,然后传文件的全路径走_get_name_from_path-执行一个os.path.relpath方法从之前的目录开始找本次传的文件的相对路径 也就是返回一个文件名--然后_get_modul_from_namet __import__(文件名)动态导入之后-然后返回一个 sys.moduls[”文件名“] 一个【文件对象】

    然后这里又调用loadTestsfromModul方法--它执行dir(modul) 找到下面所有的属性 -然后判断他是不是一个类并且属于case.TestCase的子类(就是有没有继承unittest.TestCase)-然后遍历通过getattr(modul,className) 返回一个【类对象】---然后继续又找loadTestsFromTestCase--找方法对象--当初我就被这个方法里面的 map--这个map很关键--在这里用 --建议看下源码--返回然后一个suite集合

    【目录】找到【文件对象moudel】找到 【类对象】找到【方法对象】 就这么一个玩意
     新建py文件 嗯 --叫什么名字呢。。。。。。就叫unitba.py
    class TestCase():  
        def __init__(self,name="test"):
            self.name=name
        def __repr__(self):
            return "<%s testMethod=%s>" % ("%s,%s"%(self.__class__.__module__, self.__class__.__qualname__), self.name)
        def __str__(self):  
        return "%s (%s)" % (self.name, ("%s.%s"%(self.__class__.__module__, self.__class__.__qualname__)))
    class test_C(TestCase):
        def test_1(self):
            pass
        def test_2(self):
            pass
        def test_3(self):
            pass
    class BaseTestSuite(object):  #假装这是unittest的BaseTestSuite
        def __init__(self,tests=()):
            # self.b=1
            self.tests=[]
            self.add(tests)
        def __repr__(self):
            return "<%s tests=%s>"%(self.__class__,self.tests)
        def  add(self,tests):
            for a  in tests:
                self.tests.append(a)
    class TestSuite(BaseTestSuite):  #那么这个就是假装TestSuite
        pass
    #这个场景贼真实吧-。-
     
    建一个文件tests.py--这个就是TestLoader
    #第一步模拟场景  加环境变量
    import os,sys,django
    
    os.environ.setdefault("DJANGO_SETTINGS_MODULE","besettest.settings")
    django.setup()#如果不是django就不用管这两句
    
    
    path=r"E:PyFilesBesettestesettestappscase"   #unitba.py的目录  我们传的目录
    sys.path.insert(0,os.path.abspath(r"E:PyFilesDjangoBesettestappscase"))  #加入变量--这一步其实是在discover中完成的
    __import__('unitba')
    modul=sys.modules[unitba]   #这一步是dicvoer调用——>_find_tests——>_find_tests_path——>_get_modul_from_name  
    suite=unitba.TestSuite  #上面已经动态导入了直接用就好
    print(modul)  #<module 'unitba' from 'E:\PyFiles\Besettest\besettest\apps\case\unitba.py'> 
    #那这里已经返回了文件对象
    for  testClassName in dir(modul):   #那遍历出来的就是文件对象下面的 属性了
        obj=getattr(modul,testClassName)    #从modul中取类对象-
        if isinstance(obj, type)  :  #判断他是否是一个类
            def isTestMethod(attrname, testCaseClass=obj,p=testMethodPrefix):  
                return attrname.startswith(p) and callable(getattr(testCaseClass, attrname))  #callable检查是否可调用   getattr(testCaseClass, attrname)
            testClassObjs=list(filter(isTestMethod, dir(obj)))  #这个filter应该会用吧。。。
            MethodObj= list(map(obj, testCase) )  #将方法名传入类对象---这里下面解释为什么可以这样用---只需要记住-这里的testCase是getattr返回的属性值就是个类名 类名(参数)  就实例化了
            print(MethodObj)  #[<unitba.test_C at '假装是一个内存地址'>, <unitba.test_C at '假装是一个内存地址'>, <unitba.test_C at '假装是一个内存地址'>]
            
            #一直到这里--都只是在单纯的找用例对象-那现在改一下用例类-按照unittest的suite格式改 模块名.类名  testMethod=方法名
    # unittest格式<case.testss.A tests=[<testss.test_C testMethod=test_1>, <testss.test_C testMethod=test_2>, <testss.test_C testMethod=test_3>]>
            
    class test_C(object):
        def __init__(self,name="test"):  #手动传一个方法名-这里稍微有点绕--没基础的可能有点难理解-我尽量解释。。。。展示先写死
            self.name=name
        def __repr__(self):
            return "<%s.%s testMethod=%s>" % (self.__class__.__module__, self.__class__.__qualname__, self.name)
        def test_1(self):
            pass
        def test_2(self):
            pass
        def test_3(self):
            pass
    ###然后在执行tests.py  
    print(MethodObj)  #[<unitba,test_C testMethod=test_1>, <unitba,test_C testMethod=test_2>, <unitba,test_C testMethod=test_3>]是不是和里面的数据一样了
    
    
    ###然后继续执行---
    ###suite=unitba.TestSuite  上面有定义一个这个东西   suite()就是一个实例化对象了这个能理解吧
    ###TestSuite继承BaseTestSuite --所以是可以传值的
    ###name
    
    suite(MethodObj)    
    #suite name接收的值-就是一个这么东西----实际上unittes不是向我这样处理的--但是原理是一样--他传的是一个map对象--我是在前面一步把map对象转换成列表了--而他是通过list(name)转换的
    #注意一点----MethodObj里面的对象都是实例对象-实例化的时候传的参数是下面每个方法的名称--然后一个TestsCase类实例化了很个实例对象  他就是这么一个东西
    #得到<<class 'case.unitba.TestSuite'> tests=[<unitba,test_C testMethod=test_1>, <unitba,test_C testMethod=test_2>, <unitba,test_C testMethod=test_3>]>
    #对比<case.testss.A tests=[<testss.test_C testMethod=test_1>, <testss.test_C testMethod=test_2>, <testss.test_C testMethod=test_3>]>
    是不是一样--你可以继续加东西--我这里不完全一样是因为我没有把参数加到和unittetst加到一样
     MethodObj= list(map(obj, testCase) )  
     #这里为什么可以这样用,看下面这个方法就可以理解了---因为obj之前是用一个getattr取出来的值obj=getattr(modul,testClassName) 所以他是可以接受参数的-可以当做一个function用-
     
    class   test1():
        def add(self,a,b):
            return a+b
    a=test1()
    b=getattr(a,"add")  #这个是返回了add的内存地址  加上括号和里面的参数就直接执行了。。
    print(b(1,2))
    
    
    
    
    
    class   test2():  #这个也是suite集合生成用的东西
        def __call__(self, *args, **kwargs):
            return self.add(*args, **kwargs)
        def add(self,a,b):
            return a+b
    a=test2()
    print(a(1,2))
     
    为什么用iter()以及怎么执行testCase原理
     1 class  test2():
     2     def __init__(self,tests=None):
     3         self._tests=[]
     4         self.run(tests)
     5     def __repr__(self):
     6         return "<%s tests=%s>" % ("%s.%s"%(self.__class__.__module__,self.__class__.__class__.__qualname__), list(self))  #这里用list(self)
     7     def __iter__(self):  #让这个类变成一个可迭代的对象,并且让self._tests变成一个迭代器----那他有什么作用---就是这个对象只能用一次如下
     8         return iter(self._tests)    #返回该可迭代对象的的实例--放在suite集合的意思就是当我们直接打印这个实例的时候返回的是一个self._tests--
     9 
    10     def run(self,tests):
    11         for a in tests:
    12             self._tests.append(a)
    13 
    14 class   test1():
    15     def  __init__(self,methodName):  #实际上这个methodName是有我们写的TestClass继承TestCase得到的
    16         self._testMethodName=methodName
    17     def __repr__(self):
    18         return "<%s testMethod=%s>" %("%s.%s"%(self.__class__.__module__,self.__class__.__class__.__qualname__), self.methodName)
    19         
    20     # def __str__(self):
    21     #     return self.resul
    22     def test_1(self):
    23         return self._testMethodName,1
    24     def test_2(self):
    25         return self._testMethodName
    26 test2=test2
    27 name=["test1","test2","test3"]
    28 MethodsName=list(filter(lambda x:x.startswith("test"),dir(test1)))
    29 print(MethodsName)#这里打印出来是方法的名字
    30 print(test2(map(test1,MethodsName)))   #map(test1,MethodsName))--test("参数")   这个得到结果就是相当于传了不同的方法名--定义了多个实例对象-然后传给test2--得到一个test2的实例对象
    31 
    32 #['test_1', 'test_2']
    33 #<__main__.type tests=[<__main__.type testMethod=test_1>, <__main__.type testMethod=test_2>]>
    34 
    35 
    36 #比如:
    37 mapG=map(test1,MethodsName)
    38 a=test2(mapG)  #<__main__.type tests=[<__main__.type testMethod=test_1>, <__main__.type testMethod=test_2>]>
    39 b=test2(mapG)  #<__main__.type tests=[]>
    40 
    41 补充:那么怎么执行test_1 和 test_2这两个方法呢
    42 for item  in a:
    43     print(item)  #这个得到的打印出来的是以方法名为参数的实例对象对吧--实例对象取属性---取到methodName=item.methodName  这个也没毛病对吧
    44     Method=getattr(item,item._testMethodName)    #第一次item是test_1的实例对象-取到的methodName是test_1--第二次是test_2的实例对象,取到的是test_2
    45     #之前有说过getattr是什么?
    46     print(Method())
    47 
    48 >>><__main__.type testMethod=test_1>
    49 >>>('test_1', 1)
    50 >>><__main__.type testMethod=test_2>
    51 >>>test_2

    所suite集合 就是在TestLoader找测试用例的时候--通过_find_tests这个方法从目录开始找文件(子目录)-模块-类-方法名

    然后将某个模块下的类通过他下面的方法map返回多个对象,也就是说一个testClass下面存在五个test_method,他就会返回五个实例对象-并生成一个suite集合--然后加入到一个列表
    如果一个模块下有多个testClasee 同样-实际上是一样的--实际上他是先通过loadTestsFromModule这个方法找到所有的类对象之后在遍历--然后才走上面那一步的,,,多个testClasss就存在多个suite集合--
    也就是说 一个modul下面的 suite集合会添加到一个列表--[suite=[A-TestCase实例化对象1,A-TestCase实例化对象2],suite=[B-TestCase实例化对象1,B-TestCase实例化对象2]]---然后在将这个列表当做参数传入TestSuite实例化一个新对象[suite=-[suite=[A-TestCase实例化对象1,A-TestCase实例化对象2],suite=[B-TestCase实例化对象1,B-TestCase实例化对象2]]]----这样就是一个模块下用的结构
    但是还没有完--这里只是一个modul下的---还有多个modul--到了大家估计也知道剩下的会干什么了---
    没错--当我得到modul的全部suite集合之后---这个集合最终会返回给_find_tests方法--通过生成器返回给discover--也就是将这个suite集合又加入到了一个新的列表--然后discover又将这个list
    带入形成了一个-----最终的实例对象,最终返回的格式如下-------
    [suite=
    [suite1=-[suite=[A-TestCase实例化对象1,A-TestCase实例化对象2],suite=[B-TestCase实例化对象1,B-TestCase实例化对象2]]],
    [suite2=-[suite=[A-TestCase实例化对象1,A-TestCase实例化对象2],suite=[B-TestCase实例化对象1,B-TestCase实例化对象2]]]
    ]
     
    --看过源码的都知道---我们run的时候---就这个实例对象是可以接受参数的--而这个参数就是result---因为TestSuite继承的BaseTestsSuite 有一个__call__这个魔术方法如果在类中实现了 __call__ 方法,那么实例对象也将成为一个可调用对象,具体百度。这里不做过多解释-----所以最终的suite是可以接受参数的test(result)--接受参数之后直接走——call下面的逻辑了

    class test2(): def __init__(self,tests=None): self._tests=[] self.run(tests) def __repr__(self): return "<%s tests=%s>" % ("%s.%s"%(self.__class__.__module__,self.__class__.__class__.__qualname__), list(self)) #这里用list(self) def __iter__(self): #让这个类变成一个可迭代的对象,并且让self._tests变成一个迭代器----那他有什么作用---就是这个对象只能用一次如下 return iter(self._tests) #返回该可迭代对象的的实例 def run(self,tests): for a in tests: self._tests.append(a) class test1(): def __init__(self,resul): self.resul=resul def __repr__(self): return "<%s testMethod=%s>" %("%s.%s"%(self.__class__.__module__,self.__class__.__class__.__qualname__), self.resul) # def __str__(self): # return self.resul def test_1(self): return self.resul,1 def test_2(self): return self.resul test2=test2 name=["test1","test2","test3"] MethodsName=list(filter(lambda x:x.startswith("test"),dir(test1))) print(MethodsName)#这里打印出来是方法的名字 print(test2(map(test1,MethodsName))) #map(test1,MethodsName))--test("参数") 这个得到结果就是相当于传了不同的方法名--定义了多个实例对象-然后传给test2--得到一个test2的实例对象 #['test_1', 'test_2'] #<__main__.type tests=[<__main__.type testMethod=test_1>, <__main__.type testMethod=test_2>]> #比如: mapG=map(test1,MethodsName) a=test2(mapG) #<__main__.type tests=[<__main__.type testMethod=test_1>, <__main__.type testMethod=test_2>]> b=test2(mapG) #<__main__.type tests=[]>

    class test2(): def __init__(self,tests=None): self._tests=[] self.run(tests) def __repr__(self): return "<%s tests=%s>" % ("%s.%s"%(self.__class__.__module__,self.__class__.__class__.__qualname__), list(self)) #这里用list(self) def __iter__(self): #让这个类变成一个可迭代的对象,并且让self._tests变成一个迭代器----那他有什么作用---就是这个对象只能用一次如下 return iter(self._tests) #返回该可迭代对象的的实例--放在suite集合的意思就是当我们直接打印这个实例的时候返回的是一个self._tests-- def run(self,tests): for a in tests: self._tests.append(a) class test1(): def __init__(self,methodName): #实际上这个methodName是有我们写的TestClass继承TestCase得到的 self._testMethodName=methodName def __repr__(self): return "<%s testMethod=%s>" %("%s.%s"%(self.__class__.__module__,self.__class__.__class__.__qualname__), self.methodName) # def __str__(self): # return self.resul def test_1(self): return self._testMethodName,1 def test_2(self): return self._testMethodName test2=test2 name=["test1","test2","test3"] MethodsName=list(filter(lambda x:x.startswith("test"),dir(test1))) print(MethodsName)#这里打印出来是方法的名字 print(test2(map(test1,MethodsName))) #map(test1,MethodsName))--test("参数") 这个得到结果就是相当于传了不同的方法名--定义了多个实例对象-然后传给test2--得到一个test2的实例对象 #['test_1', 'test_2'] #<__main__.type tests=[<__main__.type testMethod=test_1>, <__main__.type testMethod=test_2>]> #比如: mapG=map(test1,MethodsName) a=test2(mapG) #<__main__.type tests=[<__main__.type testMethod=test_1>, <__main__.type testMethod=test_2>]> b=test2(mapG) #<__main__.type tests=[]> 补充:那么怎么执行test_1 和 test_2这两个方法呢 for item in a: print(item) #这个得到的打印出来的是以方法名为参数的实例对象对吧--实例对象取属性---取到methodName=item.methodName 这个也没毛病对吧 Method=getattr(item,item._testMethodName) #第一次item是test_1的实例对象-取到的methodName是test_1--第二次是test_2的实例对象,取到的是test_2 #之前有说过getattr是什么? print(Method()) >>><__main__.type testMethod=test_1> >>>('test_1', 1) >>><__main__.type testMethod=test_2> >>>test_2

  • 相关阅读:
    fastjson的@JSONField注解
    Java 日期比较大小
    linux 查看文件显示行号
    Java double 加、减、乘、除
    Java 身份证判断性别获取年龄
    linux 查看端口被占用
    Unable to complete the scan for annotations for web application [/wrs] due to a StackOverflowError. Possible root causes include a too low setting for -Xss and illegal cyclic inheritance dependencies.
    nginx 返回数据不完整
    linux redis 启动 overcommit_memory
    IntelliJ IDEA 常用设置
  • 原文地址:https://www.cnblogs.com/pythontest/p/13169351.html
Copyright © 2011-2022 走看看