TestLoader源碼解析

 1 def loadTestsFromTestCase(self, testCaseClass) #看名稱分析:從TestCase找測試集--那麼就是把我們的def用例加載到testSuit裏面
 2 def loadTestsFromModule(self, module, *args, pattern=None, **kws) #看名稱分析:從模塊裏面找測試集,那麼 模塊>類>test_方法>添加到testSuit裏面
 3 def loadTestsFromName(self, name, module=None)  #看名稱分析: 接收到name直接添加到testSuit裏面
 4 def loadTestsFromNames(self, names, module=None) #看名稱分析:接受到的是一個包含測試test_方法的列表
 5 def getTestCaseNames(self, testCaseClass) # 看名稱分析:  取出一個包含test_方法的列表
 6 def discover(self, start_dir, pattern='test*.py', top_level_dir=None) ## 看名稱分析:發現--找test_方法
 7 def _get_directory_containing_module(self, module_name) #獲取目錄包含的模塊
 8 def _get_name_from_path(self, path)  #從路徑從找名稱
 9 def _get_module_from_name(self, name)  #從名稱找模塊
10 def _match_path(self, path, full_path, pattern)   #正則匹配路徑--參數包含pattern 那估計是匹配我們測試腳本格式的
11 def _find_tests(self, start_dir, pattern, namespace=False)  #找測試集合
12 def _find_test_path(self, full_path, pattern, namespace=False)  #找測試集合的路徑

View Code

 1 那就是1234
 2 一個discover,getTest,_match_path
 3 二個find
 4 三個_get
 5 四個loadTests
 6 
 7 discover  邏輯
 8 >
 9 _find_tests【兩個處理邏輯  一個是本次傳的目錄和上次傳的一樣或不一樣,】
10 【一樣:直接從我們傳的目錄下面繼續去找testcaose---11 【不一樣:會從我們傳的目錄下面去執行os.path.listdir找到所有的子文件列表paths(文件)),然後遍歷得到單獨的path做 start_dir+path拼接】
12 >
13 ①—get_name_from_path【傳入start_dir,判斷當前傳入的目錄是否為上次傳入的頂級目錄返回".",不一樣可能有點繞-並返回一個值這個值有四種情況 . test  ...test dir.tests--正常應該是返回test文件名
14 ②_find_test_path(self, full_path, pattern, namespace=False)
15 【執行這個從路徑中找test,那麼很明顯 一樣:傳目錄路徑   不一樣傳文件路徑  】
16 _find_test_path

View Code

 1 class TestLoader(object):
 2     """
 3     This class is responsible for loading tests according to various criteria
 4     and returning them wrapped in a TestSuite
 5     """
 6     testMethodPrefix = 'test'
 7     sortTestMethodsUsing = staticmethod(util.three_way_cmp)
 8     suiteClass = suite.TestSuite
 9     _top_level_dir = None
10 
11     def __init__(self):
12         super(TestLoader, self).__init__()
13         self.errors = []
14         # Tracks packages which we have called into via load_tests, to
15         # avoid infinite re-entrancy.
16         self._loading_packages = set()  #這裏創建了一個空的self._loading_packages={}無序且不重複的元素集合

View Code 1.discover方法:unittest.defaultTestLoader a.定義了三個布爾值屬性 is_not_importable==True則是不能導入,is_namespace ,set_implicit_top b.對頂層目錄做了處理–當服務首次啟動 執行unittest.defaultTestLoader.discover(“文件目錄A”,pattern,top_level_dir=None):self._top_level_dir = top_level_dir = start_dir 這三個相等。 再次執行unittest.defaultTestLoader.discover(“文件目錄B”,pattern,top_level_dir=None): top_level_dir=self._top_level_dir【也就是他會默認上次start_dir 為頂層目錄】– 無論是首次還是複次–上面的操作完成之後 self._top_level_dir = top_level_dir 仍然繼續執行了一句這個—也就是說 self._top_level_dir == top_level_dir 始終一樣 c.針對頂層目錄不是一個目錄文件做了一系列的處理如果你傳的目錄是一個可導入的模塊-他在這個異常處理中.會重新自導入這個模塊。並開始追尋他的絕對路徑,判斷其模塊的可用性,然後執行_find_tests()尋找用例 d.如果是一個目錄就直接開始執行_find_tests()尋找用例 e.所以他這裏分兩種情況可以找到用例 第一種:傳的目錄 第二種:傳入的可導入模塊-這種情況self._top_level_dir 最終也是一個絕對路徑

  1 def discover(self, start_dir, pattern='test*.py', top_level_dir=None):  #一般我們top_level_dir傳的都None
  2     set_implicit_top = False   #是否存在頂層目錄
  3     if top_level_dir is None and self._top_level_dir is not None:
  4         # make top_level_dir optional if called from load_tests in a package
  5         top_level_dir = self._top_level_dir  #複次走這裏
  6     elif top_level_dir is None:  #初次走這裏
  7         set_implicit_top = True
  8         top_level_dir = start_dir
  9     #上面這一串花里胡哨的東西就是處理頂層目錄-如果是第一次啟動服務-
 10     #就走elif-top_level_dir==我們下面傳的值--之後--self._top_level_dir就不為空了,
 11     #但是top_level_dir  頂部是處理==None所以會走if=True
 12     top_level_dir = os.path.abspath(top_level_dir)#轉絕對路徑
 13     if not top_level_dir in sys.path:
 14         #這裡是防止重複將top_level_dir加入執行目錄--BUT如果我第一次傳的start_dir=a,第二次傳的start_dir=b
 15         #分析一下--第一次就是把a加入到了執行目錄---下面self._top_level_dir=a 二次(複次)傳b的時候,會出現top_level_dir=a---並沒有判斷b是否在執行目錄
 16         #這裏加一波問號?????????????????????
 17         #但是一般情況 我們目錄就只有一個--所以這裏--先放着。。。先看後面再來看這裏
 18         # all test modules must be importable from the top level directory
 19         # should we *unconditionally* put the start directory in first
 20         # in sys.path to minimise likelihood of conflicts between installed
 21         # modules and development versions?
 22         sys.path.insert(0, top_level_dir)
 23     self._top_level_dir = top_level_dir
 24     #如果top_level_dir我們傳的目錄不在可執行目錄--則臨時添加進去
 25     is_not_importable = False  #是否  不能導入
 26     is_namespace = False    #is_namespace那麼這個字段的意思就是是否可以找到傳入的路徑
 27     tests = []
 28     if os.path.isdir(os.path.abspath(start_dir)):  #判斷我們傳的是否為一個目錄--實際上這裏直接用top_level_dir不香嗎--
 29         start_dir = os.path.abspath(start_dir)
 30         # 之前把top_level_dir = start_dir
 31         # 然後top_level_dir = os.path.abspath(top_level_dir)
 32         #現在start_dir = os.path.abspath(start_dir)
 33         #為什麼不   直接用top_level_dir?  小朋友你是否有許多問號
 34         # 問題出在上面--複次的時候--並沒有走 top_level_dir = start_dir 而是走的 top_level_dir = self._top_level_dir ,
 35         #所以如果我們上次傳的路徑如果和這次不一樣--那麼top_level_dir是不等於start_dir--而start_dir才是我們傳的--
 36         if start_dir != top_level_dir:  #所以這裏相當於判斷前後傳的路徑是否一樣--一般來說我們的start_dir都是等於top_level_dir的
 37             is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py'))#如果是一個文件返回false
 38             #如果不一樣--則判斷我們當前傳入的start_dir/__init__.py是不是一個正確的文件路徑..os.path.isfile() 返回布爾值
 39     else: #如果我們傳入的不是一個目錄,就開始一堆花里胡哨的報錯東西了。。暫時不用管
 40         # support for discovery from dotted module names
 41         try:
 42             __import__(start_dir)
 43             #這裏就很有意思了---__impor__("PyFiles.Besettest")那就是導入PyFiles
 44             #那麼也就是說這個97.33的概率會報錯--也就是說你如果目錄錯了--下面的else基本不會走。。。除非你很神奇的填的路徑右側是一個可導入的模塊
 45         except ImportError:
 46             is_not_importable = True   #如果導入不鳥--就is_not_importable設置為true  在這裏我清楚了這個字段的含義--不能導入=true
 47         else:#那麼這裏假設導入成功之後
 48             the_module = sys.modules[start_dir]  #這裡是如果我們導入成功--就走這裏-取出start_dir導入的賦值給the_module
 49             top_part = start_dir.split('.')[0]    #這裡是將我們導入的模塊名稱取出來
 50             try:
 51                 start_dir = os.path.abspath( #打印導入模塊所在目錄的絕對路徑
 52                    os.path.dirname((the_module.__file__)))
 53             except AttributeError:     #這裡是如果導入模塊成功了---但是尼瑪打印導入模塊的絕對路徑又報錯--不想看了+2
 54                 # look for namespace packages
 55                 try:  #然後有開始進行模塊導入檢查---日了狗了。。。。。
 56                     #  fuck----想直接關機了+1,這裏估計是想找到為什麼不能導入的原因。。大神的思路就是完美,如果是我就拋出一個目錄不對就完事
 57                     #這一塊的學習 文檔 Python標準模塊--import
 58                     spec = the_module.__spec__
 59                     #將導入成功的模塊的規格說明賦值給spec--
 60                     #打印出來就是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'])
 61                     #這麼一串東西--也沒用過。。。。大概就是模塊名稱、路徑、導入的模塊對象吧
 62                     #origin 加載模塊的位置--
 63                     #loader_state模塊特定數據的容器
 64                 except AttributeError:  #如果模塊的規格說明取不出來。。。。。。。
 65                     spec = None   #我查閱了一下。。。的確存在部分模塊規格說明為None的--所以還得繼續往下看
 66 
 67                 if spec and spec.loader is None:   #如果存在規格說明 且 數據容器為None。
 68                     if spec.submodule_search_locations is not None: 
 69                         #這是個什麼玩意呢--模塊 搜索 位置s(列表)。。。
 70                         is_namespace = True    #如果spec.submodule_search_locations不為none -----
 71                         # 2.5級英文翻譯 就是模塊的路徑 如果模塊路徑不為空is_namespace可以找到---is_namespace那麼這個字段的意思就是存在命名空間。。就是可以找到這個模塊
 72                         for path in the_module.__path__:   #這裏我研究懷疑是故意提升逼格。。the_module.__path__==spec.submodule_search_locations
 73                             if (not set_implicit_top and     #首次set_implicit_top==True  複次set_implicit_top==Fase
 74                                 not path.startswith(top_level_dir)):
 75                                 continue
 76                                 #這裏讓我稍微有點疑惑。。為什麼要判斷是首次還是複次--我猜是判斷the_module.__path__列表裡面有幾個某塊的路徑
 77                                 #如果是首次直接下一步--如果是複次會有多個路徑。但是如果是複次top_level_dir這個路徑又是上次的。。。日
 78                                 #他的作用是找到導入模塊的路徑-知道這個就行。。。
 79                             self._top_level_dir = \
 80                                 (path.split(the_module.__name__
 81                                      .replace(".", os.path.sep))[0])   #取出導入模塊的上級目錄絕對路徑。。。
 82                             #the_module.__name__.replace(".", os.path.sep) 這一串我看來是沒有必要的。。因為  the_module.__name__既然取到了模塊名稱那他肯定是一個字符串
 83                             tests.extend(self._find_tests(path,     #然後調用_find_tests 尋找測試。加入tests列表--這個有點熟悉的味道---
 84                                                           pattern,  #我覺得基本不會走這裏去找---腳本路徑一般都會填對  填錯了,都不知道執行到哪裡去了。。。
 85                                                           namespace=True))
 86                 elif the_module.__name__ in sys.builtin_module_names:
 87                     #判斷 sys.builtin_module_names返回一個列表,包含所有已經編譯到Python解釋器里的模塊的名字 和sys.models是一個字典
 88                     #就是沒法導入報錯
 89                     # builtin module
 90                     raise TypeError('Can not use builtin modules '
 91                                     'as dotted module names') from None
 92                 else:  #沒發現這個模塊
 93                     raise TypeError(
 94                         'don\'t know how to discover from {!r}'
 95                         .format(the_module)) from None
 96 
 97             if set_implicit_top:     #如果是首次。。。。
 98                 if not is_namespace:  #is_namespace默認的是false-且可以找到模塊相關規格
 99                     self._top_level_dir = \
100                        self._get_directory_containing_module(top_part)  #interface.testFiles   interface假設這個是導入的 -self._top_level_dir 是一個目錄的絕對路徑
101                     #top_part導入的模塊名稱-----
102                     sys.path.remove(top_level_dir)    #只知道是從系統路徑移除--但是不知道為什麼移除。。。。
103                 else:
104                     sys.path.remove(top_level_dir)   #
105 
106     if is_not_importable:   #如果我們傳的文件不能導入---就直接拋出異常
107         raise ImportError('Start directory is not importable: %r' % start_dir)
108 
109     if not is_namespace:  #is_namespace默認的是false--這裏就是可以找到模塊。。。。
110         tests = list(self._find_tests(start_dir, pattern))
111     return self.suiteClass(tests)

View Code 2._find_tests()–尋找testCase並生成測試套件tests=[]

 1 def _find_tests(self, start_dir, pattern, namespace=False):   #注意這裏如果我們傳的不是腳本目錄而是一個可導入的模塊namespace是等於True的
 2     """Used by discovery. Yields test suites it loads."""
 3     # Handle the __init__ in this package
 4     name = self._get_name_from_path(start_dir)   #返回一個name   name存在三種返回情況  "."-當本次和上次傳入的start_dir一致     不一致  "文件名"    "...文件名"
 5      #get_name_from_path的邏輯在這裏就很清晰了 
 6           
 7     # name is '.' when start_dir == top_level_dir (and top_level_dir is by
 8     # definition not a package).
 9     if name != '.' and name not in self._loading_packages:  
10     #當name最少有一個且也不再self._loading_packages.【self._loading_packages初始化的時候建的空集合】 走下面這個
11         # name is in self._loading_packages while we have called into
12         # loadTestsFromModule with name.
13         tests, should_recurse = self._find_test_path(      #然後這裏start_dir是我們傳的模塊--他就去找。。這裏就恢復到了傳測試目錄的邏輯了
14             start_dir, pattern, namespace)
15         if tests is not None:
16             yield tests
17         if not should_recurse:
18             # Either an error occurred, or load_tests was used by the
19             # package.
20             return
21     # Handle the contents.
22     paths = sorted(os.listdir(start_dir))  #那就從這裏開始--當我們穿的目錄和上次一樣-他會找到目錄下所有的文件然後排序--我們的用例執行順序就是從這裏開始搞了。。
23     for path in paths:       #遍歷我們傳的目錄下的所有文件
24         full_path = os.path.join(start_dir, path)     將我們傳入的目錄和目錄下的py文件拼接的完整路徑
25         tests, should_recurse = self._find_test_path(    #把我們文件路徑和我們的文件格式傳入_find_test_path這個方法--
26             full_path, pattern, namespace)
27         如果當前傳的是一個目錄-會返回should_recurse=True--這個英文直譯是應該_遞歸--下面yield from 就是執行遞歸的操作
28         if tests is not None:
29             yield tests
30         if should_recurse:  #這句是判斷他是不是一個目錄
31             # we found a package that didn't use load_tests.
32             name = self._get_name_from_path(full_path)
33             self._loading_packages.add(name)
34             try:
35                 yield from self._find_tests(full_path, pattern, namespace)
36             finally:
37                 self._loading_packages.discard(name)

View Code yield與yield from

 1 def  a(n):
 2     testList=b(n)
 3     return testList
 4 
 5 def  b(n,m=1):
 6     print("執行第%s次"%m)
 7     for a in range(n):
 8         if not divmod(a,2)[1] and a!=0:
 9             print(a)
10             yield a   #是用yield之後返回的是一個生成器
11             if divmod(a,3)[1]:
12                 m =m+1
13                 yield from b(a,m)  #重新執行b方法
14 
15 print(list(a(7)))
16 
17 執行第1次
18 2
19 執行第2次
20 4
21 執行第3次
22 2
23 執行第4次
24 6
25 [2, 4, 2, 6]

View Code _get_name_from_path 他主要做了:從我們傳的路徑裏面找腳本文件 名。。。如果找到了腳本文件則返迴文件名稱—如果沒找到也就是我們返回一個點 或者 至少一個點(三種情況 . test_case …..test_case 返回name可能存在的三種值) 在_test_find調用這個方法path=start_dir(我們傳的目錄)–這個返回一個點 或者 至少一個點 在 _fin_test_path調用這個方法是傳的我們傳的目錄下的文件路口–返回的name就是文件名

 1 name = self._get_name_from_path(start_dir)    #因為discover我們是支持我們傳目錄或者模塊尋找testcase的,所以這個方法
 2 
 3 def _get_name_from_path(self, path):
 4      #主要正確邏輯三個 比如我們的腳本目錄結構是  E://a/b/     b目錄下面有script.py    和  /c/script.py
 5      #第一次是我們自己傳的目錄--之前在discover他做了一個處理 就是第一次運行時會把我們傳的目錄賦值給頂層目錄---
 6      #第一個邏輯判斷我們傳的是不是  -和頂層一樣---一樣的話===_find_tests方法就直接從目錄下面找腳本-當如如果有目錄也會繼續走--他是在_find_tests_path判斷的--最終也是回到找腳本模塊上
 7      #如果不一樣--那就是找了 找到這個目錄了---那麼就從頂層開始找這個目錄的相對路徑--其實就是找最後那個目錄(必須是一個packge。上面說的目錄都是包)、。。然後返回一個name
 8      #如果還有子目錄  d---那就會返回  c.d
 9     if path == self._top_level_dir:  #首次運行pattern,top_level_dir=None):self._top_level_dir = top_level_dir = start_dir -第二次運行如果目錄沒有變,這裏也是直接返回的
10         return '.'
11         
12         
13     path = _jython_aware_splitext(os.path.normpath(path))  #如果我們當前傳的和上次傳的目錄不一致。。這裏得path我們當前傳的路徑
14     
15     _relpath = os.path.relpath(path, self._top_level_dir)    #從self._top_level_dir開始找path的相對路徑
16     #這裡是從我們傳的path開始找到self._top_level_dir上次傳的相對路徑 
17     #例如: path=path1="E:\\PyFiles\\Besettest\\besettest\\interface\\testFiles"   self._top_level_dir="E:\\PyFiles\\Besettest\\besettest\\interface\\result"
18     #那麼_relpath="..\testFiles"    -暫時還不清楚為什麼要找這個??????????????????????????
19     assert not os.path.isabs(_relpath), "Path must be within the project"
20     #↑↑斷言 不是絕對路徑-也就是說_relpath是否為相對路徑↑↑↑特么的 這裏肯定是一個相對路徑啊。。。上面都有relpath了。。。丟
21     #↓↓↓↓斷言以..開頭就失敗。。。↓↓↓--這兩處超出理解範圍了。。。。。。
22     assert not _relpath.startswith('..'), "Path must be within the project"
23     
24     name = _relpath.replace(os.path.sep, '.')  #然後這裏又把分隔符替換成.  返回 a.b  當然或許會有異常情況返回.....這是我意淫的
25     return name

View Code self._find_test_path #找測試的路徑 兩個主要邏輯: 傳到full_path 是一個文件 還是一個目錄

 1 def _find_test_path(self, full_path, pattern, namespace=False):  
 2  #_find_tests()調用這個方法 傳了一個我們傳的目錄下的a文件路徑、和需要找的文件pattern-namespace【傳的可能是true 也可能是false】,如果我的目錄是對的-namespace傳的就是false
 3     """Used by discovery.
 4 
 5     Loads tests from a single file, or a directories' __init__.py when
 6     passed the directory.
 7 
 8     Returns a tuple (None_or_tests_from_file, should_recurse).
 9     """
10     basename = os.path.basename(full_path)   #basename==文件名.py後續帶py的統稱文件--不帶後綴的統稱文件名。。。
11     if os.path.isfile(full_path):   #如果我們傳的full_path是一個文件---我們在discover傳的是一個腳本目錄-之前在_test_find是做了一個拼接得到的完整路徑full_path
12         if not VALID_MODULE_NAME.match(basename): #判斷他是不是一個py文件----
13             
14             # valid Python identifiers only
15             return None, False   #如果不是直接返回
16         if not self._match_path(basename, full_path, pattern):  #這裏雖然傳了三個值--但是實際上只有basename,pattern有用--
17          #_match_path調用fnmatch(文件, 我們傳的文件格式或文件)這個需要————from fnmatch import fnmatch他的主要作用是做此模塊的主要作用是文件名稱的匹配
18          #當此次傳入的文件名與我們的文件格式匹配一致self._match_path返回true
19             return None, False    #如果不一樣 就直接回到 _find_test繼續找
20         # if the test file matches, load it
21         name = self._get_name_from_path(full_path)   #然後這裏把文件路徑又傳到 self._get_name_from_path去返迴文件名-這個時候因為我們傳的是腳本目錄-full_path目錄下的文件路徑,所以返回的name 就是文件名
22        
23         #self._top_level_dir是當前文件目錄路徑,path是當前文件路徑--從目錄找文件--直接就是文件名--他返回的name就是文件名   
24         try:
25             module = self._get_module_from_name(name)    #_get_module_from_name  這個方法就是動態導入模塊名--然後返回一個所有導入的模塊的對象 moudel.__file__路徑、moudel.__name__名稱
26         except case.SkipTest as e:   #如果導入不成功 case.SkipTest 實際上case是繼承--Exception--所以把這個理解為Exception就可以了--
27             return _make_skipped_test(name, e, self.suiteClass), False
28         except:
29             error_case, error_message = \
30                 _make_failed_import_test(name, self.suiteClass)   
31             self.errors.append(error_message)
32             return error_case, False
33         else: #module 獲取到值之後走這裏。。
34             mod_file = os.path.abspath(
35                 getattr(module, '__file__', full_path))    #然後這裏取出我們導入模塊的 絕對路徑---如果反射找不到就返回該文件的路徑-其實差別不大-處理一下更嚴謹
36             realpath = _jython_aware_splitext(
37                 os.path.realpath(mod_file))   #os.path.realpath(mod_file)然後又返回真實路徑---然後又去掉路徑的.py,。,,,,,,,,丟
38             fullpath_noext = _jython_aware_splitext(
39                 os.path.realpath(full_path))   #然後full_path 找真實路徑去掉.py
40             if realpath.lower() != fullpath_noext.lower():  #如果動態導入的模塊的目錄路徑 不等於 傳進來(也就是pattern)的目錄路徑--實際上傳進來的路徑肯定是個絕對路徑--因為前面已經轉了好幾次絕對路徑了
41                 module_dir = os.path.dirname(realpath)   #不等於就找動態導入模塊所在的目錄----實際上上面處理的realpath已經是一個目錄了。。但是他防止realpath還是一個.py文件。所以又操作了一次
42                 mod_name = _jython_aware_splitext(    #full_path是文件的路徑.py的,然後這裏又先是basename取出文件(就是把路徑去掉,只留下xxx.py) 然後外面那個方法 把.py去掉--留下文件名
43                     os.path.basename(full_path))
44                 expected_dir = os.path.dirname(full_path)  
45                 #然後找到需要執行腳本所在的目錄。。。。。也就是說正常情況  假設expected_dir="e://a/b"  那麼 mod_file =realpathfullpath_noext="e://a/b/scripy" 
46                 #scripy是一個py文件---上面這個if是說的 正常情況。。。我想不到導入模塊和導入模塊的路徑不相等的情況--不過這個不重要-源碼這樣肯定是有道理的
47                   msg = ("%r module incorrectly imported from %r. Expected "
48                        "%r. Is this module globally installed?")
49                 raise ImportError(
50                     msg % (mod_name, module_dir, expected_dir))
51             return self.loadTestsFromModule(module, pattern=pattern), False   
52             #然後走 從模塊從加載測試s 這個方法---也就是說discover實際上是調用loadTestsFromMould這個方法的。。測試套件也是在這一步處理的
53     elif os.path.isdir(full_path):  #dicover裏面傳腳本目錄是走這裏。。
54         if (not namespace and    #namespace-默認是false   not namespace就是true  
55             not os.path.isfile(os.path.join(full_path, '__init__.py'))): #不是一個包。。-也就是說我們傳的目錄應該是一個包,下面包含__init__.py
56             return None, False
57         
58         load_tests = None  #這個load_tests是啥意思呢??????????後面繼續看----看了一遍--並且用unittest.main()試了一下-模塊下面是沒有這個屬性的。。只是unittest的初始化文件有這個方法--他也是通過discover找的。。
59         tests = None
60         name = self._get_name_from_path(full_path)   #這裏就是走子目錄的邏輯了
61         #get_name_from_path的邏輯在這裏就很清晰了 
62             #A.如果通過_find_test 調用self._get_name_from_path  是為了判斷兩次start_dir是否一致一致返回.  不一致返回從上次的start_dir1找到本次start_dir12的相對路徑--
63             #這裏又分兩種情況-A1正常情況-start_dir1是start_dir12的上級目錄。。。那麼返回的那麼就是-A.B這樣的了。。因為第一次的A是已經os.path.insert到環境變量了..所以A.B是可以直接用
64                                 #A2不正常情況 就是之前說的 最少返回一個點的...A這種返回---然後問題來了--他為什麼要這麼處理呢--原因是?????????
65                                 #我猜是與腳本同級存在另一個腳本目錄。。。後面驗證這一點。-----這裡在上面補充了--是因為子目錄中還存在腳本所有這麼走邏輯-完美的
66         try:
67             package = self._get_module_from_name(name)   #上面是導入的一個module--這裡是導入一個包-- 返回--
68         except case.SkipTest as e:
69             return _make_skipped_test(name, e, self.suiteClass), False
70         except:
71             error_case, error_message = \
72                 _make_failed_import_test(name, self.suiteClass)
73             self.errors.append(error_message)
74             return error_case, False
75         else:
76             load_tests = getattr(package, 'load_tests', None)  #然後判斷這個包裏面有沒有'load_tests'這個屬性---這裏我代碼一直看下來,我們是不知道這個lood_tests是什麼的,字面意思 加載測試集合
77             # Mark this package as being in load_tests (possibly ;))
78             self._loading_packages.add(name)  #然後把模塊名稱添加到set集合
79             try:
80                 tests = self.loadTestsFromModule(package, pattern=pattern)   
81                 #這裏傳了一個package模塊對象,和文件匹配規則合作或者文件。。--但是這裏導入一個包之後實際上是找不到testCase的-因為包下面的屬性肯定不是一個類-不會走loadTestsFromTestCase
82                 #所以這裏返回的tests是一個空列表
83                 if load_tests is not None: #貌似這個是棄用的,向後兼容-暫時沒看明白這個load_tests代表的意思
84                     # loadTestsFromModule(package) has loaded tests for us.
85                     return tests, False
86                 return tests, True  #  如果能走到這裏------就返回True_就是給_find_test判斷走遞歸的--_find_tests裏面就得到 should_recurse=True
87             finally:
88                 self._loading_packages.discard(name)  #然後這個刪掉set集合裏面之前導入的那個包
89         else:
90         return None, False

View Code

 1 def loadTestsFromModule(self, module, *args, pattern=None, **kws):
 2     """Return a suite of all test cases contained in the given module"""
 3     # This method used to take an undocumented and unofficial
 4     # use_load_tests argument.  For backward compatibility, we still
 5     # accept the argument (which can also be the first position) but we
 6     # ignore it and issue a deprecation warning if it's present.
 7     if len(args) > 0 or 'use_load_tests' in kws:    #args這個默認是一個空元組  長度默認為0  kws是一個空字典
 8         warnings.warn('use_load_tests is deprecated and ignored',
 9                       DeprecationWarning)
10         kws.pop('use_load_tests', None)
11     if len(args) > 1:
12         # Complain about the number of arguments, but don't forget the
13         # required `module` argument.
14         complaint = len(args) + 1
15         raise TypeError('loadTestsFromModule() takes 1 positional argument but {} were given'.format(complaint))
16     if len(kws) != 0:
17         # Since the keyword arguments are unsorted (see PEP 468), just
18         # pick the alphabetically sorted first argument to complain about,
19         # if multiple were given.  At least the error message will be
20         # predictable.
21         complaint = sorted(kws)[0]  #取出第一個Key-
22         raise TypeError("loadTestsFromModule() got an unexpected keyword argument '{}'".format(complaint))
23     tests = []
24     for name in dir(module):    #這裏得modul實際上使我們傳入的模塊對象---dir(object)  返回模塊下的所有屬性列表-
25         obj = getattr(module, name)   #然後反射返回name對象。。返回的是一個class對象
26         if isinstance(obj, type) and issubclass(obj, case.TestCase):    #這裏判斷obj是否是一個類--並且這個類是case.TestCase的子類,也就是說 是否寫在我們繼承unitest.testCase那個類的下面
27             tests.append(self.loadTestsFromTestCase(obj))  #可以看到最後走loadTestFromTestCase obj這裡是傳入的一個類名
28 
29     load_tests = getattr(module, 'load_tests', None)
30     tests = self.suiteClass(tests)
31     if load_tests is not None:
32         try:
33             return load_tests(self, tests, pattern)
34         except Exception as e:
35             error_case, error_message = _make_failed_load_tests(
36                 module.__name__, e, self.suiteClass)
37             self.errors.append(error_message)
38             return error_case
39     return tests  #返回集合

View Code loadTestsFromTestCase:這裏就是添加testcase到suit集合裏面的主要邏輯

 1 def loadTestsFromTestCase(self, testCaseClass):  #testCaseClass是我們傳的一個用例類
 2     """Return a suite of all test cases contained in testCaseClass"""
 3     if issubclass(testCaseClass, suite.TestSuite):  #這個類是不是suite.TestSuite的子類--如果是的就拋出異常==
 4         raise TypeError("Test cases should not be derived from "
 5                         "TestSuite. Maybe you meant to derive from "
 6                         "TestCase?")
 7     testCaseNames = self.getTestCaseNames(testCaseClass)  #getTestCaseNames  從類下面找到用例名稱--找到名稱返回的是一個列表
 8     if not testCaseNames and hasattr(testCaseClass, 'runTest'):  #這裏判斷testCaseNames是否為空-並且 是否存在"runTest"這個元素
 9         testCaseNames = ['runTest']
10     loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames)) #是我的一個用例類--然後將testCaseNames類下面的測試方法--帶進去遍歷。。。高級用法---第一次見--這個方法很關鍵
11     return loaded_suite

View Code getTestCaseNames

 1 def getTestCaseNames(self, testCaseClass):
 2     """Return a sorted sequence of method names found within testCaseClass
 3     """
 4     def isTestMethod(attrname, testCaseClass=testCaseClass,   #定義一個內部方法
 5                      prefix=self.testMethodPrefix):     #self.testMethodPrefix="test"   這個在TestLoader下第一行就已經默認了,他是我們用例開頭的固定格式
 6         return attrname.startswith(prefix) and \  #這裏判斷了是否已test開頭以及 方法對象是否可用----getattr返回方法對象--callable()是檢測對象是否可用    返回一個布爾值
 7             callable(getattr(testCaseClass, attrname))
 8     testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
 9      #dir(testCaseClass)返回該對象下面所有的屬性--包括變量test_1--所以上面需要檢測屬性時test開頭且是一個可以調用的對象--
10      #filter函數---前面是一個function -後面是一個可迭代對象-會遍歷可迭代對象-並傳入function-functions返回true則添加到列表--這樣就找到了所有的
11     if self.sortTestMethodsUsing:
12         testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))     
13         #sortTestMethodsUsing = staticmethod(util.three_way_cmp) 轉為為靜態方法-內存地址指向self.sortTestMethodsUsing
14         #然後通過functools這個模塊排序==
15     return testFnNames  #然後返回用例方法名稱列表

View Code 所有的邏輯 就是在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下面的邏輯了 本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

您可能也會喜歡…