24--异常处理、测试相关
异常处理
1 什么是异常
# 异常
程序发生错误的信号,程序一旦出错就会抛出异常,程序的运行随即终止
print('start....')
[1,2,3][1000]
print('stop...')
# 异常处理的三个特征
异常的追踪信息 : 异常的出处
异常的类型
异常的内容
2 处理异常的目的
为了增强程序的健壮性,即便是程序运行过程中出错了,也不要终止程序
而是捕捉异常并处理:将出错信息记录到日志内
3 如何处理异常
3.1 异常形式
# 1 语法上的错误SyntaxError,
处理:必须在程序运行前就改正
if
File "<stdin>", line 1
if
^
SyntaxError: invalid syntax
# 2 逻辑上的错误
# TypeError:数字类型无法与字符串类型相加
1+’2’
# ValueError:当字符串包含有非数字的值时,无法转成int类型
num=input(">>: ") #输入hello
int(num)
# NameError:引用了一个不存在的名字x
x
# IndexError:索引超出列表的限制
l=['egon','aa']
l[3]
# KeyError:引用了一个不存在的key
dic={'name':'egon'}
dic['age']
# AttributeError:引用的属性不存在
class Foo:
pass
Foo.x
# ZeroDivisionError:除数不能为0
1/0
3.2 处理逻辑异常
# 1 错误发生的条件是可以预知的,使用if判断来解决
age=input('>>: ').strip() # 输入的只要不是数字就会出错
if age.isdigit():
age=int(age)
if age > 18:
print('猜大了')
elif age < 18:
print('猜大了')
else:
print('猜对了')
else:
print('必须输入数字')
# 2 错误发生的条件是无法预知的
print('start...')
try:
# 有可能会抛出异常的代码
子代码1
子代码2
子代码3
except 异常类型1 as e:
pass
except 异常类型2 as e:
pass
...
else:
如果被检测的子代码块没有异常发生,则会执行else的子代码
finally:
无论被检测的子代码块有无异常发生,都会执行finally的子代码
print('end...')
# 用法一:
print('start...')
try:
print('1111111111')
l=['aaa','bbbb']
l[3] # 抛出异常IndexError,该行代码同级别的后续代码不会运行
print('2222222222')
xxx
print('33333333')
dic={'a':1}
dic['a']
except IndexError as e:
print('异常的信息: ',e)
print('end....')
# 用法二:
print('start...')
try:
print('1111111111')
l=['aaa','bbbb']
# l[3] # 抛出异常IndexError,该行代码同级别的后续代码不会运行
print('2222222222')
xxx
print('33333333')
dic={'a':1}
dic['a']
except IndexError as e:
print('异常的信息: ',e)
except NameError as e:
print('异常的信息: ',e)
print('end....')
# 用法三:
print('start...')
try:
print('1111111111')
l = ['aaa', 'bbbb']
l[3] # 抛出异常IndexError,该行代码同级别的后续代码不会运行
print('2222222222')
xxx
print('33333333')
dic = {'a': 1}
dic['aaa']
# except (IndexError, NameError) as e:
# print('异常的信息: ', e)
# except KeyError as e:
# print('字典的key不存在: ', e)
except Exception as e: # 万能异常
print('所有异常都可以匹配的到')
print('end....')
# 用法四:else不能单独与try配合使用,必须要搭配except
print('start...')
try:
print('1111111111')
print('2222222222')
print('33333333')
except Exception as e: # 万能异常
print('所有异常都可以匹配的到')
else:
print('====>')
print('end....')
# 用法五:finally可以单独与try配合使用
print('start...')
try:
print('1111111111')
l = ['aaa', 'bbbb']
l[3] # 抛出异常IndexError,该行代码同级别的后续代码不会运行
print('2222222222')
xxx
print('33333333')
dic = {'a': 1}
dic['aaa']
finally: # 不处理异常,无论是否发生异常都会执行finally的子代码
print('====》》》》》应该把被检测代码中回收系统资源的代码放到这里')
print('end....')
测试相关
测试是发现和标记缺陷的过程。所谓的缺陷是指实际结果和期望结果之间的任何差别。有的地方,测试也被认为是执行以找出错误为目的的程序的过程。 测试是为了让产品达到以下目标:
- 满足需求用户满意
- 改善产品的市场占有率
- 树立对产品的信任
- 减少开发和维护的成本
1 测试分类
1.1 功能测试
如果一个软件单元的行为方式与它的开发规范完全一样,那么该软件单元就通过了它的功能测试
-
白盒测试:开发人员自己实现,知道函数的内部结构
测试要点:保证代码的每条执行路径、每个分支、每个语句最后都被覆盖到
最基本的形式是单元测试,还有集成测试和系统测试
-
黑盒测试:由开发团队之外的人执行,对测试代码没有可见性,不知道函数的内部结构,将被测系统视为黑盒子
测试要点:调用函数,施加输入,看实际的输出 跟预期是否完全一致
通常由测试人员或QA工程师来执行,Web应用可以通过Selenium这样的测试框架自动化实施。
1.2 性能测试
软件在高工作负载下对其响应性和健壮性展开的测试。
- 负载测试:在特定负载下执行的测试。
- 压力测试:突发条件或极限条件下的性能测试。
1.3 安全性测试
系统的敏感数据都是经过认证和授权之后才能访问。
1.4 其他测试
易用性测试 / 安装测试 / 可访问性测试
2 测试方法
2.1 单元测试
针对程序中最小的功能单元所进行的测试,通过对实际输出和预期输出的比对以及各种的断言条件来判定被测单元是否满足设计需求
在Python程序中,最小的功能单元是函数和对象的方法
- 测试用例
- 测试固件 - 每次测试时都要使用的东西。
- 测试套件(测试集)- 组合了多个测试用例而构成的集合。
# Python中的测试模块unittest,可以辅助我们编写测试用例 但不太好用,
# 一般使用第三方pytest,再加pytest-cov(帮助检查是否覆盖到所有的代码)
核心类:TestCase
# 步骤:
1.自定义测试类(习惯:以Test开头或 XxxTest 驼峰命名 eg:TestFoo),继承TestCase
2.在测试类中,定义以 'test_' 开头的方法 ---> 凡是这种方法都会被当做测试执行
3.在测试方法中,使用断言语句来判断实际的输出跟预期输出是否完全一致
4.在测试类 或 测试类方法中 左边右击小箭头就可以执行
或 在命令终端中
python -m unittest test.py # 整个文件都一起测试执行
python -m unittest test.测试类[.测试方法] # 测试到某个具体的测试类或测试方法
5.若结果不匹配,则会抛出AssertionError 断言错误
# 注意:
测试的数据,一定要有代表性 其中方法:划分等价类 / 边界值 / 因果图 ===> 书《软件测试的艺术》
# TestCase 的断言方法:
assertEqual / assertNotEqual
assertTrue / assertFalse / assertIsNotassertRaise / assertRaiseRegexp
assertAlmostEqual / assertNotAlmostEqual
assertGreater / assertGreaterEqual / assertLess / assertLessEqual
assertRegexpMatches / assertNotRegexpMatches
assertListEqual / assertSetEqual / assertTupleEqual / assertDictEqual
eg:
# 被测试函数
def foo(a: str, b: str) -> bool:
"""
判断两个字符串是否完全相等
"""
if len(a) != len(b):
return False
temp = {}
for ch in a:
temp[ch] = temp.get(ch, 0) + 1 # 对a字符串每个字母出现的次数,计数
for ch in b:
if ch not in temp:
return False
temp[ch] -= 1
if temp[ch] < 0:
return False
return True
# 测试用例
from unittest import TestCase
class TestFoo(TestCase):
def test_with_same_string(self):
"""测试完全相同的字符串"""
self.assertTrue(foo('hello', 'hello'))
self.assertTrue(foo('你好', '你好'))
self.assertTrue(foo('日语', '日语'))
def test_with_same_string_diff_order(self):
"""测试相同字符但出现顺序不同的字符串"""
self.assertTrue(foo('hello', 'llohe'))
self.assertTrue(foo('我爱你', '你爱我'))
def test_with_diff_string_same_length(self):
"""测试不同字符但长度相同的字符串"""
self.assertFalse(foo('hello', 'hallo'))
self.assertFalse(foo('我爱你', '你爱他'))
def test_with_diff_length_string(self):
"""测试不同长度的字符串"""
self.assertFalse(foo('hello', ' hello'))
self.assertFalse(foo('hello', 'hhello'))
self.assertFalse(foo('hello', 'helloo'))
self.assertFalse(foo('我爱你', '你爱他 '))
def test_with_big_same_string(self):
"""测试很大长度相同的字符串"""
self.assertTrue(foo('a'*10000, 'a'*10000))
self.assertTrue(foo('我爱你'*10000, '你爱我'*10000))
def test_with_big_diff_string(self):
"""测试很大长度不同的字符串"""
self.assertFalse(foo('a'*10000, 'b'*10000))
self.assertTrue(foo('我爱你'*10000, '你爱我'*10000))
辅助执行单元测试
可以使用nose2
或pytest
来辅助执行单元测试,同时通过cov-core
或pytest-cov
可以对测试覆度进行评估
覆盖率由百分比表示。比如测试代码执行过了程序的每一行,那么覆盖率就是100%。这种时候,几乎不会出现新程序上线后突然无法运行的尴尬情况。
覆盖率不关心代码内容究竟是什么,覆盖率是用来检查“测试代码不足、测试存在疏漏”的一个指标,“测试内容是否妥当”并不归它管。
pip install nose2 cov-core pytest pytest-cov
# 执行
pytest test.py
pytest --verbose --cov=[被测试代码的文件名] test.py # --verbose 显示详细信息 --cov 显示覆盖率的详细报告
eg:pytest --verbose --cov=a111 test.py
Web应用的自动化测试
Selenium
可以使用Selenium
来实现Web应用的自动化测试
它还可用于屏幕抓取与浏览器行为模拟,也可以使用它抓取页面上的动态数据
Selenium其实包括三个部分:
-
Selenium IDE:嵌入到浏览器的插件,可以录制和回放脚本。
-
Selenium WebDriver:支持多种语言可以操控浏览器的API。
-
Selenium Standalone Server:Selenium Grid、远程控制、分布式部署。
pip install selenium
from selenium import webdriver
import pytest
import contextlib
@pytest.fixture(scope='session')
def chrome():
# 设置使用无头浏览器(不会打开浏览器窗口)
options = webdriver.ChromeOptions()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
yield driver
driver.quit()
def test_baidu_index(chrome):
chrome.get('https://www.baidu.com')
assert chrome.title == '百度一下,你就知道'
Robot Framework
除了Selenium之外,还有一个Web自动化测试工具名叫Robot Framework
nose2 -v -C
pytest --cov
Ran 7 tests in 0.002s
OK
Name Stmts Miss Cover
----------------------------------------------
example01.py 15 0 100%
example02.py 49 49 0%
example03.py 22 22 0%
example04.py 61 61 0%
example05.py 29 29 0%
example06.py 39 39 0%
example07.py 19 19 0%
example08.py 27 27 0%
example09.py 18 18 0%
example10.py 19 19 0%
example11.py 22 22 0%
example12.py 28 28 0%
example13.py 28 28 0%
test_ddt_example.py 18 0 100%
test_pytest_example.py 11 6 45%
test_unittest_example.py 22 0 100%
----------------------------------------------
TOTAL 427 367 14%
在测试过程中需要孤立各种外部依赖(数据库、外部接口调用、时间依赖),具体又包括两个方面:
- 数据源:数据本地化 / 置于内存中 / 测试之后回滚
- 资源虚拟化:存根/桩(stub)、仿制/模拟(mock)、伪造(fake)
- stub:测试期间为提供响应的函数生成的替代品
- mock:代替实际对象(以及该对象的API)的对象
- fake:没有达到生产级别的轻量级对象
2.2 集成测试
集成多个函数或方法的输入输出的测试,测试时需要将多个测试对象组合在一起。
- 测试组件互操作性 / 需求变更测试 / 外部依赖和API / 调试硬件问题 / 在代码路径中发现异常
2.3 系统测试
对需求的测试,测试成品是否最终满足了所有需求,在客户验收项目时进行。
2.4 数据驱动测试
使用外部数据源实现对输入值与期望值的参数化,避免在测试中使用硬编码的数据。
被测函数:
def add(x, y):
return x + y
data.csv文件:
3,1,2
0,1,-1
100,50,50
100,1,99
15,7,8
测试代码:
import csv
from unittest import TestCase
from ddt import ddt, data, unpack
@ddt
class TestAdd(TestCase):
def load_data_from_csv(filename):
data_items = []
with open(filename, 'r', newline='') as fs:
reader = csv.reader(fs)
for row in reader:
data_items.append(list(map(int, row)))
return data_items
@data(*load_data_from_csv('data.csv'))
@unpack
def test_add(self, result, param1, param2):
self.assertEqual(result, add(param1, param2))
Django中的测试
-
测试Django的视图
Django中提供的
TestCase
扩展了unittest
中的TestCase
,绑定了一个名为client
的属性,可以用来模拟浏览器发出的GET、POST、DELETE、PUT等请求。
class SomeViewTest(TestCase):
def test_example_view(self):
resp = self.client.get(reverse('index'))
self.assertEqual(200, resp.status_code)
self.assertEqual(5, resp.context['num'])
- 运行测试 - 配置测试数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': 'localhost',
'PORT': 3306,
'NAME': 'DbName',
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASS'],
'TEST': {
'NAME': 'DbName_for_testing',
'CHARSET': 'utf8',
},
}
}
python manage.py test
python manage.py test common
python manage.py test common.tests.UtilsTest
python manage.py test common.tests.UtilsTest.test_to_md5_hex
- 评估测试覆盖度
pip install coverage
coverage run --source=<path1> --omit=<path2> manage.py test common
coverage report
Name Stmts Miss Cover
---------------------------------------------------
common/__init__.py 0 0 100%
common/admin.py 1 0 100%
common/apps.py 3 3 0%
common/forms.py 16 16 0%
common/helper.py 32 32 0%
common/middlewares.py 19 19 0%
common/migrations/__init__.py 0 0 100%
common/models.py 71 2 97%
common/serializers.py 14 14 0%
common/tests.py 14 8 43%
common/urls_api.py 3 3 0%
common/urls_user.py 3 3 0%
common/utils.py 22 7 68%
common/views.py 69 69 0%
---------------------------------------------------
TOTAL 267 176 34%
2.5 性能测试
问题1:性能测试的指标有哪些?
-
ab( Apache Benchmark) / webbench / httpperf
yum -y install httpd ab -c 10 -n 1000 http://www.baidu.com/ ... Benchmarking www.baidu.com (be patient).....done Server Software: BWS/1.1 Server Hostname: www.baidu.com Server Port: 80 Document Path: / Document Length: 118005 bytes Concurrency Level: 10 Time taken for tests: 0.397 seconds Complete requests: 100 Failed requests: 98 (Connect: 0, Receive: 0, Length: 98, Exceptions: 0) Write errors: 0 Total transferred: 11918306 bytes HTML transferred: 11823480 bytes Requests per second: 252.05 [#/sec] (mean) Time per request: 39.675 [ms] (mean) Time per request: 3.967 [ms] (mean, across all concurrent requests) Transfer rate: 29335.93 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 6 7 0.6 7 9 Processing: 20 27 22.7 24 250 Waiting: 8 11 21.7 9 226 Total: 26 34 22.8 32 258 Percentage of the requests served within a certain time (ms) 50% 32 66% 34 75% 34 80% 34 90% 36 95% 39 98% 51 99% 258 100% 258 (longest request)
-
mysqlslap
mysqlslap -a -c 100 -h 1.2.3.4 -u root -p mysqlslap -a -c 100 --number-of-queries=1000 --auto-generate-sql-load-type=read -h <负载均衡服务器IP地址> -u root -p mysqlslap -a --concurrency=50,100 --number-of-queries=1000 --debug-info --auto-generate-sql-load-type=mixed -h 1.2.3.4 -u root -p
-
sysbench
sysbench --test=threads --num-threads=64 --thread-yields=100 --thread-locks=2 run sysbench --test=memory --num-threads=512 --memory-block-size=256M --memory-total-size=32G run
-
JMeter
请查看《使用JMeter进行性能测试》。
-
LoadRunner / QTP