单元测试:是开发者编写的一小段代码,用来检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或场景)下某个特定函数的行为。
单元测试什么时候测试:介入的越早越好,极限编程讲究测试驱动开发,也就是先编写测试代码再进行开发,在实际工作当中,不用过分强调谁先谁后,最最重要的是高效,单元测试介入的越早,后面的集成测试的问题就会越少。
单元测试谁负责?:单元测试与其他测试不同,单元测试可看作编码的一部分,应该由程序员来负责,也就是说经过单元测试的代码才是以完成的代码,提交产品代码时也要提交单元测试的代码。
单元测试的时候一个大前提就是需要清楚的知道,自己要测试的程序块所预期的输入和输出,然后根据这个预期和程序逻辑来写case(用例),这里的预期一定要根据需求/设计的逻辑去写,而不是针对程序的实现去写,否则单元测试就失去了意义,照着错误的实现设计的case也很有可能是错误的。
单元测试框架有哪些?
unittest:是python内置的标准类库。它与API和Java的Junit,.net的Nunitt,c++的cppunit 很相似。
pytest:丰富,灵活的测试框架,现在比较主流,语法简单,可以结合Allure生成炫酷的测试报告。
Nose:是对unittest的扩展,使unittest测试更简单。
mock:unittest.mock是用来测试python的库,这是一个标准的库(出现在python3.3版本以上)
还有很多其他的测试框架
单元测试覆盖率:代码覆盖率也被用于单元测试和手工测试来度量测试是否全面的指标之一,应用覆盖率的思想是增强测试用例的设计。
单元测试覆盖类型:
语句覆盖:定义:通过设计一定了的测试用例,保证被测试的方法每一行都会被执行一遍,运行测试用例时被击中的代码行即为被覆盖的语句。也有漏洞:比如程序员把下列例子中的第一个if后面的and写成了or,传入下面例子同样的参数也可以实现行覆盖,所以虽然行覆盖是最基础的覆盖也是最薄弱的 ,如果完全依赖行覆盖,会出现很严重的问题。
# 被测代码块
def demo(a, b, x):
if a > 1 and b == 0:
x = x / a
if a == 2 or x > 1:
x = x + 1
return x
# 传入a=2,b=0,c=3,也就是语句覆盖的测试用例,仅需要传入这一组参数就可以实现行覆盖(语句覆盖)
print(demo(2,0,3))
判断覆盖:定义:运行测试用例的过程被击中的判定语句。也就是上面举例的被测代码块可以写4条测试用例(如下表格),也有漏洞:大部分的判定语句都是由多个逻辑条件组合而成的,若仅仅判断其整个最终结果(也就是只关注整体判断),而忽略每个条件的取值,情况,必然会遗漏部分测试路径。就像上面举例的被测代码块的条件:a>1andb=0;a=2orx>1
case | a>1&&b==0 | a==2||x>1 |
a=2,b=0,c=3 | Ture | Ture |
a=1,b=0,x=1 | False | False |
a=3,b=0,x=3 | Ture | False |
a=2,b=1,x=1 | False | Ture |
条件覆盖定义:条件覆盖和判断覆盖类似,不过判定覆盖关注整个判定语句,而条件覆盖则关注某个判断条件,以上面举例的被测代码块为例,可以写条件覆盖的测试用例(如下表格),也有漏洞:以条件覆盖的思路设计的测试用例会有很多,虽然全面,但数量太多了
test cases | a>1 | b==0 |
case1 | Ture | Ture |
case2 | False | False |
case3 | Ture | False |
case4 | False | Ture |
路径覆盖:覆盖所有可能执行的路径,以下面图的被测代码块为例,可以写路径覆盖的测试用例(如下表格),一般来说,设计测试用例都是用这个方式来设计
test cases | a,b,x | executepath(执行路径) |
case1 | 2,0,3 | 124 |
case2 | 1,0,1 | 135 |
case3 | 3,0,3 | 125 |
case4 | 2,1,1 | 134 |
unittest:常用在单元测试,在自动化测试中提供用例组织和执行,提供丰富的断言方法,验证函数等功能,加上HTMLtestrunner可以生成HTML的报告,现在依然很多公司在用这个框架。
unittest提供了test cases(写的测试用例),test suites(测试用例的集合),test fixtures(在执行用例之前或之后会准备环境或清理工作),test runner(执行测试用例或者执行测试套件test suites)相关的组件
unittest编写规范:首先需要导入包import unittest,然后再创建一个类(创建的这个类必须继承unitest.testcase这个类),测试用例写在定义一个方法下且方法名必须以“test_”开头,模块名和类名没有特殊要求。
import unittest
# 定义一个类
class demo(unittest.TestCase):
# setupclass\teardownclass用到了class类,所以要加@classmethod
# setupclass和teardownclass是分别在整个类执行完成之前和之后执行一次
@classmethod
def setUpClass(cls) -> None:
print('setupclass')
@classmethod
def tearDownClass(cls) -> None:
print('teardownclass')
# setup方法就是比如在执行测试用例之前需要登录操作或者是需要准备一些数据或者是一些图片资源等,就可以在setup方法下写
# teardown方法就是需要对数据进行清空的操作或者是断开数据库连接等,就不会影响其他测试用例执行,保证每一条测试用例的独立性
# 所以setup和teardown可以在测试用例的方法之前或者之后用
# 对于unittest框架,setup和teardown方法都是自动调用的,不写也会调用,写了就相当于重复写,需要在这两个方法下添加其他内容时,可以写一下
# setup是每一个测试用例方法执行前都会先执行setup一次,同理teardown是每一个测试用例方法执行前都会先执行teardown一次
def setUp(self) -> None:
print('setup')
def tearDown(self) -> None:
print('teardown')
# unittest提供了断言操作,在执行测试用例时可以加断言来判断测试用例要判断的一些数据或一些过程,常用断言方法assertEqual,assertIn,assertTure。
def test_case01(self):
print('test_case01')
self.assertEqual(1, 1, '判断相等')
self.assertIn('h', 'hi')
def test_case02(self):
print('test_case02')
self.assertEqual(2, 2, '判断相等')
# self.assertIn('h', 'hello')
# 如果在一条测试用例方法之前加@unittest.skip,就可以跳过该条测试用例,就不用执行该条测试用例
@unittest.skip
def test_case03(self):
print('test_case03')
self.assertEqual(3, 4, '判断相等')
# self.assertIn('h', 'hello')
if __name__ == '__main__':
unittest.main
unittest断言:常用:assertEqual(断言两个值是否相等),assertIn(断言第一个字符串是否在第二个字符串里面),assertTure(断言结果是否为ture);unittest基本断言方法:基本的断言方法提供了测试结果是ture还是false,所有断言方法都有一个msg(全称message)参数,如果指定msg参数的值,则该信息会作为失败的错误信息返回。
断言方法 | 断言描述 |
assertEqual(arg1, arg2, msg=None) | 验证arg1 = arg2,不等则fail |
assertNotEqual(arg1, arg2, msg=None) | 验证arg1 != arg2,相等则fail |
assertTure(expr, msg=None) | 验证expr是ture,如果是false,则fail |
assertFalse(expr, msg=None) | 验证expr是false,如果是ture,则fail |
assertls(arg1, arg2, msg=None) | 验证arg1,arg2是同一个对象,否则fail |
assertlsNot(arg1, arg2, msg=None) | 验证arg1,arg2不是同一个对象,否则fail |
asserlsNone(expr, msg=None) | 验证expr是None,否则fail |
asserlsNotNone(expr, msg=None) | 验证expr不是None,否则fail |
assertIn(arg1, arg2, msg=None) | 验证arg1是arg2的子串,否则fail |
assertNotIn(arg1, arg2, msg=None) | 验证arg1不是arg2的子串,否则fail |
assertlsInstance(obj, cls, msg=None) | 验证obj是cls的实例,否则fail |
assertNotlsInstance(obj, cls, msg=None) | 验证obj不是cls的实例,否则fail |
多个测试用例的集合就是测试套件,通过测试套件来管理多个测试用例,unittest的执行,unittest.main()
unittest,测试用例执行的过程:1、首先写好testcase;2、然后由testloader加载testcase到testsuite;3、然后由texttestrunner来运行testsuite;4、运行结果保存在texttestresult中;5、整个过程集成在unittest.main模块中;6、testcase可以是多个,testsuite也可以是多个。
texttestresult的结果如何分析呢:HTML test runner工具可以分析texttestresult结果,并且生成一个带日志的report,以HTML的形式展示出来。
HTMLTestRunner安装和使用,安装地址GitHub - huilansame/HTMLTestRunner_PY3: 针对PY3做了修改,增加对subTest的支持,用Echarts加了执行统计表 ,在下载好的文件中,将HTMLTestRunner_PY3拷贝到python的Lib文件夹,打开pychram,在需要执行HTMLTestRunner工具的文件,导入import HTMLTestRunner,并在
if __name__ == '__main__'下写如下段代码所示,执行时要用python解释器执行
import HTMLTestRunner
import unittest
# 定义一个类
class demo(unittest.TestCase):
# setupclass\teardownclass用到了class类,所以要加@classmethod
# setupclass和teardownclass是分别在整个类执行完成之前和之后执行一次
@classmethod
def setUpClass(cls) -> None:
print('setupclass')
@classmethod
def tearDownClass(cls) -> None:
print('teardownclass')
# setup方法就是比如在执行测试用例之前需要登录操作或者是需要准备一些数据或者是一些图片资源等,就可以在setup方法下写
# teardown方法就是需要对数据进行清空的操作或者是断开数据库连接等,就不会影响其他测试用例执行,保证每一条测试用例的独立性
# 所以setup和teardown可以在测试用例的方法之前或者之后用
# 对于unittest框架,setup和teardown方法都是自动调用的,不写也会调用,写了就相当于重复写,需要在这两个方法下添加其他内容时,可以写一下
# setup是每一个测试用例方法执行前都会先执行setup一次,同理teardown是每一个测试用例方法执行前都会先执行teardown一次
def setUp(self) -> None:
print('setup')
def tearDown(self) -> None:
print('teardown')
# unittest提供了断言操作,在执行测试用例时可以加断言来判断测试用例要判断的一些数据或一些过程,常用断言方法assertEqual,assertIn,assertTure。
def test_case01(self):
print('test_case01')
self.assertEqual(1, 1, '判断相等')
self.assertIn('h', 'hi')
def test_case02(self):
print('test_case02')
self.assertEqual(2, 2, '判断相等')
# self.assertIn('h', 'hello')
# 如果在一条测试用例方法之前加@unittest.skip,就可以跳过该条测试用例,就不用执行该条测试用例
# @unittest.skip
def test_case03(self):
print('test_case03')
self.assertEqual(3, 3, '判断相等')
# self.assertIn('h', 'hello')
class demo1(unittest.TestCase):
def test_demo1_case01(self):
print('test_case01')
self.assertEqual(1, 1, '判断相等')
# self.assertIn('h', 'hello')
class demo2(unittest.TestCase):
def test_demo2_case01(self):
print('test_case01')
self.assertEqual(1, 1, '判断相等')
# self.assertIn('h', 'hello')
if __name__ == '__main__':
# unittest.main可以将当前页面上所有以"test_" 开头的方法名的测试用例方法全部加载出来,并且去执行它
# unittest.main()
# 创建测试套件,方便执行自己想执行的测试用例
# suite = unittest.TestSuite()
# suite.addTest(demo('test_case02'))
# 执行测试套件
# unittest.TestSuite().run(suite)
# 执行多个类的其中几个类
# suite = unittest.TestLoader().loadTestsFromTestCase(demo)
# suite1 = unittest.TestLoader().loadTestsFromTestCase(demo1)
# suiteall = unittest.TestSuite([suite, suite1])
# unittest.TextTestRunner().run(suiteall)
# 去匹配路径,执行路径下的所有文件且导入了unittest模块的,里面以"test_" 开头的测试用例方法
# discover = unittest.defaultTestLoader.discover('./', 'test*.py')
# unittest.TextTestRunner().run(discover)
# 使用HTMLTestRunner生成报告
report_title = '测试报告'
desc = '用于展示修改样式后的HTMLTestRunner'
report_file = './report.html'
# 需要执行的类
suite = unittest.TestSuite()
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(demo))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(demo1))
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(demo2))
# 执行测试套件,生成测试报告
with open(report_file, 'wb') as report:
runner = HTMLTestRunner.HTMLTestRunner(stream=report, title=report_title, description=desc)
runner.run(suite)