TestLoader source code analysis

 1 def loadTestsFromTestCase(self, testCaseClass) #Look at name analysis: from TestCase Find test set--So, it's our def Use cases loaded into testSuit inside
 2 def loadTestsFromModule(self, module, *args, pattern=None, **kws) #Look at the name analysis: find the test set from the module, then the module>class>test_method>Add to testSuit inside
 3 def loadTestsFromName(self, name, module=None)  #See name analysis: received name Add directly to testSuit inside
 4 def loadTestsFromNames(self, names, module=None) #Look at name analysis: an inclusion test was received test_List of methods
 5 def getTestCaseNames(self, testCaseClass) # Look at name analysis: take out an include test_List of methods
 6 def discover(self, start_dir, pattern='test*.py', top_level_dir=None) ## Look at name analysis: find -- find test_ method
 7 def _get_directory_containing_module(self, module_name) #Get the modules contained in the directory
 8 def _get_name_from_path(self, path)  #Find name from path
 9 def _get_module_from_name(self, name)  #Find module from name
10 def _match_path(self, path, full_path, pattern)   #Regular matching path--Parameter contains pattern That probably matches the format of our test script
11 def _find_tests(self, start_dir, pattern, namespace=False)  #Find test set
12 def _find_test_path(self, full_path, pattern, namespace=False)  #Find the path to the test collection
View Code
 1 2It's 1234
 2 a discover, gettest_ match_ path
 3 two find
 4 three_ get
 5 four loadTests
 6 
 7 discover logic
 8 >
 9. find_ Tests [two processing logics: one is that the directory to be transmitted this time is the same or different from the directory to be transmitted last time
 10 [same: continue to find testcase directly from the directory we passed]
11 [different: it will be executed from the directory we passed os.path.listdir Find all the sub file list paths (files)), and then traverse to get a separate path to start_dir+path splicing]
12 >
13 ①—get_name_from_path [input start_dir, judge whether the current incoming directory returns "." for the last incoming top-level directory. It may be a bit different - and return a value. There are four cases. Test... Test dir.tests --Normally, it should return the test filename
14 ②_find_test_path(self, full_path, pattern, namespace=False)
15 [run this to find test from the path, so it is obviously the same: the path to the directory is not the same as the path to the file]
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()  #This creates an empty self._loading_packages={}An unordered and unrepeatable set of elements
View Code
1.discover method: unittest.defaultTestLoader
a. Three Boolean attributes are defined_ not_ If importable = = true, it cannot be imported, is_namespace ,set_implicit_top
b. The top-level directory is processed -- when the service starts execution for the first time unittest.defaultTestLoader.discover (file directory a, pattern, top_ level_ dir=None):self._ top_ level_ dir = top_ level_ dir = start_ Dirthese three are equal.
Execute again unittest.defaultTestLoader.discover (file directory B, pattern, top_ level_ dir=None): top_ level_ dir=self._ top_ level_ Dir [that is, he will default to the last start_dir is the top directory]--
Whether it's the first time or the second time -- self_ top_ level_ dir = top_ level_ Dir continues with this - that is, self_ top_ level_ dir == top_ level_ Dir is always the same
c. A series of processing has been done for the top-level directory that is not a directory file. If the directory you pass is an importable module - it will re import this module in this exception processing. And start to track his absolute path, judge the availability of its modules, and then execute_ find_tests() looking for use cases
d. If it's a directory, start directly_ find_tests() looking for use cases
e. So he can find the use case in two cases: the first is the passed directory; the second is the imported module - this case is self_ top_ level_ Dir is ultimately an absolute path
  1 def discover(self, start_dir, pattern='test*.py', top_level_dir=None):  #Generally we top_level_dir The capital of biography None
  2     set_implicit_top = False   #Whether top level directory exists
  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  #Go here again
  6     elif top_level_dir is None:  #First time here
  7         set_implicit_top = True
  8         top_level_dir = start_dir
  9     #The above list of fancy things is to deal with the top-level directory-If it is the first time to start the service-
 10     #Just go elif-top_level_dir==Our next value--after--self._top_level_dir It's not empty,
 11     #however top_level_dir  At the top is the treatment==None So I will go if=True
 12     top_level_dir = os.path.abspath(top_level_dir)#To absolute path
 13     if not top_level_dir in sys.path:
 14         #This is to prevent repetition top_level_dir Add to execution directory--BUT If I did it for the first time start_dir=a,Second pass start_dir=b
 15         #Analyze it--The first time is a Added to execution directory---below self._top_level_dir=a secondary(repeat over and over in detail)pass b When,Will appear top_level_dir=a---No judgment b Whether to execute directory
 16         #Add a question mark here?????????????????????
 17         #But in general, we only have one catalog--So here--Put it first... First look at the back, then here
 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     #If top_level_dir The directory we passed is not in the executable directory--Add it temporarily
 25     is_not_importable = False  #Can't import
 26     is_namespace = False    #is_namespace So this field means whether the incoming path can be found
 27     tests = []
 28     if os.path.isdir(os.path.abspath(start_dir)):  #Determine whether the directory we passed is a directory--Actually, it's directly used here top_level_dir Is it not fragrant--
 29         start_dir = os.path.abspath(start_dir)
 30         # Before top_level_dir = start_dir
 31         # then top_level_dir = os.path.abspath(top_level_dir)
 32         #Now? start_dir = os.path.abspath(start_dir)
 33         #Why not use it directly top_level_dir?  Do you have many question marks, children
 34         # That's the problem--Again--Didn't go top_level_dir = start_dir It's walking top_level_dir = self._top_level_dir ,
 35         #So if our last route is different from this one--that top_level_dir Is not equal to start_dir--and start_dir It's from us--
 36         if start_dir != top_level_dir:  #So this is equivalent to judging whether the path of the front and back is the same--Generally speaking, our start_dir All equal to top_level_dir Of
 37             is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py'))#If it is a file, return false
 38             #If not--Then judge our current incoming start_dir/__init__.py Is it a correct file path..os.path.isfile() Return Boolean
 39     else: #If we don't pass in a directory,I started to make a lot of fancy mistakes.. Not for the moment
 40         # support for discovery from dotted module names
 41         try:
 42             __import__(start_dir)
 43             #It's interesting here---__impor__("PyFiles.Besettest")That's import PyFiles
 44             #So that's 97.33 Probability of error--That is to say, if you have the wrong directory--Below else Basically can't go... Unless you're magically filling in a path with an importable module on the right
 45         except ImportError:
 46             is_not_importable = True   #If the import is not bird--Just is_not_importable Set to true  Here I understand the meaning of this field--Cannot import=true
 47         else:#So here we assume that after the import is successful
 48             the_module = sys.modules[start_dir]  #Here is if we import successfully--Let's go-take out start_dir Imported assignment to the_module
 49             top_part = start_dir.split('.')[0]    #Here is the name of the module we imported
 50             try:
 51                 start_dir = os.path.abspath( #Print the absolute path of the directory where the import module is located
 52                    os.path.dirname((the_module.__file__)))
 53             except AttributeError:     #Here is if the import module succeeds---But the absolute path of NIMA print import module is wrong again--I don't want to see it+2
 54                 # look for namespace packages
 55                 try:  #Then start the module import check---what the fuck.....
 56                     #  fuck----I want to shut it down+1,I guess I want to find out why I can't import.. The idea of the great God is perfection. If it's me, I'll throw out a wrong directory and it's over
 57                     #Learning document of this part Python Standard module--import
 58                     spec = the_module.__spec__
 59                     #Assign the specification of the imported module to spec--
 60                     #Print it out 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                     #Such a string of things--Never used.... Probably the module name, path and imported module object
 62                     #origin Location of loading module--
 63                     #loader_state Container for module specific data
 64                 except AttributeError:  #If the specification of the module cannot be taken out.......
 65                     spec = None   #I checked... There are some module specifications None Of--So we have to keep looking down
 66 
 67                 if spec and spec.loader is None:   #If a specification exists and the data container is None. 
 68                     if spec.submodule_search_locations is not None: 
 69                         #What is this--Module search location s(list). . . 
 70                         is_namespace = True    #If spec.submodule_search_locations Not for none -----
 71                         # 2.5 If the module path is not empty is_namespace Can be found---is_namespace So this field means that there is a namespace.. You can find this module
 72                         for path in the_module.__path__:   #Here, I study the suspicion that it is intended to enhance the force.. the_module.__path__==spec.submodule_search_locations
 73                             if (not set_implicit_top and     #first set_implicit_top==True  repeat over and over in detail set_implicit_top==Fase
 74                                 not path.startswith(top_level_dir)):
 75                                 continue
 76                                 #I'm a little confused here.. Why to judge whether it is the first time or the second time--I guess it's judgment the_module.__path__There are several paths of some block in the list
 77                                 #If it's the first time, go straight to the next step--If it is a repeat, there will be multiple paths. But if it's a repeat top_level_dir This path is the last time... day
 78                                 #Its function is to find the path of the import module-Just know this...
 79                             self._top_level_dir = \
 80                                 (path.split(the_module.__name__
 81                                      .replace(".", os.path.sep))[0])   #Take the absolute path of the parent directory of the import module...
 82                             #the_module.__name__.replace(".", os.path.sep) This string seems unnecessary to me.. because  the_module.__name__Now that you get the module name, it must be a string
 83                             tests.extend(self._find_tests(path,     #Then call_find_tests Look for tests. join tests list--It's a little familiar---
 84                                                           pattern,  #I don't think I'm going to go here---The script path is usually filled in correctly and wrongly, and I don't know where it has been executed...
 85                                                           namespace=True))
 86                 elif the_module.__name__ in sys.builtin_module_names:
 87                     #judge sys.builtin_module_names Returns a list of all compiled to Python The name of the module in the interpreter and sys.models It's a dictionary
 88                     #Just can't import the error report
 89                     # builtin module
 90                     raise TypeError('Can not use builtin modules '
 91                                     'as dotted module names') from None
 92                 else:  #I didn't find this module
 93                     raise TypeError(
 94                         'don\'t know how to discover from {!r}'
 95                         .format(the_module)) from None
 96 
 97             if set_implicit_top:     #If it's the first time....
 98                 if not is_namespace:  #is_namespace By default false-The module specifications can be found
 99                     self._top_level_dir = \
100                        self._get_directory_containing_module(top_part)  #interface.testFiles   interface Suppose this is imported -self._top_level_dir Is the absolute path to a directory
101                     #top_part Imported module name-----
102                     sys.path.remove(top_level_dir)    #Just remove it from the system path--But I don't know why to remove....
103                 else:
104                     sys.path.remove(top_level_dir)   #
105 
106     if is_not_importable:   #If the file we passed cannot be imported---Just throw an exception
107         raise ImportError('Start directory is not importable: %r' % start_dir)
108 
109     if not is_namespace:  #is_namespace By default false--Here is the module that can be found....
110         tests = list(self._find_tests(start_dir, pattern))
111     return self.suiteClass(tests)
View Code
2._find_tests() -- find testCase and generate test suite tests = []
 1 def _find_tests(self, start_dir, pattern, namespace=False):   #Note here that if we are passing a module that can be imported instead of a script directory namespace Is equal to True Of
 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)   #Return to a name   name There are three return situations  "."-When the current and last incoming start_dir agreement     atypism  "file name"    "...file name"
 5      #get_name_from_path The logic is clear here 
 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     #When name At least one and no more self._loading_packages.[self._loading_packages Empty set created during initialization] go to the following
11         # name is in self._loading_packages while we have called into
12         # loadTestsFromModule with name.
13         tests, should_recurse = self._find_test_path(      #And here start_dir It's our module--He'll find it.. The logic of transferring test directory is restored here
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))  #Then start here--When we wear the same catalog as last time-He will find all the files in the directory and sort them--This is where our use case execution sequence begins..
23     for path in paths:       #Traverse all files in the directory we passed
24         full_path = os.path.join(start_dir, path)     The directory we passed in and the py Full path of file splicing
25         tests, should_recurse = self._find_test_path(    #Pass in our file path and our file format_find_test_path This method--
26             full_path, pattern, namespace)
27         If the current one is a directory-Will return should_recurse=True--This literal English translation should_recursion--below yield from Is to perform recursive operations
28         if tests is not None:
29             yield tests
30         if should_recurse:  #This sentence is to judge whether he is a catalogue
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 and yield from
 1 def  a(n):
 2     testList=b(n)
 3     return testList
 4 
 5 def  b(n,m=1):
 6     print("Execute the%s second"%m)
 7     for a in range(n):
 8         if not divmod(a,2)[1] and a!=0:
 9             print(a)
10             yield a   #Yes yield Then a generator is returned
11             if divmod(a,3)[1]:
12                 m =m+1
13                 yield from b(a,m)  #Reexecution b method
14 
15 print(list(a(7)))
16 
17 1st execution
18 2
19 2nd execution
20 4
21 3rd execution
22 2
23 4th execution
24 6
25 [2, 4, 2, 6]
View Code
_get_name_from_path
He mainly did: find the script file name from the path we passed... If the script file is found, the file name will be returned - if not, we will return a point or at least one point (three cases. test_case .....test_case returns three possible values of name)
At_ test_find calls this method path = start_ Dir (our directory) - this returns a point or at least one point
At_ fin_test_path calls the file intersection under the directory we passed -- the returned name is the file name
 1 name = self._get_name_from_path(start_dir)    #because discover We support the search of directory or module testcase Yes, so this method
 2 
 3 def _get_name_from_path(self, path):
 4      #For example, our script directory structure is  E://Under the A / B / b directory script.py     And / c/script.py
 5      #For the first time, it was our own catalog--Before discover One of the things he did was assign the directory we passed to the top-level directory at the first run---
 6      #The first logic is to judge whether we're passing on or not  -Same as the top floor---The same thing===_find_tests Method to find the script directly from the directory-When there is a directory, I will continue to walk--He was in_find_tests_path Judgmental--Finally, it's back to the script module
 7      #If not--That's to find the directory---Find the relative path of this directory from the top--It's actually looking for the last directory(Must be a packge. All the directories mentioned above are packages),. . Then return to a name
 8      #If there are subdirectories  d---Then it will return  c.d
 9     if path == self._top_level_dir:  #First run pattern,top_level_dir=None):self._top_level_dir = top_level_dir = start_dir -If the directory does not change during the second run, it will be returned directly here
10         return '.'
11         
12         
13     path = _jython_aware_splitext(os.path.normpath(path))  #If the directory that we passed is not the same as the directory that we passed last time.. Here we have to path The path of our Prequel
14     
15     _relpath = os.path.relpath(path, self._top_level_dir)    #from self._top_level_dir Start looking path Relative path of
16     #This is from us path Start to find self._top_level_dir Relative path of last transfer 
17     #For example: path=path1="E:\\PyFiles\\Besettest\\besettest\\interface\\testFiles"   self._top_level_dir="E:\\PyFiles\\Besettest\\besettest\\interface\\result"
18     #that_relpath="..\testFiles"    -It's not clear why we should look for this??????????????????????????
19     assert not os.path.isabs(_relpath), "Path must be within the project"
20     #↑↑Assertion is not an absolute path-in other words_relpath Relative path or not↑↑↑It must be a relative path here... It's all on it relpath Yes... lose
21     #↓↓↓↓Assert with..Fail at the beginning...↓↓↓--These two are beyond understanding......
22     assert not _relpath.startswith('..'), "Path must be within the project"
23     
24     name = _relpath.replace(os.path.sep, '.')  #And here we replace the separator with.  return a.b  Of course, there may be some exceptions.....This is what I mean
25     return name
View Code
self._find_test_path - find the path of the test
Two main logic: pass to full_ Is path a file or a directory
 1 def _find_test_path(self, full_path, pattern, namespace=False):  
 2  #_find_tests()Call this method and pass it to the a File path,, and files to find pattern-namespace[It could be true It could be false],If my catalog is right-namespace It's about 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==file name.py Follow up belt py Collectively referred to as documents--Generic file name without suffix...
11     if os.path.isfile(full_path):   #If we do full_path It's a file---We are discover It's a script directory-Before_test_find It's a complete path from splicing full_path
12         if not VALID_MODULE_NAME.match(basename): #Judge whether he is one py file----
13             
14             # valid Python identifiers only
15             return None, False   #If not directly back
16         if not self._match_path(basename, full_path, pattern):  #Three values are passed here--But in fact, only basename,pattern Useful--
17          #_match_path call fnmatch(file, The file format or document we transmit)This need———— from fnmatch import fnmatch The main function of this module is to match the file name
18          #When the file name passed in this time matches our file format self._match_path return true
19             return None, False    #If not, go straight back _find_test Keep looking
20         # if the test file matches, load it
21         name = self._get_name_from_path(full_path)   #And then I'll pass the file path here self._get_name_from_path To return the file name-At this time, because we passed the script directory-full_path Directory, so the name Is the file name
22        
23         #self._top_level_dir Is the current file directory path, path Is the current file path--Find files from directory--Directly the file name--He returned name Is the file name   
24         try:
25             module = self._get_module_from_name(name)    #_get_module_from_name  This method is to dynamically import the module name--Then return an object of all imported modules moudel.__file__Path moudel.__name__name
26         except case.SkipTest as e:   #If the import is not successful case.SkipTest actually case It's inheritance--Exception--So take this as Exception That's it--
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 Get the value and go here..
34             mod_file = os.path.abspath(
35                 getattr(module, '__file__', full_path))    #And then take out the absolute path of our imported module here---Returns the path to the file if reflection is not found-It's not much different-Deal with it more rigorously
36             realpath = _jython_aware_splitext(
37                 os.path.realpath(mod_file))   #os.path.realpath(mod_file)And then back to the real path---And then we get rid of the path.py,. ,,,,,,,,lose
38             fullpath_noext = _jython_aware_splitext(
39                 os.path.realpath(full_path))   #then full_path Find the real path to remove.py
40             if realpath.lower() != fullpath_noext.lower():  #If the directory path of the dynamically imported module is not equal to the path passed in(that is pattern)Directory path for--The path actually uploaded must be an absolute path--Because it's been several times since the absolute path
41                 module_dir = os.path.dirname(realpath)   #If not, find the directory where the dynamic import module is located----Actually, it's handled above realpath It's already a directory.. But he prevented realpath Or a.py Documents. So I did it again
42                 mod_name = _jython_aware_splitext(    #full_path Is the path to the file.py Yes, and then here it is basename Take out the file(Just take the path out and leave it xxx.py) And then the way out there.py Remove--Leave a file name
43                     os.path.basename(full_path))
44                 expected_dir = os.path.dirname(full_path)  
45                 #Then find the directory where the script needs to be executed..... That is to say, normal situation assumption expected_dir="e://A / b "then mod_file =realpathfullpath_noext="e://a/b/scripy" 
46                 #scripy It's a py file---Above this if That's normal... I can't think of the unequal paths between the import module and the import module--But it doesn't matter-The source code must be reasonable
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             #Then walk from module to load test s This method---in other words discover It's actually a call loadTestsFromMould This method.. The test suite is also handled in this step
53     elif os.path.isdir(full_path):  #dicover The script directory is here..
54         if (not namespace and    #namespace-The default is false   not namespace namely true  
55             not os.path.isfile(os.path.join(full_path, '__init__.py'))): #It's not a bag..-That is to say, the directory we passed should be a package, which contains__init__.py
56             return None, False
57         
58         load_tests = None  #this load_tests What do you mean?????????? Keep looking back----Have a look--And use unittest.main()Try it-There is no such attribute under the module.. just unittest The initialization file of has this method--He also passed discover Yes..
59         tests = None
60         name = self._get_name_from_path(full_path)   #This is the logic of subdirectories
61         #get_name_from_path The logic is clear here 
62             #A.If passed_find_test call self._get_name_from_path  To judge twice start_dir Consistent return.  Inconsistent return from last start_dir1 Find this time start_dir12 Relative path of--
63             #There are two situations here-A1 Normal condition-start_dir1 yes start_dir12 The parent directory of... So what comes back is-A.B That's it.. Because for the first time A Is already os.path.insert It's the environment variable..therefore A.B It can be used directly
64                                 #A2 The abnormal situation is to return at least one point...A This return---And then the question came--Why did he do this--as a result of?????????
65                                 #I guess there is another script directory at the same level as the script... Verify this later.-----Here we add--Because there are scripts in the subdirectory. All this logic-perfect
66         try:
67             package = self._get_module_from_name(name)   #Above is the imported one module--Here is importing a package-- return--
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)  #Then judge if there is any in this bag'load_tests'This property---I've been reading the code here. We don't know this lood_tests What is it, literally loading a test set
77             # Mark this package as being in load_tests (possibly ;))
78             self._loading_packages.add(name)  #Then add the module name to the set aggregate
79             try:
80                 tests = self.loadTestsFromModule(package, pattern=pattern)   
81                 #Here's a message package Module object,Cooperate with file matching rule or file..--But after importing a package here, we can't find it testCase Of-Because the properties under the package must not be a class-Not going loadTestsFromTestCase
82                 #So back here tests Is an empty list
83                 if load_tests is not None: #It seems that this one is abandoned and backward compatible-I haven't understood this yet load_tests Meaning of representative
84                     # loadTestsFromModule(package) has loaded tests for us.
85                     return tests, False
86                 return tests, True  #  If we can get here------Just go back True_It's for_find_test Judge recursive--_find_tests You get it in there should_recurse=True
87             finally:
88                 self._loading_packages.discard(name)  #And then delete this one set The previously imported package in the collection
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 The default is an empty tuple, and the default length is 0  kws It's an empty dictionary
 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]  #Take out the first one Key-
22         raise TypeError("loadTestsFromModule() got an unexpected keyword argument '{}'".format(complaint))
23     tests = []
24     for name in dir(module):    #Here we have to modul The module object that we actually passed in---dir(object)  Returns a list of all properties under the module-
25         obj = getattr(module, name)   #Then the reflection returns name Object.. Back to a class object
26         if isinstance(obj, type) and issubclass(obj, case.TestCase):    #Judge here obj Is it a class--And this class is case.TestCase That is, whether it is written in our inheritance unitest.testCase Under that category
27             tests.append(self.loadTestsFromTestCase(obj))  #You can see the last step loadTestFromTestCase obj Here is a class name passed in
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  #Return to collection
View Code
loadTestsFromTestCase: here is the main logic of adding testcase to the suit e
 1 def loadTestsFromTestCase(self, testCaseClass):  #testCaseClass It's a use case class we passed
 2     """Return a suite of all test cases contained in testCaseClass"""
 3     if issubclass(testCaseClass, suite.TestSuite):  #Is this class suite.TestSuite Subclass of--Throw an exception if it is==
 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  Find the use case name under the class--Name found returns a list
 8     if not testCaseNames and hasattr(testCaseClass, 'runTest'):  #Judge here testCaseNames Empty or not-And whether it exists"runTest"This element
 9         testCaseNames = ['runTest']
10     loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames)) #Is one of my use case classes--Then the testCaseNames Test method under class--Take it in and traverse... Advanced Usage ---See you for the first time--This is the key
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,   #Define an internal method
 5                      prefix=self.testMethodPrefix):     #self.testMethodPrefix="test"   This is in TestLoader The next line is already default. It is the fixed format at the beginning of our use case
 6         return attrname.startswith(prefix) and \  #Here we judge whether it has test Start and method objects available----getattr Return method object--callable()Is to check whether the object is available    Returns a Boolean value
 7             callable(getattr(testCaseClass, attrname))
 8     testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
 9      #dir(testCaseClass)Returns all the properties below the object--Include variables test_1--So when the above needs to detect properties test Starts with a callable object--
10      #filter function---There's a function -Followed by an iterative object-Iteratable objects will be traversed-And pass in function-functions return true Add to list--That's how we found everything
11     if self.sortTestMethodsUsing:
12         testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))     
13         #sortTestMethodsUsing = staticmethod(util.three_way_cmp) Turn to static method-Memory address points to self.sortTestMethodsUsing
14         #And then through functools Sorting of this module==
15     return testFnNames  #Then return to the list of use case method names
View Code
All logic
When TestLoader looks for test cases -- pass_ find_tests this method starts from the directory to find the file (subdirectory) - module - class - method name
Then the class under a certain module will return multiple objects through its method map, that is to say, there are five tests under a testClass_ Method, he will return five instance objects - and generate a suite Collection - and add them to a list
If there are more than one testclass under a module, it is the same - in fact, it is the same - in fact, it first finds all the class objects through the loadTestsFromModule method and then traverses them - then it goes to the above step, multiple testclasses have multiple suite sets--
In other words, the suite collection under a module will be added to a list -- [suite=[A-TestCase instantiation object 1,A-TestCase instantiation object 2], suite=[B-TestCase instantiation object 1, B-TestCase instantiation object 2]] --- and then pass this list as a parameter to TestSuite to instantiate a new object [suite=-[suite=[A-TestCase instantiation object 1,A-TestCase instantiation object 2], suite=[B-TestCase instantiation object 1, B-TestCase instantiation object 2]]] - this is the structure used under a module
But it's not over yet -- it's just a module here -- there are multiple modules here -- and you probably know what the rest will do---
That's right -- when I get all suite sets of modul -- this set will eventually return to the_ find_tests method -- return to discover through generator -- that is, add the suite collection to a new list -- and discover will add the list again
It forms a final instance object. The format of the final return is as follows-------
[suite=
[suite1=-[suite=[A-TestCase instantiation object 1,A-TestCase instantiation object 2], suite=[B-TestCase instantiation object 1, B-TestCase instantiation object 2]],
[suite2=-[suite=[A-TestCase instantiation object 1,A-TestCase instantiation object 2], suite=[B-TestCase instantiation object 1, B-TestCase instantiation object 2]]]
]
 
--It's known from the source code that when we run, the instance object can accept parameters, and this parameter is result, because TestSuite inherits basetestsssuite, which has one__ call__ this Magic method: if implemented in class__ call__ Method, then the instance object will also become a callable object, specifically Baidu. No more explanation here - so the final suite is a test(result) that can accept parameters - go directly after accepting parameters - the logic below the call

Tags: Python Attribute REST

Posted on Sat, 20 Jun 2020 23:24:34 -0400 by vinpkl