假如我们有这样一个工程,我们new一个新的project。在project中,创建一个用例模块case。而在我们实际工作项目中,功能模块不止是有一个(例如:我们真实的项目中有三个模块,baidu模块用例由小a来完成;blog模块由小b来完成;news由小c来完成)。而每个模块下(baidu、blog、news)都有写好的好多用例。在这里我为了方便,统一写成test01.py,test02.py。里面的内容除了class的名字和用例名字不一样,其他内容全部一样。
具体看下面图所示,该图为整体目录:
test01.py
test02.py
依次类推,一直到test05.py。
假如我们项目模块少、功能少、用例也少,而且只有一个测试,所有的模块设计都是由一个人来完成。那可能就直接运行了,不用进行用例模块的划分。
我们直接在project中创建一个runAll.py,写入代码:
-
# coding:utf-8
-
import unittest
-
import os
-
# 用例路径
-
case_path = os.path.join(os.getcwd(), "case")#其实就是:C:\Users\songlihui\PycharmProjects\temp20191015\case
-
# 报告存放路径
-
# report_path = os.path.join(os.getcwd(), "report")
-
def all_case():
-
discover = unittest.defaultTestLoader.discover(case_path, pattern="test*.py", top_level_dir=None)
-
print(discover)
-
return discover
-
if __name__ == "__main__":
-
runner = unittest.TextTestRunner()
-
runner.run(all_case())
然后右键runAll.py文件,点击运行,就直接将case下的所用模块下的用例文件test01到test05.py都执行,结果如下:
然而现实的工作中,多数都跟我们文章开头所述。多人协作开发,最后将代码合并。那这样的一个庞大的项目,我们在执行的时候,并不需要将所有case下的所有用例都执行。又该怎么处理???,例如我把用例路径改成blog,就会只执行blog下的所有用例。
但是问题又来了,我这里是写死的路径C:\Users\songlihui\PycharmProjects\temp20191015\case\blog,而实际的项目中,我们应该是通过一个地方去读,读出来然后做拼接。比如我们现在就是把创建的目录和每一条用例存储在数据库。从数据库中去读模块名的状态,如果状态是0不执行。如果状态是1,执行。然后我们将从数据库中读出case下的所有目录,然后循环拼接判断。这里我就不演示从数据库来存储和读取了,我就简单一些。写一个简单的配置文件来进行演示。
首先在project下创建一个config.ini文件,文件中的内容如下:
-
#0不执行,1执行
-
[case]
-
baidu = 0
-
blog = 1
-
news = 0
第二,我们再在project下创建一个readConfig.py用于读取config.ini配置文件:
-
import os
-
import configparser
-
con = configparser.ConfigParser()#实例化
-
con.read(r'C:\Users\songlihui\PycharmProjects\temp20191015\config.ini', encoding='utf-8')
-
class GetPath:
-
"""
-
"""
-
def get_options(self, section):
-
"""
-
通过get_options方法,拿到[case]下的'baidu', 'blog', 'news'
-
:param section:
-
:return:
-
"""
-
# 通过section名,获取config文件中section名为[case]下的options,即'baidu', 'blog', 'news'
-
options = con.options(section)
-
# 可以看到打印出来为option拼接的一个list['baidu', 'blog', 'news']
-
return options
-
def get_case_path(self):
-
"""
-
通过get_options方法,拿到[case]下的'baidu', 'blog', 'news'
-
通过get_case_path方法,将[case]下,对应状态存储为1的取出来
-
然后跟项目路径进行拼接,从而拼接出来一个路径list
-
:return:
-
"""
-
# 定义一个空list
-
end_path = []
-
# 然后我们拼接
-
# 获取工程目录C:\Users\songlihui\PycharmProjects\temp20191015
-
current_path = os.path.dirname(os.path.abspath(__file__))
-
# 打印一下看一下是否正确
-
print('current_path', current_path)
-
# 开始拼接
-
# 可以看到模块baidu,是在我们创建的工程temp20191015下的case下
-
# 所以先循环取出'baidu', 'blog', 'news'
-
options = GetPath().get_options('case')
-
print('options', options)
-
for option in options:
-
value = con.getint('case', option)
-
if value == 1:
-
end_path.append(os.path.join(current_path, 'case', option))
-
print(end_path)
-
return end_path
-
if __name__ == '__main__':
-
# GetPath().get_options('case')
-
GetPath().get_case_path()
如果此时我们的config.ini内容为:
-
#0不执行,1执行
-
[case]
-
baidu = 0
-
blog = 1
-
news = 0
可以执行一下,看一下拼接后的路径
如果我们修改一下配置文件,为如下:
-
#0不执行,1执行
-
[case]
-
baidu = 0
-
blog = 1
-
news = 1
再执行一下,看一下拼接后的路径呢?变成了两个,因为配置文件中news的状态也改成了1,代表执行,所以也应该拼接出来
然后我们继续优化代码,将runAll.py中的文件进行改动,把之前写死的路径改为动态拼接获取。修改runAll.py文件内容如下:
-
# coding:utf-8
-
import unittest
-
import readConfig
-
import os
-
# 用例路径
-
#case_path = os.path.join(os.getcwd(), "case", 'blog')
-
# 报告存放路径
-
# report_path = os.path.join(os.getcwd(), "report")
-
path = readConfig.GetPath().get_case_path()# 因为这个拿到的是一个list,所以我们应该循环
-
print('path111', path)
-
def all_case():
-
discover_list = []
-
# 循环
-
for case_path in path:
-
discover = unittest.defaultTestLoader.discover(case_path, pattern="test*.py", top_level_dir=None)
-
print(discover)
-
discover_list.append(discover)
-
return discover_list
-
if __name__ == "__main__":
-
runner = unittest.TextTestRunner()
-
runner.run(all_case())
运行,我们发现报错了?
这又是为什么??因为之前discover中的case_path是一次性写死的,这次我们修改成从配置文件中取,取出来状态为1的就是我们要执行的模块,然后拼接成一个list,然后再循环list,一个一个添加到discover中。
存在多个目录下的case时,加载第一个case通过,但当加载第二个case时失败,提示:Path must be within the project
这个是因为discover中的第三个参数top_level_dir导致:
discover 第三个参数 top_level_dir 第一次运行时如果为None 会取当前传入的start_dir所在路径为 top_level_dir,而top_level_dir会作为self的参数保存下来,这样第二次运行时 top_level_dir实际取的是上一次的路径,直接影响到了下一次的运行
因此规避此问题方法:
1、将所有case保存在同一目录下,或 只调用一次discover
2、如果需要调用多次,且在不同目录下的话,那么需要手动给top_level_dir传值,将根目录的值给此参数
在代码中新添加了top_level_dir = r'C:\Users\songlihui\PycharmProjects\temp20191015\case'。以及将discover修改为了:discover = unittest.defaultTestLoader.discover(case_path, pattern="test*.py", top_level_dir=top_level_dir)
-
# coding:utf-8
-
import unittest
-
import readConfig
-
import os
-
# 用例路径
-
#case_path = os.path.join(os.getcwd(), "case", 'blog')
-
# 报告存放路径
-
# report_path = os.path.join(os.getcwd(), "report")
-
path = readConfig.GetPath().get_case_path()# 因为这个拿到的是一个list,所以我们应该循环
-
top_level_dir = r'C:\Users\songlihui\PycharmProjects\temp20191015\case'
-
print('path111', path)
-
def all_case():
-
discover_list = []
-
# 循环
-
for case_path in path:
-
discover = unittest.defaultTestLoader.discover(case_path, pattern="test*.py", top_level_dir=top_level_dir)
-
print(discover)
-
discover_list.append(discover)
-
return discover_list
-
if __name__ == "__main__":
-
runner = unittest.TextTestRunner()
-
runner.run(all_case())
继续运行,我们发现又报错了???what?怎么又错了,不要怕,出错不可怕,我们要学会寻找错误。
通过看错误提示,应该是discover在执行的时候没有循环造成的,我们添加循环,将runAll中if __name__中代码修改为如下:
if __name__ == "__main__":
runner = unittest.TextTestRunner()
for discovers in all_case():
runner.run(discovers)
继续运行,看到成功了:
以后我们想执行哪个模块,就直接修改配置文件config.ini文件,将里面的0改为1就可以啦,这样我们是不是就可以通过配置很好的来确定我们到底执行哪个模块下的用例了噢。
扩展:我们可以在前端写一个页面,可以在页面中做一个配置,这个配置对应的是配置文件。当我们在页面操作添加修改删除选择的时候都同步数据到配置文件(我这块是同步到了数据库)。那以后我们就可以在页面中去选择性的选择我们每次需要执行哪个模块的测试用例了。