首先创建case_set.py文件继承 unittest.TestCase 类,
case_set.py
class MyTest(unittest.TestCase):
# 整个Test类的开始和结束执行
driver = None
@classmethod
def setUpClass(cls):
print("test class start =======>")
cls.hp = help()
cls.driver = cls.hp.run()
@classmethod
def tearDownClass(cls):
print("test class end =======>")
cls.driver.quit()
def setUp(self):
pass
def tearDown(self):
pass
在MyTest类中实现setUpClass,tearDownClass,用于整个Test类的开始和结束执行,
在setUpClass中实现启动app driver并获得driver,在tearDownClass中实现当一个Test类执行完毕后退出driver,然后后面写的每一个Test类(具体的case)都继承自MyTest。
起初的想法是这样做的,好处很明显,就是不用每次执行case都重启driver,节省了大量的时间。
但是这样做有2个很明显的问题,第一个就是用例的耦合度变高了,case与case之间必须有上下级关联关系
还有一个致命问题:比如登录页面和登录成功后的认证页面,如果上一条登录case执行失败或者程序报错后,会停留在当前的登录页面,继续执行下一条认证的case的时候,因为登录case失败,所以认证的case会因为找不到元素报错。这样就导致一旦某一条case执行失败,那么所有的case都会执行失败
第一个问题好解决,有上下级关联就有关联,因为有的case与case之间也是存在上下级关联的,
第二个问题目前的想法就是,一旦某一条case执行失败或者报错,那么就直接退出driver,重新打开driver,并在执行下一条case的时候设置前置的操作,比如登录页面的case执行失败后,执行下一条认证的case的时候,首先判断是否需要前置操作(其实就是判断app是否重新打开过了,可以通过获取页面元素来判断),如果重开了,就需要就执行前置操作登录操作。这样就完美解决了目前的问题,以后所有的case都写一些前置的操作
那么如何来判断程序是如何出错的呢?怎么获得异常?这里我参考了一下别人的博客内容
https://www.cnblogs.com/ywhyme/p/10657345.html 感谢大神分享,
完整的实现方法如下
import contextlib
import sys
import unittest
from help.db_help import help
from unittest.case import SkipTest, _ShouldStop, _Outcome
from help.is_open_driver import Singleton
@contextlib.contextmanager
def testPartExecutor(self, test_case, isTest=False):
old_success = self.success
self.success = True
try:
yield
except Exception:
try:
# if error
getattr(test_case, test_case._testMethodName).__func__._error = True
raise
except KeyboardInterrupt:
raise
except SkipTest as e:
self.success = False
self.skipped.append((test_case, str(e)))
except _ShouldStop:
pass
except:
exc_info = sys.exc_info()
if self.expecting_failure:
self.expectedFailure = exc_info
else:
self.success = False
self.errors.append((test_case, exc_info))
# explicitly break a reference cycle:
# exc_info -> frame -> exc_info
exc_info = None
else:
if self.result_supports_subtests and self.success:
self.errors.append((test_case, None))
finally:
self.success = self.success and old_success
_Outcome.testPartExecutor = testPartExecutor
# 整个文件的开始和结束执行
def setUpModule():
print("test module start >>>>>>>>>>>>>>")
pass
def tearDownModule():
print("test module end >>>>>>>>>>>>>>")
pass
class MyTest(unittest.TestCase):
# 整个Test类的开始和结束执行
driver = None
@classmethod
def setUpClass(cls):
print("test class start =======>")
cls.hp = help()
cls.driver = cls.hp.run()
@classmethod
def tearDownClass(cls):
print("test class end =======>")
cls.driver.quit()
pass
def setUp(self):
if(Singleton().is_need_open_driver):
self.setUpClass()
Singleton().change_not_need_open_driver()
def tearDown(self):
#如果用例执行失败,则退出driver
if hasattr(getattr(self, self._testMethodName), "_error"):
# dosomething 可以进行比如打印日志,截图,等等操作
self.driver.quit()
Singleton().change_need_open_driver()
pass
当程序出错或者case失败时,则一定会进入tearDown方法中,这里实现的就是判断是否case执行成功,(ps:查看源码后发现每次case执行失败后会清楚掉erroes,所以不会出现下一个case运行成功时tearDown方法的错误判断)
如果有错误则执行driver的退出操作,并设置标注状态需要重新打开driver,Singleton类是单例模式来实现的,用来保存is_need_open_driver属性的值,
实现逻辑
#单例模式,每次初始化的时候,不改变类中属性的值,沿用同一套属性值
class Singleton(object):
instance = None
init_flag = False
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self):
if Singleton.init_flag:
return
Singleton.init_flag = True
#添加是否需要重新启动driver的判断
self.is_need_open_driver = False
#改变是否需要重新启动driver的判断的值
def change_need_open_driver(self):
self.is_need_open_driver = True
def change_not_need_open_driver(self):
self.is_need_open_driver = False
为什么is_need_open_driver这个属性值不直接写在MyTest类中呢?而是通过单例模式来保存下来?
因为我发现如果直接写在MyTest类中,上一条case执行失败,在执行下一条case的时候,即使在tearDown方法中改变了该属性的值,但是在setUp重新去获取该值时,还是获取的改变前的值,很奇怪,不晓得是什么情况导致的,我感觉也许是在执行下一条case的时候重新初始化了该MyTest类导致的,不晓得是不是这个原因,所以就想到了单例模式。有知道原因的请留言哈。。
最后,最最重要的是在setUp中重新启动driver,千万不要在setUp方法中重新去打开一个driver,否则程序执行会报错,正确的做法是需要重新调用实现setUpClass()方法
因为setUpClass()该方法的实现是整个Test类的执行开始和结束,Test类中的case未全部执行完成时,如果你在setUp方法中重新去打开一个driver,即使你重新赋值driver也不能覆盖原来的driver属性(虽然我也不晓得是个什么原因),那么你这次执行的case其实就是使用的setUp中的driver,但是你的下下条case,使用的却是setUpClass()方法中的driver,此时setUpClass()中的driver因为上个异常case关闭了,一定会报错。
所以这里只能重新调用setUpClass()中的方法,并重新覆盖driver。
最后附上测试后的case截图,如果有知道上面我说的2个原因的,请留言解答下哈
如果大家有更加合适的解决办法,也请留言,我觉得我这样做虽然解决了问题,但是绕了一圈