TestLoader源码解析


- def loadTestsFromTestCase(self, testCaseClass) #看名称分析:从TestCase找测试集--那么就是把我们的def用例加载到testSuit里面
- def loadTestsFromModule(self, module, *args, pattern=None, **kws) #看名称分析:从模块里面找测试集,那么 模块>类>test_方法>添加到testSuit里面
- def loadTestsFromName(self, name, module=None) #看名称分析: 接收到name直接添加到testSuit里面
- def loadTestsFromNames(self, names, module=None) #看名称分析:接受到的是一个包含测试test_方法的列表
- def getTestCaseNames(self, testCaseClass) # 看名称分析: 取出一个包含test_方法的列表
- def discover(self, start_dir, pattern='test*.py', top_level_dir=None) ## 看名称分析:发现--找test_方法
- def _get_directory_containing_module(self, module_name) #获取目录包含的模块
- def _get_name_from_path(self, path) #从路径从找名称
- def _get_module_from_name(self, name) #从名称找模块
- def _match_path(self, path, full_path, pattern) #正则匹配路径--参数包含pattern 那估计是匹配我们测试脚本格式的
- def _find_tests(self, start_dir, pattern, namespace=False) #找测试集合
- def _find_test_path(self, full_path, pattern, namespace=False) #找测试集合的路径


- 那就是1234
- 一个discover,getTest,_match_path
- 二个find
- 三个_get
- 四个loadTests
- discover 逻辑
- >
- _find_tests【两个处理逻辑 一个是本次传的目录和上次传的一样或不一样,】
- 【一样:直接从我们传的目录下面继续去找testcaose---】
- 【不一样:会从我们传的目录下面去执行os.path.listdir找到所有的子文件列表paths(文件)),然后遍历得到单独的path做 start_dir+path拼接】
- >
- ①—get_name_from_path【传入start_dir,判断当前传入的目录是否为上次传入的顶级目录返回".",不一样可能有点绕-并返回一个值这个值有四种情况 . test ...test dir.tests--正常应该是返回test文件名
- ②_find_test_path(self, full_path, pattern, namespace=False)
- 【执行这个从路径中找test,那么很明显 一样:传目录路径 不一样传文件路径 】
- _find_test_path


- class TestLoader(object):
- """
- This class is responsible for loading tests according to various criteria
- and returning them wrapped in a TestSuite
- """
- testMethodPrefix = 'test'
- sortTestMethodsUsing = staticmethod(util.three_way_cmp)
- suiteClass = suite.TestSuite
- _top_level_dir = None
- def __init__(self):
- super(TestLoader, self).__init__()
- self.errors = []
- # Tracks packages which we have called into via load_tests, to
- # avoid infinite re-entrancy.
- self._loading_packages = set() #这里创建了一个空的self._loading_packages={}无序且不重复的元素集合


- def discover(self, start_dir, pattern='test*.py', top_level_dir=None): #一般我们top_level_dir传的都None
- set_implicit_top = False #是否存在顶层目录
- if top_level_dir is None and self._top_level_dir is not None:
- # make top_level_dir optional if called from load_tests in a package
- top_level_dir = self._top_level_dir #复次走这里
- elif top_level_dir is None: #初次走这里
- set_implicit_top = True
- top_level_dir = start_dir
- #上面这一串花里胡哨的东西就是处理顶层目录-如果是第一次启动服务-
- #就走elif-top_level_dir==我们下面传的值--之后--self._top_level_dir就不为空了,
- #但是top_level_dir 顶部是处理==None所以会走if=True
- top_level_dir = os.path.abspath(top_level_dir)#转绝对路径
- if not top_level_dir in sys.path:
- #这里是防止重复将top_level_dir加入执行目录--BUT如果我第一次传的start_dir=a,第二次传的start_dir=b
- #分析一下--第一次就是把a加入到了执行目录---下面self._top_level_dir=a 二次(复次)传b的时候,会出现top_level_dir=a---并没有判断b是否在执行目录
- #这里加一波问号?????????????????????
- #但是一般情况 我们目录就只有一个--所以这里--先放着。。。先看后面再来看这里
- # all test modules must be importable from the top level directory
- # should we *unconditionally* put the start directory in first
- # in sys.path to minimise likelihood of conflicts between installed
- # modules and development versions?
- sys.path.insert(0, top_level_dir)
- self._top_level_dir = top_level_dir
- #如果top_level_dir我们传的目录不在可执行目录--则临时添加进去
- is_not_importable = False #是否 不能导入
- is_namespace = False #is_namespace那么这个字段的意思就是是否可以找到传入的路径
- tests = []
- if os.path.isdir(os.path.abspath(start_dir)): #判断我们传的是否为一个目录--实际上这里直接用top_level_dir不香吗--
- start_dir = os.path.abspath(start_dir)
- # 之前把top_level_dir = start_dir
- # 然后top_level_dir = os.path.abspath(top_level_dir)
- #现在start_dir = os.path.abspath(start_dir)
- #为什么不 直接用top_level_dir? 小朋友你是否有许多问号
- # 问题出在上面--复次的时候--并没有走 top_level_dir = start_dir 而是走的 top_level_dir = self._top_level_dir ,
- #所以如果我们上次传的路径如果和这次不一样--那么top_level_dir是不等于start_dir--而start_dir才是我们传的--
- if start_dir != top_level_dir: #所以这里相当于判断前后传的路径是否一样--一般来说我们的start_dir都是等于top_level_dir的
- is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py'))#如果是一个文件返回false
- #如果不一样--则判断我们当前传入的start_dir/__init__.py是不是一个正确的文件路径..os.path.isfile() 返回布尔值
- else: #如果我们传入的不是一个目录,就开始一堆花里胡哨的报错东西了。。暂时不用管
- # support for discovery from dotted module names
- try:
- __import__(start_dir)
- #这里就很有意思了---__impor__("PyFiles.Besettest")那就是导入PyFiles
- #那么也就是说这个97.33的概率会报错--也就是说你如果目录错了--下面的else基本不会走。。。除非你很神奇的填的路径右侧是一个可导入的模块
- except ImportError:
- is_not_importable = True #如果导入不鸟--就is_not_importable设置为true 在这里我清楚了这个字段的含义--不能导入=true
- else:#那么这里假设导入成功之后
- the_module = sys.modules[start_dir] #这里是如果我们导入成功--就走这里-取出start_dir导入的赋值给the_module
- top_part = start_dir.split('.')[0] #这里是将我们导入的模块名称取出来
- try:
- start_dir = os.path.abspath( #打印导入模块所在目录的绝对路径
- os.path.dirname((the_module.__file__)))
- except AttributeError: #这里是如果导入模块成功了---但是尼玛打印导入模块的绝对路径又报错--不想看了+2
- # look for namespace packages
- try: #然后有开始进行模块导入检查---日了狗了。。。。。
- # fuck----想直接关机了+1,这里估计是想找到为什么不能导入的原因。。大神的思路就是完美,如果是我就抛出一个目录不对就完事
- #这一块的学习 文档 Python标准模块--import
- spec = the_module.__spec__
- #将导入成功的模块的规格说明赋值给spec--
- #打印出来就是ModuleSpec(name='besettest.interface', loader=<_frozen_importlib_external.SourceFileLoader object at 0x0000000003D6E780>, origin='E:\\PyFiles\\Besettest\\besettest\\interface\\__init__.py', submodule_search_locations=['E:\\PyFiles\\Besettest\\besettest\\interface'])
- #这么一串东西--也没用过。。。。大概就是模块名称、路径、导入的模块对象吧
- #origin 加载模块的位置--
- #loader_state模块特定数据的容器
- except AttributeError: #如果模块的规格说明取不出来。。。。。。。
- spec = None #我查阅了一下。。。的确存在部分模块规格说明为None的--所以还得继续往下看
- if spec and spec.loader is None: #如果存在规格说明 且 数据容器为None。
- if spec.submodule_search_locations is not None:
- #这是个什么玩意呢--模块 搜索 位置s(列表)。。。
- is_namespace = True #如果spec.submodule_search_locations不为none -----
- # 2.5级英文翻译 就是模块的路径 如果模块路径不为空is_namespace可以找到---is_namespace那么这个字段的意思就是存在命名空间。。就是可以找到这个模块
- for path in the_module.__path__: #这里我研究怀疑是故意提升逼格。。the_module.__path__==spec.submodule_search_locations
- if (not set_implicit_top and #首次set_implicit_top==True 复次set_implicit_top==Fase
- not path.startswith(top_level_dir)):
- continue
- #这里让我稍微有点疑惑。。为什么要判断是首次还是复次--我猜是判断the_module.__path__列表里面有几个某块的路径
- #如果是首次直接下一步--如果是复次会有多个路径。但是如果是复次top_level_dir这个路径又是上次的。。。日
- #他的作用是找到导入模块的路径-知道这个就行。。。
- self._top_level_dir = \
- (path.split(the_module.__name__
- .replace(".", os.path.sep))[0]) #取出导入模块的上级目录绝对路径。。。
- #the_module.__name__.replace(".", os.path.sep) 这一串我看来是没有必要的。。因为 the_module.__name__既然取到了模块名称那他肯定是一个字符串
- tests.extend(self._find_tests(path, #然后调用_find_tests 寻找测试。加入tests列表--这个有点熟悉的味道---
- pattern, #我觉得基本不会走这里去找---脚本路径一般都会填对 填错了,都不知道执行到哪里去了。。。
- namespace=True))
- elif the_module.__name__ in sys.builtin_module_names:
- #判断 sys.builtin_module_names返回一个列表,包含所有已经编译到Python解释器里的模块的名字 和sys.models是一个字典
- #就是没法导入报错
- # builtin module
- raise TypeError('Can not use builtin modules '
- 'as dotted module names') from None
- else: #没发现这个模块
- raise TypeError(
- 'don\'t know how to discover from {!r}'
- .format(the_module)) from None
- if set_implicit_top: #如果是首次。。。。
- if not is_namespace: #is_namespace默认的是false-且可以找到模块相关规格
- self._top_level_dir = \
- self._get_directory_containing_module(top_part) #interface.testFiles interface假设这个是导入的 -self._top_level_dir 是一个目录的绝对路径
- #top_part导入的模块名称-----
- sys.path.remove(top_level_dir) #只知道是从系统路径移除--但是不知道为什么移除。。。。
- else:
- sys.path.remove(top_level_dir) #
- if is_not_importable: #如果我们传的文件不能导入---就直接抛出异常
- raise ImportError('Start directory is not importable: %r' % start_dir)
- if not is_namespace: #is_namespace默认的是false--这里就是可以找到模块。。。。
- tests = list(self._find_tests(start_dir, pattern))
- return self.suiteClass(tests)


- def _find_tests(self, start_dir, pattern, namespace=False): #注意这里如果我们传的不是脚本目录而是一个可导入的模块namespace是等于True的
- """Used by discovery. Yields test suites it loads."""
- # Handle the __init__ in this package
- name = self._get_name_from_path(start_dir) #返回一个name name存在三种返回情况 "."-当本次和上次传入的start_dir一致 不一致 "文件名" "...文件名"
- #get_name_from_path的逻辑在这里就很清晰了
- # name is '.' when start_dir == top_level_dir (and top_level_dir is by
- # definition not a package).
- if name != '.' and name not in self._loading_packages:
- #当name最少有一个且也不再self._loading_packages.【self._loading_packages初始化的时候建的空集合】 走下面这个
- # name is in self._loading_packages while we have called into
- # loadTestsFromModule with name.
- tests, should_recurse = self._find_test_path( #然后这里start_dir是我们传的模块--他就去找。。这里就恢复到了传测试目录的逻辑了
- start_dir, pattern, namespace)
- if tests is not None:
- yield tests
- if not should_recurse:
- # Either an error occurred, or load_tests was used by the
- # package.
- return
- # Handle the contents.
- paths = sorted(os.listdir(start_dir)) #那就从这里开始--当我们穿的目录和上次一样-他会找到目录下所有的文件然后排序--我们的用例执行顺序就是从这里开始搞了。。
- for path in paths: #遍历我们传的目录下的所有文件
- full_path = os.path.join(start_dir, path) 将我们传入的目录和目录下的py文件拼接的完整路径
- tests, should_recurse = self._find_test_path( #把我们文件路径和我们的文件格式传入_find_test_path这个方法--
- full_path, pattern, namespace)
- 如果当前传的是一个目录-会返回should_recurse=True--这个英文直译是应该_递归--下面yield from 就是执行递归的操作
- if tests is not None:
- yield tests
- if should_recurse: #这句是判断他是不是一个目录
- # we found a package that didn't use load_tests.
- name = self._get_name_from_path(full_path)
- self._loading_packages.add(name)
- try:
- yield from self._find_tests(full_path, pattern, namespace)
- finally:
- self._loading_packages.discard(name)


- def a(n):
- testList=b(n)
- return testList
- def b(n,m=1):
- print("执行第%s次"%m)
- for a in range(n):
- if not divmod(a,2)[1] and a!=0:
- print(a)
- yield a #是用yield之后返回的是一个生成器
- if divmod(a,3)[1]:
- m =m+1
- yield from b(a,m) #重新执行b方法
- print(list(a(7)))
- 执行第1次
- 2
- 执行第2次
- 4
- 执行第3次
- 2
- 执行第4次
- 6
- [2, 4, 2, 6]


- name = self._get_name_from_path(start_dir) #因为discover我们是支持我们传目录或者模块寻找testcase的,所以这个方法
- def _get_name_from_path(self, path):
- #主要正确逻辑三个 比如我们的脚本目录结构是 E://a/b/ b目录下面有script.py 和 /c/script.py
- #第一次是我们自己传的目录--之前在discover他做了一个处理 就是第一次运行时会把我们传的目录赋值给顶层目录---
- #第一个逻辑判断我们传的是不是 -和顶层一样---一样的话===_find_tests方法就直接从目录下面找脚本-当如如果有目录也会继续走--他是在_find_tests_path判断的--最终也是回到找脚本模块上
- #如果不一样--那就是找了 找到这个目录了---那么就从顶层开始找这个目录的相对路径--其实就是找最后那个目录(必须是一个packge。上面说的目录都是包)、。。然后返回一个name
- #如果还有子目录 d---那就会返回 c.d
- if path == self._top_level_dir: #首次运行pattern,top_level_dir=None):self._top_level_dir = top_level_dir = start_dir -第二次运行如果目录没有变,这里也是直接返回的
- return '.'
- path = _jython_aware_splitext(os.path.normpath(path)) #如果我们当前传的和上次传的目录不一致。。这里得path我们当前传的路径
- _relpath = os.path.relpath(path, self._top_level_dir) #从self._top_level_dir开始找path的相对路径
- #这里是从我们传的path开始找到self._top_level_dir上次传的相对路径
- #例如: path=path1="E:\\PyFiles\\Besettest\\besettest\\interface\\testFiles" self._top_level_dir="E:\\PyFiles\\Besettest\\besettest\\interface\\result"
- #那么_relpath="..\testFiles" -暂时还不清楚为什么要找这个??????????????????????????
- assert not os.path.isabs(_relpath), "Path must be within the project"
- #↑↑断言 不是绝对路径-也就是说_relpath是否为相对路径↑↑↑特么的 这里肯定是一个相对路径啊。。。上面都有relpath了。。。丢
- #↓↓↓↓断言以..开头就失败。。。↓↓↓--这两处超出理解范围了。。。。。。
- assert not _relpath.startswith('..'), "Path must be within the project"
- name = _relpath.replace(os.path.sep, '.') #然后这里又把分隔符替换成. 返回 a.b 当然或许会有异常情况返回.....这是我意淫的
- return name


- def _find_test_path(self, full_path, pattern, namespace=False):
- #_find_tests()调用这个方法 传了一个我们传的目录下的a文件路径、和需要找的文件pattern-namespace【传的可能是true 也可能是false】,如果我的目录是对的-namespace传的就是false
- """Used by discovery.
- Loads tests from a single file, or a directories' __init__.py when
- passed the directory.
- Returns a tuple (None_or_tests_from_file, should_recurse).
- """
- basename = os.path.basename(full_path) #basename==文件名.py后续带py的统称文件--不带后缀的统称文件名。。。
- if os.path.isfile(full_path): #如果我们传的full_path是一个文件---我们在discover传的是一个脚本目录-之前在_test_find是做了一个拼接得到的完整路径full_path
- if not VALID_MODULE_NAME.match(basename): #判断他是不是一个py文件----
- # valid Python identifiers only
- return None, False #如果不是直接返回
- if not self._match_path(basename, full_path, pattern): #这里虽然传了三个值--但是实际上只有basename,pattern有用--
- #_match_path调用fnmatch(文件, 我们传的文件格式或文件)这个需要————from fnmatch import fnmatch他的主要作用是做此模块的主要作用是文件名称的匹配
- #当此次传入的文件名与我们的文件格式匹配一致self._match_path返回true
- return None, False #如果不一样 就直接回到 _find_test继续找
- # if the test file matches, load it
- name = self._get_name_from_path(full_path) #然后这里把文件路径又传到 self._get_name_from_path去返回文件名-这个时候因为我们传的是脚本目录-full_path目录下的文件路径,所以返回的name 就是文件名
- #self._top_level_dir是当前文件目录路径,path是当前文件路径--从目录找文件--直接就是文件名--他返回的name就是文件名
- try:
- module = self._get_module_from_name(name) #_get_module_from_name 这个方法就是动态导入模块名--然后返回一个所有导入的模块的对象 moudel.__file__路径、moudel.__name__名称
- except case.SkipTest as e: #如果导入不成功 case.SkipTest 实际上case是继承--Exception--所以把这个理解为Exception就可以了--
- return _make_skipped_test(name, e, self.suiteClass), False
- except:
- error_case, error_message = \
- _make_failed_import_test(name, self.suiteClass)
- self.errors.append(error_message)
- return error_case, False
- else: #module 获取到值之后走这里。。
- mod_file = os.path.abspath(
- getattr(module, '__file__', full_path)) #然后这里取出我们导入模块的 绝对路径---如果反射找不到就返回该文件的路径-其实差别不大-处理一下更严谨
- realpath = _jython_aware_splitext(
- os.path.realpath(mod_file)) #os.path.realpath(mod_file)然后又返回真实路径---然后又去掉路径的.py,。,,,,,,,,丢
- fullpath_noext = _jython_aware_splitext(
- os.path.realpath(full_path)) #然后full_path 找真实路径去掉.py
- if realpath.lower() != fullpath_noext.lower(): #如果动态导入的模块的目录路径 不等于 传进来(也就是pattern)的目录路径--实际上传进来的路径肯定是个绝对路径--因为前面已经转了好几次绝对路径了
- module_dir = os.path.dirname(realpath) #不等于就找动态导入模块所在的目录----实际上上面处理的realpath已经是一个目录了。。但是他防止realpath还是一个.py文件。所以又操作了一次
- mod_name = _jython_aware_splitext( #full_path是文件的路径.py的,然后这里又先是basename取出文件(就是把路径去掉,只留下xxx.py) 然后外面那个方法 把.py去掉--留下文件名
- os.path.basename(full_path))
- expected_dir = os.path.dirname(full_path)
- #然后找到需要执行脚本所在的目录。。。。。也就是说正常情况 假设expected_dir="e://a/b" 那么 mod_file =realpathfullpath_noext="e://a/b/scripy"
- #scripy是一个py文件---上面这个if是说的 正常情况。。。我想不到导入模块和导入模块的路径不相等的情况--不过这个不重要-源码这样肯定是有道理的
- msg = ("%r module incorrectly imported from %r. Expected "
- "%r. Is this module globally installed?")
- raise ImportError(
- msg % (mod_name, module_dir, expected_dir))
- return self.loadTestsFromModule(module, pattern=pattern), False
- #然后走 从模块从加载测试s 这个方法---也就是说discover实际上是调用loadTestsFromMould这个方法的。。测试套件也是在这一步处理的
- elif os.path.isdir(full_path): #dicover里面传脚本目录是走这里。。
- if (not namespace and #namespace-默认是false not namespace就是true
- not os.path.isfile(os.path.join(full_path, '__init__.py'))): #不是一个包。。-也就是说我们传的目录应该是一个包,下面包含__init__.py
- return None, False
- load_tests = None #这个load_tests是啥意思呢??????????后面继续看----看了一遍--并且用unittest.main()试了一下-模块下面是没有这个属性的。。只是unittest的初始化文件有这个方法--他也是通过discover找的。。
- tests = None
- name = self._get_name_from_path(full_path) #这里就是走子目录的逻辑了
- #get_name_from_path的逻辑在这里就很清晰了
- #A.如果通过_find_test 调用self._get_name_from_path 是为了判断两次start_dir是否一致一致返回. 不一致返回从上次的start_dir1找到本次start_dir12的相对路径--
- #这里又分两种情况-A1正常情况-start_dir1是start_dir12的上级目录。。。那么返回的那么就是-A.B这样的了。。因为第一次的A是已经os.path.insert到环境变量了..所以A.B是可以直接用
- #A2不正常情况 就是之前说的 最少返回一个点的...A这种返回---然后问题来了--他为什么要这么处理呢--原因是?????????
- #我猜是与脚本同级存在另一个脚本目录。。。后面验证这一点。-----这里在上面补充了--是因为子目录中还存在脚本所有这么走逻辑-完美的
- try:
- package = self._get_module_from_name(name) #上面是导入的一个module--这里是导入一个包-- 返回--
- except case.SkipTest as e:
- return _make_skipped_test(name, e, self.suiteClass), False
- except:
- error_case, error_message = \
- _make_failed_import_test(name, self.suiteClass)
- self.errors.append(error_message)
- return error_case, False
- else:
- load_tests = getattr(package, 'load_tests', None) #然后判断这个包里面有没有'load_tests'这个属性---这里我代码一直看下来,我们是不知道这个lood_tests是什么的,字面意思 加载测试集合
- # Mark this package as being in load_tests (possibly ;))
- self._loading_packages.add(name) #然后把模块名称添加到set集合
- try:
- tests = self.loadTestsFromModule(package, pattern=pattern)
- #这里传了一个package模块对象,和文件匹配规则合作或者文件。。--但是这里导入一个包之后实际上是找不到testCase的-因为包下面的属性肯定不是一个类-不会走loadTestsFromTestCase
- #所以这里返回的tests是一个空列表
- if load_tests is not None: #貌似这个是弃用的,向后兼容-暂时没看明白这个load_tests代表的意思
- # loadTestsFromModule(package) has loaded tests for us.
- return tests, False
- return tests, True # 如果能走到这里------就返回True_就是给_find_test判断走递归的--_find_tests里面就得到 should_recurse=True
- finally:
- self._loading_packages.discard(name) #然后这个删掉set集合里面之前导入的那个包
- else:
- return None, False


- def loadTestsFromModule(self, module, *args, pattern=None, **kws):
- """Return a suite of all test cases contained in the given module"""
- # This method used to take an undocumented and unofficial
- # use_load_tests argument. For backward compatibility, we still
- # accept the argument (which can also be the first position) but we
- # ignore it and issue a deprecation warning if it's present.
- if len(args) > 0 or 'use_load_tests' in kws: #args这个默认是一个空元组 长度默认为0 kws是一个空字典
- warnings.warn('use_load_tests is deprecated and ignored',
- DeprecationWarning)
- kws.pop('use_load_tests', None)
- if len(args) > 1:
- # Complain about the number of arguments, but don't forget the
- # required `module` argument.
- complaint = len(args) + 1
- raise TypeError('loadTestsFromModule() takes 1 positional argument but {} were given'.format(complaint))
- if len(kws) != 0:
- # Since the keyword arguments are unsorted (see PEP 468), just
- # pick the alphabetically sorted first argument to complain about,
- # if multiple were given. At least the error message will be
- # predictable.
- complaint = sorted(kws)[0] #取出第一个Key-
- raise TypeError("loadTestsFromModule() got an unexpected keyword argument '{}'".format(complaint))
- tests = []
- for name in dir(module): #这里得modul实际上使我们传入的模块对象---dir(object) 返回模块下的所有属性列表-
- obj = getattr(module, name) #然后反射返回name对象。。返回的是一个class对象
- if isinstance(obj, type) and issubclass(obj, case.TestCase): #这里判断obj是否是一个类--并且这个类是case.TestCase的子类,也就是说 是否写在我们继承unitest.testCase那个类的下面
- tests.append(self.loadTestsFromTestCase(obj)) #可以看到最后走loadTestFromTestCase obj这里是传入的一个类名
- load_tests = getattr(module, 'load_tests', None)
- tests = self.suiteClass(tests)
- if load_tests is not None:
- try:
- return load_tests(self, tests, pattern)
- except Exception as e:
- error_case, error_message = _make_failed_load_tests(
- module.__name__, e, self.suiteClass)
- self.errors.append(error_message)
- return error_case
- return tests #返回集合


- def loadTestsFromTestCase(self, testCaseClass): #testCaseClass是我们传的一个用例类
- """Return a suite of all test cases contained in testCaseClass"""
- if issubclass(testCaseClass, suite.TestSuite): #这个类是不是suite.TestSuite的子类--如果是的就抛出异常==
- raise TypeError("Test cases should not be derived from "
- "TestSuite. Maybe you meant to derive from "
- "TestCase?")
- testCaseNames = self.getTestCaseNames(testCaseClass) #getTestCaseNames 从类下面找到用例名称--找到名称返回的是一个列表
- if not testCaseNames and hasattr(testCaseClass, 'runTest'): #这里判断testCaseNames是否为空-并且 是否存在"runTest"这个元素
- testCaseNames = ['runTest']
- loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames)) #是我的一个用例类--然后将testCaseNames类下面的测试方法--带进去遍历。。。高级用法---第一次见--这个方法很关键
- return loaded_suite


- def getTestCaseNames(self, testCaseClass):
- """Return a sorted sequence of method names found within testCaseClass
- """
- def isTestMethod(attrname, testCaseClass=testCaseClass, #定义一个内部方法
- prefix=self.testMethodPrefix): #self.testMethodPrefix="test" 这个在TestLoader下第一行就已经默认了,他是我们用例开头的固定格式
- return attrname.startswith(prefix) and \ #这里判断了是否已test开头以及 方法对象是否可用----getattr返回方法对象--callable()是检测对象是否可用 返回一个布尔值
- callable(getattr(testCaseClass, attrname))
- testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
- #dir(testCaseClass)返回该对象下面所有的属性--包括变量test_1--所以上面需要检测属性时test开头且是一个可以调用的对象--
- #filter函数---前面是一个function -后面是一个可迭代对象-会遍历可迭代对象-并传入function-functions返回true则添加到列表--这样就找到了所有的
- if self.sortTestMethodsUsing:
- testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))
- #sortTestMethodsUsing = staticmethod(util.three_way_cmp) 转为为静态方法-内存地址指向self.sortTestMethodsUsing
- #然后通过functools这个模块排序==
- return testFnNames #然后返回用例方法名称列表
TestLoader源码解析的更多相关文章
- 语义分割丨PSPNet源码解析「测试阶段」
引言 本文接着上一篇语义分割丨PSPNet源码解析「网络训练」,继续介绍语义分割的测试阶段. 模型训练完成后,以什么样的策略来进行测试也非常重要. 一般来说模型测试分为单尺度single scale和 ...
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
- 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...
- 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...
- 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例
前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...
- jQuery2.x源码解析(缓存篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 缓存是jQuery中的又一核心设计,jQuery ...
- Spring IoC源码解析——Bean的创建和初始化
Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架,同时也是轻量级的IoC和AOP的容器框架,主要是针对JavaBean的生命周期进行管理的轻量级容器 ...
- jQuery2.x源码解析(构建篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...
- jQuery2.x源码解析(设计篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...
随机推荐
- 使用records库操作SQL并且查询MySQL数据库
获取数据库 db = records.Database('mysql://root:xxxx@47.106.151.165/web_table?charset=utf8')注释:xxxx为数据密码 执 ...
- Mycat-多实例的搭建
1. 基础环境准备1.1 环境准备:两台虚拟机 db01 db02每台创建四个mysql实例:3307 3308 3309 33101.2 删除历史环境:pkill mysqldrm -rf /dat ...
- eatwhatApp开发实战(七)
之前我们为app添加了读取本地数据的功能和删除的功能.本次我们来将listview上item项的触控修改为item项上单一控件的触控事件.用item项上的button来实现删除数据. 先上布局: &l ...
- 正则表达式:匹配单个数字重复n次
匹配单个数字重复n次:(\d)\1{n-1}其中,\d表示一位数字,(\d)表示匹配之后捕获该匹配,并分组并对组进行编号\1表示被捕获的第一个分组{n-1}是因为被捕获的第一个分组已经消耗了一位数字, ...
- DDD之1微服务设计为什么选择DDD
背景 名词解释 如果你的团队目前正是构建微服务架构风格的软件系统,问自己两个问题? 软件架构演进 软件架构大致经历了从单机架构,集中式架构,分布式微服架构,程序的层次图如下所示. 单机架构 特点如下: ...
- 最新 iOS 框架整体梳理(一)
前言 这段话其实是我差不多写完文章之后再回过头来写的,原本在写文章之前想写一下写的初衷的,但当我写完之后感觉初衷没有收获更真切一些.其实到这篇为止总结出来的也就三十多个,有些是比较新的框架,有些是我们 ...
- Java实现 LeetCode 576 出界的路径数(DFS || DP)
576. 出界的路径数 给定一个 m × n 的网格和一个球.球的起始坐标为 (i,j) ,你可以将球移到相邻的单元格内,或者往上.下.左.右四个方向上移动使球穿过网格边界.但是,你最多可以移动 N ...
- Java实现 LeetCode 50 Pow(x,n)
50. Pow(x, n) 实现 pow(x, n) ,即计算 x 的 n 次幂函数. 示例 1: 输入: 2.00000, 10 输出: 1024.00000 示例 2: 输入: 2.10000, ...
- Java中StringBuffer和StringBuilder的区别
区别1线程安全: StringBuffer是线程安全的,StringBuilder是线程是不安全的.因为StringBuffer的所有公开方法都用synchronized 来修饰,StringBuil ...
- Java实现 洛谷 P1579 哥德巴赫猜想(升级版)
题目背景 1742年6月7日哥德巴赫写信给当时的大数学家欧拉,正式提出了以下的猜想:任何一个大于9的奇数都可以表示成3个质数之和.质数是指除了1和本身之外没有其他约数的数,如2和11都是质数,而6不是 ...