python自动化测试之DDT数据驱动的实现代码

news2024/10/7 0:04:05

时隔已久,再次冒烟,自动化测试工作仍在继续,自动化测试中的数据驱动技术尤为重要,不然咋去实现数据分离呢,对吧,这里就简单介绍下与传统unittest自动化测试框架匹配的DDT数据驱动技术。

话不多说,先撸一波源码,其实整体代码并不多

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

# -*- coding: utf-8 -*-

# This file is a part of DDT (https://github.com/txels/ddt)

# Copyright 2012-2015 Carles Barrobés and DDT contributors

# For the exact contribution history, see the git revision log.

# DDT is licensed under the MIT License, included in

# https://github.com/txels/ddt/blob/master/LICENSE.md

import inspect

import json

import os

import re

import codecs

from functools import wraps

try:

  import yaml

except ImportError: # pragma: no cover

  _have_yaml = False

else:

  _have_yaml = True

__version__ = '1.2.1'

# These attributes will not conflict with any real python attribute

# They are added to the decorated test method and processed later

# by the `ddt` class decorator.

DATA_ATTR = '%values'   # store the data the test must run with

FILE_ATTR = '%file_path'  # store the path to JSON file

UNPACK_ATTR = '%unpack'  # remember that we have to unpack values

index_len = 5       # default max length of case index

try:

  trivial_types = (type(None), bool, int, float, basestring)

except NameError:

  trivial_types = (type(None), bool, int, float, str)

def is_trivial(value):

  if isinstance(value, trivial_types):

    return True

  elif isinstance(value, (list, tuple)):

    return all(map(is_trivial, value))

  return False

def unpack(func):

  """

  Method decorator to add unpack feature.

  """

  setattr(func, UNPACK_ATTR, True)

  return func

def data(*values):

  """

  Method decorator to add to your test methods.

  Should be added to methods of instances of ``unittest.TestCase``.

  """

  global index_len

  index_len = len(str(len(values)))

  return idata(values)

def idata(iterable):

  """

  Method decorator to add to your test methods.

  Should be added to methods of instances of ``unittest.TestCase``.

  """

  def wrapper(func):

    setattr(func, DATA_ATTR, iterable)

    return func

  return wrapper

def file_data(value):

  """

  Method decorator to add to your test methods.

  Should be added to methods of instances of ``unittest.TestCase``.

  ``value`` should be a path relative to the directory of the file

  containing the decorated ``unittest.TestCase``. The file

  should contain JSON encoded data, that can either be a list or a

  dict.

  In case of a list, each value in the list will correspond to one

  test case, and the value will be concatenated to the test method

  name.

  In case of a dict, keys will be used as suffixes to the name of the

  test case, and values will be fed as test data.

  """

  def wrapper(func):

    setattr(func, FILE_ATTR, value)

    return func

  return wrapper

def mk_test_name(name, value, index=0):

  """

  Generate a new name for a test case.

  It will take the original test name and append an ordinal index and a

  string representation of the value, and convert the result into a valid

  python identifier by replacing extraneous characters with ``_``.

  We avoid doing str(value) if dealing with non-trivial values.

  The problem is possible different names with different runs, e.g.

  different order of dictionary keys (see PYTHONHASHSEED) or dealing

  with mock objects.

  Trivial scalar values are passed as is.

  A "trivial" value is a plain scalar, or a tuple or list consisting

  only of trivial values.

  """

  # Add zeros before index to keep order

  index = "{0:0{1}}".format(index + 1, index_len)

  if not is_trivial(value):

    return "{0}_{1}".format(name, index)

  try:

    value = str(value)

  except UnicodeEncodeError:

    # fallback for python2

    value = value.encode('ascii', 'backslashreplace')

  test_name = "{0}_{1}_{2}".format(name, index, value)

  return re.sub(r'\W|^(?=\d)', '_', test_name)

def feed_data(func, new_name, test_data_docstring, *args, **kwargs):

  """

  This internal method decorator feeds the test data item to the test.

  """

  @wraps(func)

  def wrapper(self):

    return func(self, *args, **kwargs)

  wrapper.__name__ = new_name

  wrapper.__wrapped__ = func

  # set docstring if exists

  if test_data_docstring is not None:

    wrapper.__doc__ = test_data_docstring

  else:

    # Try to call format on the docstring

    if func.__doc__:

      try:

        wrapper.__doc__ = func.__doc__.format(*args, **kwargs)

      except (IndexError, KeyError):

        # Maybe the user has added some of the formating strings

        # unintentionally in the docstring. Do not raise an exception

        # as it could be that user is not aware of the

        # formating feature.

        pass

  return wrapper

def add_test(cls, test_name, test_docstring, func, *args, **kwargs):

  """

  Add a test case to this class.

  The test will be based on an existing function but will give it a new

  name.

  """

  setattr(cls, test_name, feed_data(func, test_name, test_docstring,

      *args, **kwargs))

def process_file_data(cls, name, func, file_attr):

  """

  Process the parameter in the `file_data` decorator.

  """

  cls_path = os.path.abspath(inspect.getsourcefile(cls))

  data_file_path = os.path.join(os.path.dirname(cls_path), file_attr)

  def create_error_func(message): # pylint: disable-msg=W0613

    def func(*args):

      raise ValueError(message % file_attr)

    return func

  # If file does not exist, provide an error function instead

  if not os.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("%s does not exist"), None)

    return

  _is_yaml_file = data_file_path.endswith((".yml", ".yaml"))

  # Don't have YAML but want to use YAML file.

  if _is_yaml_file and not _have_yaml:

    test_name = mk_test_name(name, "error")

    test_docstring = """Error!"""

    add_test(

      cls,

      test_name,

      test_docstring,

      create_error_func("%s is a YAML file, please install PyYAML"),

      None

    )

    return

  with codecs.open(data_file_path, 'r', 'utf-8') as f:

    # Load the data from YAML or JSON

    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):

  """

  Add tests from data loaded from the data file into the class

  """

  for i, elem in enumerate(data):

    if isinstance(data, dict):

      key, value = elem, data[elem]

      test_name = mk_test_name(name, key, i)

    elif isinstance(data, list):

      value = elem

      test_name = mk_test_name(name, value, i)

    if isinstance(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):

  """Finds out if the obj is a "primitive". It is somewhat hacky but it works.

  """

  return not hasattr(obj, '__dict__')

def _get_test_data_docstring(func, value):

  """Returns a docstring based on the following resolution strategy:

  1. Passed value is not a "primitive" and has a docstring, then use it.

  2. In all other cases return None, i.e the test name is used.

  """

  if not _is_primitive(value) and value.__doc__:

    return value.__doc__

  else:

    return None

def ddt(cls):

  """

  Class decorator for subclasses of ``unittest.TestCase``.

  Apply this decorator to the test case class, and then

  decorate test methods with ``@data``.

  For each method decorated with ``@data``, this will effectively create as

  many methods as data items are passed as parameters to ``@data``.

  The names of the test methods follow the pattern

  ``original_test_name_{ordinal}_{data}``. ``ordinal`` is the position of the

  data argument, starting with 1.

  For data we use a string representation of the data value converted into a

  valid python identifier. If ``data.__name__`` exists, we use that instead.

  For each method decorated with ``@file_data('test_data.json')``, the

  decorator will try to load the test_data.json file located relative

  to the python file containing the method that is decorated. It will,

  for each ``test_name`` key create as many methods in the list of values

  from the ``data`` key.

  """

  for name, func in list(cls.__dict__.items()):

    if hasattr(func, DATA_ATTR):

      for i, v in enumerate(getattr(func, DATA_ATTR)):

        test_name = mk_test_name(name, getattr(v, "__name__", v), i)

        test_data_docstring = _get_test_data_docstring(func, v)

        if hasattr(func, UNPACK_ATTR):

          if isinstance(v, tuple) or isinstance(v, list):

            add_test(

              cls,

              test_name,

              test_data_docstring,

              func,

              *v

            )

          else:

            # unpack dictionary

            add_test(

              cls,

              test_name,

              test_data_docstring,

              func,

              **v

            )

        else:

          add_test(cls, test_name, test_data_docstring, func, v)

      delattr(cls, name)

    elif hasattr(func, FILE_ATTR):

      file_attr = getattr(func, FILE_ATTR)

      process_file_data(cls, name, func, file_attr)

      delattr(cls, name)

  return cls

ddt源码

通过源码的说明,基本可以了解个大概了,其核心用法就是利用装饰器来实现功能的复用及扩展延续,以此来实现数据驱动,现在简单介绍下其主要函数的基本使用场景。

1. @ddt(cls) ,其服务于unittest类装饰器,主要功能是判断该类中是否具有相应 ddt 装饰的方法,如有则利用自省机制,实现测试用例命名 mk_test_name、 数据回填 _add_tests_from_data 并通过 add_test 添加至unittest的容器TestSuite中去,然后执行得到testResult,流程非常清晰。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

def ddt(cls):

  for name, func in list(cls.__dict__.items()):

    if hasattr(func, DATA_ATTR):

      for i, v in enumerate(getattr(func, DATA_ATTR)):

        test_name = mk_test_name(name, getattr(v, "__name__", v), i)

        test_data_docstring = _get_test_data_docstring(func, v)

        if hasattr(func, UNPACK_ATTR):

          if isinstance(v, tuple) or isinstance(v, list):

            add_test(

              cls,

              test_name,

              test_data_docstring,

              func,

              *v

            )

          else:

            # unpack dictionary

            add_test(

              cls,

              test_name,

              test_data_docstring,

              func,

              **v

            )

        else:

          add_test(cls, test_name, test_data_docstring, func, v)

      delattr(cls, name)

    elif hasattr(func, FILE_ATTR):

      file_attr = getattr(func, FILE_ATTR)

      process_file_data(cls, name, func, file_attr)

      delattr(cls, name)

  return cls

2. @file_data(PATH) ,其主要是通过 process_file_data 方法实现数据解析,这里通过 _add_tests_from_data 实现测试数据回填,通过源码可以得知目前文件只支持 Yaml 和 JSON 数据文件,想扩展其它文件比如 xml 等直接改源码就行

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

def process_file_data(cls, name, func, file_attr):

  """

  Process the parameter in the `file_data` decorator.

  """

  cls_path = os.path.abspath(inspect.getsourcefile(cls))

  data_file_path = os.path.join(os.path.dirname(cls_path), file_attr)

  def create_error_func(message): # pylint: disable-msg=W0613

    def func(*args):

      raise ValueError(message % file_attr)

    return func

  # If file does not exist, provide an error function instead

  if not os.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("%s does not exist"), None)

    return

  _is_yaml_file = data_file_path.endswith((".yml", ".yaml"))

  # Don't have YAML but want to use YAML file.

  if _is_yaml_file and not _have_yaml:

    test_name = mk_test_name(name, "error")

    test_docstring = """Error!"""

    add_test(

      cls,

      test_name,

      test_docstring,

      create_error_func("%s is a YAML file, please install PyYAML"),

      None

    )

    return

  with codecs.open(data_file_path, 'r', 'utf-8') as f:

    # Load the data from YAML or JSON

    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, 则更加细化的实现数据驱动,切记每组数据对应相应的形参。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

def unpack(func):

  """

  Method decorator to add unpack feature.

  """

  setattr(func, UNPACK_ATTR, True)

  return func

def data(*values):

  """

  Method decorator to add to your test methods.

  Should be added to methods of instances of ``unittest.TestCase``.

  """

  global index_len

  index_len = len(str(len(values)))

  return idata(values)

def idata(iterable):

  """

  Method decorator to add to your test methods.

  Should be added to methods of instances of ``unittest.TestCase``.

  """

  def wrapper(func):

    setattr(func, DATA_ATTR, iterable)

    return func

  return wrapper

4. 实例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

# -*- coding: utf-8 -*-

__author__ = '暮辞'

import time,random

from ddt import ddt, data, file_data, unpack

import unittest

import json

from HTMLTestRunner import HTMLTestRunner

@ddt

class Demo(unittest.TestCase):

  @file_data("./migrations/test.json")

  def test_hello(self, a, **b):

    '''

    测试hello

    '''

    print a

    print b

    #print "hello", a, type(a)

    if isinstance(a, list):

      self.assertTrue(True, "2")

    else:

      self.assertTrue(True, "3")

  @data([1, 2, 3, 4])

  def test_world(self, *b):

    '''

    测试world

    '''

    print b

    self.assertTrue(True)

  @data({"test1":[1, 2], "test2":[3, 4]}, {"test1":[1, 2],"test2":[3, 4]})

  @unpack

  def test_unpack(self, **a):

    '''

    测试unpack

    '''

    print a

    self.assertTrue(True)

if __name__ == "__main__":

  suit = unittest.TestSuite()

  test = unittest.TestLoader().loadTestsFromTestCase(Demo)

  suit.addTests(test)

  #suit.addTests(test)

  with open("./migrations/Demo.html", "w") as f:

    result = HTMLTestRunner(stream=f, description=u"Demo测试报告", title=u"Demo测试报告")

    result.run(suit)

测试结果:

至此关于ddt的数据驱动暂时告一段落了,后面还会介绍基于excel、sql等相关的数据驱动内容,并进行对比总结,拭目以待~

  最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走! 希望能帮助到你!【100%无套路免费领取】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1858999.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

神经网络的编程基础

神经网络的编程基础 二分类 二分类是机器学习中的一种基本分类问题,其中每个样本被划分为两个类别中的一个,即正类或负类。这种分类问题在现实生活中有广泛的应用,例如判定邮件是否为垃圾邮件、判别某个人是否患病等。二分类模型根据样本的特…

Python高压电容导电体和水文椭圆微分

🎯要点 🎯二维热传导二阶偏微分方程 | 🎯调和函数和几何图曲率 | 🎯解潮汐波动方程 | 🎯解静止基态旋转球体流体运动函数 | 🎯水文空间插值 | 🎯流体流动模拟求解器 | 🎯随机算法解…

光伏半导体的种类

光照射半导体材料时,其电导率发生变化的实质是光生载流子的产生。在半导体中,价带中的电子受到一定能量的光子激发后,可以跃迁到导带,形成自由电子和空穴对,即光生载流子。这些光生载流子会增加半导体的导电能力&#…

NepnepxCATCTF Pwn-Chao

文章目录 参考类型混淆异常处理的栈回退机制虚表和类的恢复假想的程序结构逆向工程场景步骤解析 idabug检查找虚表strupsc_str()alloca异常逆向maindisplayupdatecreate 新东西exp和思路 参考 https://www.cnblogs.com/winmt/articles/17018284.html 类型混淆 关于C中由虚函…

【乐吾乐2D可视化组态编辑器】图表动态显示

1. 添加数据 乐吾乐2D可视化组态编辑器地址:https://2d.le5le.com/ 图表动态展示是指一个图表图元的数据属性(一般是dataY)绑定多个变量,建立通信后数据动态变化展示。 官网默认Echarts图表拖拽到画布中是已经添加了图元的da…

【SpringCloud-Seata源码分析3】

文章目录 事务的提交客户端提交流程服务端提交流程客户端删除undo_log 事务回滚客户端事务回滚服务端回滚事务 事务的提交 前面两篇我们分析了seata的TC初始化和TM,RM初始化,并且事务准备阶段源码及业务Sql执行,下面我们分析事务的提交源码。 客户端提…

【面试题】等保(等级保护)的工作流程

等保(等级保护)的工作流程主要包括以下几个步骤,以下将详细分点介绍: 系统定级: 确定定级对象:根据《信息系统等级保护管理办法》和《信息系统等级保护定级指南》的要求,确定需要进行等级保护的…

Charles抓包工具系列文章(二)-- Repeat 回放http请求

一、什么是http请求回放 当我们对客户端进行抓包,经常会想要重试http请求,或者改写原有部分进行重新请求,都需要用到回放http请求。 还有一种场景是压力测试,对一个请求进行重复请求多少次,并加上适当的并发度。 这里…

2024年6月22日,雨中骑行谷仓坝游后记。 (校长骑行撰稿)

在这个快节奏的时代,生活中总是充满了无尽的压力和喧嚣。然而,骑行就像一股清流,给人们带来片刻的宁静和思考,是一种独特的释放方式。它不仅是一种锻炼,更是一种探索世界、理解生活的方式。这次,我们校长骑…

[职场] 线上面试的准备工作 #知识分享#经验分享#媒体

线上面试的准备工作 面对求职中的面试,应届毕业生该做些什么准备呢?在这里,向各位分享面试前做好预案不慌张几点准备。现在许多面试是通过线上形式进行的。对于求职者来说,要做好两手准备。在这里,重点与大家分享线上面…

pyhon模块以及常用的第三方模块

import my_info as info print(info.name) info.show()from my_info import * print(name) show() pyhon中包的导入 import admin.my_admin as ad # 包名.模块名 admin是包名,my_admin是模块名print(ad.name) print(ad.info())from admin import my_admin as ad # …

【可控图像生成系列论文(三)】北大 Context-Aware Unsupervised Text Stylization论文解读1

【可控图像生成系列论文(一)】 简要介绍了论文的整体流程和方法;【可控图像生成系列论文(二)】则将就整体方法、模型结构、训练数据和纹理迁移进行了更详细的介绍。 本篇将介绍来自 ACM MM 2018 的一篇字体风格化的可控…

【43 Pandas+Pyecharts | 京东某商品销量数据分析可视化】

文章目录 🏳️‍🌈 1. 导入模块🏳️‍🌈 2. Pandas数据处理2.1 读取数据2.2 查看数据信息2.3 查看数据描述信息 🏳️‍🌈 3. Pyecharts数据可视化3.1 销量(瓶)地图分布3.2 每月销量(瓶)3.3 男性女性购买数量…

《看不影子的少年》一部探讨偏见与接纳的电视剧❗

《看不见影子的少年》这部电视剧以其独特的视角和深刻的主题 给我留下了深刻的印象。该剧讲述了一位与众不同的少年 他无法在阳光下留下影子,象征着他在社会中的孤独与不被理解 观看过程中,可以感受到少年内心的挣扎与渴望 他渴望被接纳,渴…

【linux kernel】一文总结linux输入子系统

文章目录 一、导读二、重要数据数据结构(2-1)struct input_dev(2-2)input_dev_list和input_handler_list(2-3)struct input_handler 三、input核心的初始化四、常用API五、输入设备驱动开发总结(1)查看输入…

20240507-招商证券 基于鳄鱼线的指数择时及轮动策略

动量震荡指标构造 动量震荡指标为交易者提供了获利的钥匙。动量震荡指标测算了5根价格柱相对于34根价格柱的动量变化。首先计算最近5根价格柱的最高价和最低价间的中点的简单移动平均值,即(最高价最低价)12的简单移动平均,将得出的值减去最近34根价格柱的最高价和最低价中点的…

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探…

Listary——最好用的电脑搜索文件软件

简易版: https://www.listary.com/download-completion?versionstable 完整功能版: Microsoft PowerToys | Microsoft Learn

【PA交易】BackTrader(一): 如何使用实时tick数据和蜡烛图

背景和需求 整合Tick数据是PA交易的回测与实盘基本需求。多数交易回测框架往往缺乏对大规模Tick数据直接而全面的支持。Tick数据因其体量庞大(例如,某棕榈油主力合约四年间的数据达8GB)为结合价格趋势与PA分析带来挑战,凸显了实时…

探索ChatTTS项目:高效的文字转语音解决方案

文章目录 📖 介绍 📖📒 ChatTTS 📒📝 项目介绍📝 项目亮点📝 UI 🎈 项目地址 🎈 📖 介绍 📖 在AI技术迅速发展的今天,文本到语音&…