欢迎来到DIVCSS5查找CSS资料与学习DIV CSS布局技术!
  时隔已久,再次冒烟,自动化测试工作仍在继续,自动化测试中的数据驱动技术尤为重要,不然咋去实现数据分离呢,对吧,这里就简单介绍下与传统unittest自动化测试框架匹配的DDT数据驱动技术。
 
  话不多说,先撸一波源码,其实整体代码并不多
 
  #-*-coding:utf-8-*-
 
  #ThisfileisapartofDDT(https://github.com/txels/ddt)
 
  #Copyright2012-2015CarlesBarrobésandDDTcontributors
 
  #Fortheexactcontributionhistory,seethegitrevisionlog.
 
  #DDTislicensedundertheMITLicense,includedin
 
  #https://github.com/txels/ddt/blob/master/LICENSE.md
 
  importinspect
 
  importjson
 
  importos
 
  importre
 
  importcodecs
 
  fromfunctoolsimportwraps
 
  try:
 
  importyaml
 
  exceptImportError:#pragma:nocover
 
  _have_yaml=False
 
  else:
 
  _have_yaml=True
 
  __version__='1.2.1'
 
  #Theseattributeswillnotconflictwithanyrealpythonattribute
 
  #Theyareaddedtothedecoratedtestmethodandprocessedlater
 
  #bythe`ddt`classdecorator.
 
  DATA_ATTR='%values'#storethedatathetestmustrunwith
 
  FILE_ATTR='%file_path'#storethepathtoJSONfile
 
  UNPACK_ATTR='%unpack'#rememberthatwehavetounpackvalues
 
  index_len=5#defaultmaxlengthofcaseindex
 
  try:
 
  trivial_types=(type(None),bool,int,float,basestring)
 
  exceptNameError:
 
  trivial_types=(type(None),bool,int,float,str)
 
  defis_trivial(value):
 
  ifisinstance(value,trivial_types):
 
  returnTrue
 
  elifisinstance(value,(list,tuple)):
 
  returnall(map(is_trivial,value))
 
  returnFalse
 
  defunpack(func):
 
  """
 
  Methoddecoratortoaddunpackfeature.
 
  """
 
  setattr(func,UNPACK_ATTR,True)
 
  returnfunc
 
  defdata(*values):
 
  """
 
  Methoddecoratortoaddtoyourtestmethods.
 
  Shouldbeaddedtomethodsofinstancesof``unittest.TestCase``.
 
  """
 
  globalindex_len
 
  index_len=len(str(len(values)))
 
  returnidata(values)
 
  defidata(iterable):
 
  """
 
  Methoddecoratortoaddtoyourtestmethods.
 
  Shouldbeaddedtomethodsofinstancesof``unittest.TestCase``.
 
  """
 
  defwrapper(func):
 
  setattr(func,DATA_ATTR,iterable)
 
  returnfunc
 
  returnwrapper
 
  deffile_data(value):
 
  """
 
  Methoddecoratortoaddtoyourtestmethods.
 
  Shouldbeaddedtomethodsofinstancesof``unittest.TestCase``.
 
  ``value``shouldbeapathrelativetothedirectoryofthefile
 
  containingthedecorated``unittest.TestCase``.Thefile
 
  shouldcontainJSONencodeddata,thatcaneitherbealistora
 
  dict.
 
  Incaseofalist,eachvalueinthelistwillcorrespondtoone
 
  testcase,andthevaluewillbeconcatenatedtothetestmethod
 
  name.
 
  Incaseofadict,keyswillbeusedassuffixestothenameofthe
 
  testcase,andvalueswillbefedastestdata.
 
  """
 
  defwrapper(func):
 
  setattr(func,FILE_ATTR,value)
 
  returnfunc
 
  returnwrapper
 
  defmk_test_name(name,value,index=0):
 
  """
 
  Generateanewnameforatestcase.
 
  Itwilltaketheoriginaltestnameandappendanordinalindexanda
 
  stringrepresentationofthevalue,andconverttheresultintoavalid
 
  pythonidentifierbyreplacingextraneouscharacterswith``_``.
 
  Weavoiddoingstr(value)ifdealingwithnon-trivialvalues.
 
  Theproblemispossibledifferentnameswithdifferentruns,e.g.
 
  differentorderofdictionarykeys(seePYTHONHASHSEED)ordealing
 
  withmockobjects.
 
  Trivialscalarvaluesarepassedasis.
 
  A"trivial"valueisaplainscalar,oratupleorlistconsisting
 
  onlyoftrivialvalues.
 
  """
 
  #Addzerosbeforeindextokeeporder
 
  index="{0:0{1}}".format(index+1,index_len)
 
  ifnotis_trivial(value):
 
  return"{0}_{1}".format(name,index)
 
  try:
 
  value=str(value)
 
  exceptUnicodeEncodeError:
 
  #fallbackforpython2
 
  value=value.encode('ascii','backslashreplace')
 
  test_name="{0}_{1}_{2}".format(name,index,value)
 
  returnre.sub(r'\W|^(?=\d)','_',test_name)
 
  deffeed_data(func,new_name,test_data_docstring,*args,**kwargs):
 
  """
 
  Thisinternalmethoddecoratorfeedsthetestdataitemtothetest.
 
  """
 
  @wraps(func)
 
  defwrapper(self):
 
  returnfunc(self,*args,**kwargs)
 
  wrapper.__name__=new_name
 
  wrapper.__wrapped__=func
 
  #setdocstringifexists
 
  iftest_data_docstringisnotNone:
 
  wrapper.__doc__=test_data_docstring
 
  else:
 
  #Trytocallformatonthedocstring
 
  iffunc.__doc__:
 
  try:
 
  wrapper.__doc__=func.__doc__.format(*args,**kwargs)
 
  except(IndexError,KeyError):
 
  #Maybetheuserhasaddedsomeoftheformatingstrings
 
  #unintentionallyinthedocstring.Donotraiseanexception
 
  #asitcouldbethatuserisnotawareofthe
 
  #formatingfeature.
 
  pass
 
  returnwrapper
 
  defadd_test(cls,test_name,test_docstring,func,*args,**kwargs):
 
  """
 
  Addatestcasetothisclass.
 
  Thetestwillbebasedonanexistingfunctionbutwillgiveitanew
 
  name.
 
  """
 
  setattr(cls,test_name,feed_data(func,test_name,test_docstring,
 
  *args,**kwargs))
 
  defprocess_file_data(cls,name,func,file_attr):
 
  """
 
  Processtheparameterinthe`file_data`decorator.
 
  """
 
  cls_path=os.path.abspath(inspect.getsourcefile(cls))
 
  data_file_path=os.path.join(os.path.dirname(cls_path),file_attr)
 
  defcreate_error_func(message):#pylint:disable-msg=W0613
 
  deffunc(*args):
 
  raiseValueError(message%file_attr)
 
  returnfunc
 
  #Iffiledoesnotexist,provideanerrorfunctioninstead
 
  ifnotos.path.exists(data_file_path):
 
  test_name=mk_test_name(name,"error")
 
  test_docstring="""Error!"""
 
  add_test(cls,test_name,test_docstring,
 
  create_error_func("%sdoesnotexist"),None)
 
  return
 
  _is_yaml_file=data_file_path.endswith((".yml",".yaml"))
 
  #Don'thaveYAMLbutwanttouseYAMLfile.
 
  if_is_yaml_fileandnot_have_yaml:
 
  test_name=mk_test_name(name,"error")
 
  test_docstring="""Error!"""
 
  add_test(
 
  cls,
 
  test_name,
 
  test_docstring,
 
  create_error_func("%sisaYAMLfile,pleaseinstallPyYAML"),
 
  None
 
  )
 
  return
 
  withcodecs.open(data_file_path,'r','utf-8')asf:
 
  #LoadthedatafromYAMLorJSON
 
  if_is_yaml_file:
 
  data=yaml.safe_load(f)
 
  else:
 
  data=json.load(f)
 
  _add_tests_from_data(cls,name,func,data)
 
  def_add_tests_from_data(cls,name,func,data):
 
  """
 
  Addtestsfromdataloadedfromthedatafileintotheclass
 
  """
 
  fori,eleminenumerate(data):
 
  ifisinstance(data,dict):
 
  key,value=elem,data[elem]
 
  test_name=mk_test_name(name,key,i)
 
  elifisinstance(data,list):
 
  value=elem
 
  test_name=mk_test_name(name,value,i)
 
  ifisinstance(value,dict):
 
  add_test(cls,test_name,test_name,func,**value)
 
  else:
 
  add_test(cls,test_name,test_name,func,value)
 
  def_is_primitive(obj):
 
  """Findsoutiftheobjisa"primitive".Itissomewhathackybutitworks.
 
  """
 
  returnnothasattr(obj,'__dict__')
 
  def_get_test_data_docstring(func,value):
 
  """Returnsadocstringbasedonthefollowingresolutionstrategy:
 
  1.Passedvalueisnota"primitive"andhasadocstring,thenuseit.
 
  2.InallothercasesreturnNone,i.ethetestnameisused.
 
  """
 
  ifnot_is_primitive(value)andvalue.__doc__:
 
  returnvalue.__doc__
 
  else:
 
  returnNone
 
  defddt(cls):
 
  """
 
  Classdecoratorforsubclassesof``unittest.TestCase``.
 
  Applythisdecoratortothetestcaseclass,andthen
 
  decoratetestmethodswith``@data``.
 
  Foreachmethoddecoratedwith``@data``,thiswilleffectivelycreateas
 
  manymethodsasdataitemsarepassedasparametersto``@data``.
 
  Thenamesofthetestmethodsfollowthepattern
 
  ``original_test_name_{ordinal}_{data}``.``ordinal``isthepositionofthe
 
  dataargument,startingwith1.
 
  Fordataweuseastringrepresentationofthedatavalueconvertedintoa
 
  validpythonidentifier.If``data.__name__``exists,weusethatinstead.
 
  Foreachmethoddecoratedwith``@file_data('test_data.json')``,the
 
  decoratorwilltrytoloadthetest_data.jsonfilelocatedrelative
 
  tothepythonfilecontainingthemethodthatisdecorated.Itwill,
 
  foreach``test_name``keycreateasmanymethodsinthelistofvalues
 
  fromthe``data``key.
 
  """
 
  forname,funcinlist(cls.__dict__.items()):
 
  ifhasattr(func,DATA_ATTR):
 
  fori,vinenumerate(getattr(func,DATA_ATTR)):
 
  test_name=mk_test_name(name,getattr(v,"__name__",v),i)
 
  test_data_docstring=_get_test_data_docstring(func,v)
 
  ifhasattr(func,UNPACK_ATTR):
 
  ifisinstance(v,tuple)orisinstance(v,list):
 
  add_test(
 
  cls,
 
  test_name,
 
  test_data_docstring,
 
  func,
 
  *v
 
  )
 
  else:
 
  #unpackdictionary
 
  add_test(
 
  cls,
 
  test_name,
 
  test_data_docstring,
 
  func,
 
  **v
 
  )
 
  else:
 
  add_test(cls,test_name,test_data_docstring,func,v)
 
  delattr(cls,name)
 
  elifhasattr(func,FILE_ATTR):
 
  file_attr=getattr(func,FILE_ATTR)
 
  process_file_data(cls,name,func,file_attr)
 
  delattr(cls,name)
 
  returncls
 
  ddt源码
 
  通过源码的说明,基本可以了解个大概了,其核心用法就是利用装饰器来实现功能的复用及扩展延续,以此来实现数据驱动,现在简单介绍下其主要函数的基本使用场景。
 
  1.@ddt(cls),其服务于unittest类装饰器,主要功能是判断该类中是否具有相应ddt装饰的方法,如有则利用自省机制,实现测试用例命名mk_test_name、数据回填_add_tests_from_data并通过add_test添加至unittest的容器TestSuite中去,然后执行得到testResult,流程非常清晰。
 
  defddt(cls):
 
  forname,funcinlist(cls.__dict__.items()):
 
  ifhasattr(func,DATA_ATTR):
 
  fori,vinenumerate(getattr(func,DATA_ATTR)):
 
  test_name=mk_test_name(name,getattr(v,"__name__",v),i)
 
  test_data_docstring=_get_test_data_docstring(func,v)
 
  ifhasattr(func,UNPACK_ATTR):
 
  ifisinstance(v,tuple)orisinstance(v,list):
 
  add_test(
 
  cls,
 
  test_name,
 
  test_data_docstring,
 
  func,
 
  *v
 
  )
 
  else:
 
  #unpackdictionary
 
  add_test(
 
  cls,
 
  test_name,
 
  test_data_docstring,
 
  func,
 
  **v
 
  )
 
  else:
 
  add_test(cls,test_name,test_data_docstring,func,v)
 
  delattr(cls,name)
 
  elifhasattr(func,FILE_ATTR):
 
  file_attr=getattr(func,FILE_ATTR)
 
  process_file_data(cls,name,func,file_attr)
 
  delattr(cls,name)
 
  returncls
 
  2.@file_data(PATH),其主要是通过process_file_data方法实现数据解析,这里通过_add_tests_from_data实现测试数据回填,通过源码可以得知目前文件只支持Yaml和JSON数据文件,想扩展其它文件比如xml等直接改源码就行
 
  defprocess_file_data(cls,name,func,file_attr):
 
  """
 
  Processtheparameterinthe`file_data`decorator.
 
  """
 
  cls_path=os.path.abspath(inspect.getsourcefile(cls))
 
  data_file_path=os.path.join(os.path.dirname(cls_path),file_attr)
 
  defcreate_error_func(message):#pylint:disable-msg=W0613
 
  deffunc(*args):
 
  raiseValueError(message%file_attr)
 
  returnfunc
 
  #Iffiledoesnotexist,provideanerrorfunctioninstead
 
  ifnotos.path.exists(data_file_path):
 
  test_name=mk_test_name(name,"error")
 
  test_docstring="""Error!"""
 
  add_test(cls,test_name,test_docstring,
 
  create_error_func("%sdoesnotexist"),None)
 
  return
 
  _is_yaml_file=data_file_path.endswith((".yml",".yaml"))
 
  #Don'thaveYAMLbutwanttouseYAMLfile.
 
  if_is_yaml_fileandnot_have_yaml:
 
  test_name=mk_test_name(name,"error")
 
  test_docstring="""Error!"""
 
  add_test(
 
  cls,
 
  test_name,
 
  test_docstring,
 
  create_error_func("%sisaYAMLfile,pleaseinstallPyYAML"),
 
  None
 
  )
 
  return
 
  withcodecs.open(data_file_path,'r','utf-8')asf:
 
  #LoadthedatafromYAMLorJSON
 
  if_is_yaml_file:
 
  data=yaml.safe_load(f)
 
  else:
 
  data=json.load(f)
 
  _add_tests_from_data(cls,name,func,data)
 
  3.@date(*value),简单粗暴的直观实现数据驱动,直接将可迭代对象传参,进行数据传递,数据之间用逗号“,”隔离,代表一组数据,此时如果实现unpack,则更加细化的实现数据驱动,切记每组数据对应相应的形参。
 
  defunpack(func):
 
  """
 
  Methoddecoratortoaddunpackfeature.
 
  """
 
  setattr(func,UNPACK_ATTR,True)
 
  returnfunc
 
  defdata(*values):
 
  """
 
  Methoddecoratortoaddtoyourtestmethods.
 
  Shouldbeaddedtomethodsofinstancesof``unittest.TestCase``.
 
  """
 
  globalindex_len
 
  index_len=len(str(len(values)))
 
  returnidata(values)
 
  defidata(iterable):
 
  """
 
  Methoddecoratortoaddtoyourtestmethods.
 
  Shouldbeaddedtomethodsofinstancesof``unittest.TestCase``.
 
  """
 
  defwrapper(func):
 
  setattr(func,DATA_ATTR,iterable)
 
  returnfunc
 
  returnwrapper
 
  4.实例
 
  #-*-coding:utf-8-*-
 
  __author__='暮辞'
 
  importtime,random
 
  fromddtimportddt,data,file_data,unpack
 
  importunittest
 
  importjson
 
  fromHTMLTestRunnerimportHTMLTestRunner
 
  @ddt
 
  classDemo(unittest.TestCase):
 
  @file_data("./migrations/test.json")
 
  deftest_hello(self,a,**b):
 
  '''
 
  测试hello
 
  '''
 
  printa
 
  printb
 
  #print"hello",a,type(a)
 
  ifisinstance(a,list):
 
  self.assertTrue(True,"2")
 
  else:
 
  self.assertTrue(True,"3")
 
  @data([1,2,3,4])
 
  deftest_world(self,*b):
 
  '''
 
  测试world
 
  '''
 
  printb
 
  self.assertTrue(True)
 
  @data({"test1":[1,2],"test2":[3,4]},{"test1":[1,2],"test2":[3,4]})
 
  @unpack
 
  deftest_unpack(self,**a):
 
  '''
 
  测试unpack
 
  '''
 
  printa
 
  self.assertTrue(True)
 
  if__name__=="__main__":
 
  suit=unittest.TestSuite()
 
  test=unittest.TestLoader().loadTestsFromTestCase(Demo)
 
  suit.addTests(test)
 
  #suit.addTests(test)
 
  withopen("./migrations/Demo.html","w")asf:
 
  result=HTMLTestRunner(stream=f,description=u"Demo测试报告",title=u"Demo测试报告")
 
  result.run(suit)
 
  测试结果:
 
  至此关于ddt的数据驱动暂时告一段落了,后面还会介绍基于excel、sql等相关的数据驱动内容,并进行对比总结,拭目以待~

如需转载,请注明文章出处和来源网址:http://www.divcss5.com/html/h54871.shtml