Python3基础教程(十八)—— 测试
编写测试检验应用程序所有不同的功能。每一个测试集中在一个关注点上验证结果是不是期望的。定期执行测试确保应用程序按预期的工作。当测试覆盖很大的时候,通过运行测试你就有自信确保修改点和新增点不会影响应用程序。
测试范围
如果可能的话,代码库中的所有代码都要测试。但这取决于开发者,如果写一个健壮性测试是不切实际的,你可以跳过它。就像 Nick Coghlan(Python 核心开发成员) 在访谈里面说的:有一个坚实可靠的测试套件,你可以做出大的改动,并确信外部可见行为保持不变。
单元测试
这里引用维基百科的介绍:
在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
单元测试模块
在 Python 里我们有 unittest 这个模块来帮助我们进行单元测试。
阶乘计算程序
在这个例子中我们将写一个计算阶乘的程序 /home/shiyanlou/factorial.py
:
- import sys
- def fact(n):
- """
- 阶乘函数
- :arg n: 数字
- :returns: n 的阶乘
- """
- if n == 0:
- return 1
- return n * fact(n -1)
- def div(n):
- """
- 只是做除法
- """
- res = 10 / n
- return res
- def main(n):
- res = fact(n)
- print(res)
- if __name__ == '__main__':
- if len(sys.argv) > 1:
- main(int(sys.argv[1]))
运行程序:
- $ python3 factorial.py 5
第一个测试用例
测试哪个函数?
正如你所看到的, fact(n)
这个函数执行所有的计算,所以我们至少应该测试这个函数。
编辑 /home/shiyanlou/factorial_test.py
文件,代码如下:
- import unittest
- from factorial import fact
- class TestFactorial(unittest.TestCase):
- """
- 我们的基本测试类
- """
- def test_fact(self):
- """
- 实际测试
- 任何以 `test_` 开头的方法都被视作测试用例
- """
- res = fact(5)
- self.assertEqual(res, 120)
- if __name__ == '__main__':
- unittest.main()
运行测试:
- $ python3 factorial_test.py
- .
- ----------------------------------------------------------------------
- Ran 1 test in 0.000s
- OK
说明
我们首先导入了 unittest 模块,然后测试我们需要测试的函数。
测试用例是通过子类化 unittest.TestCase
创建的。
现在我们打开测试文件并且把 120 更改为 121,然后看看会发生什么?
各类assert函数
New in | ||
---|---|---|
assertEqual(a, b) | a == b |
|
assertNotEqual(a, b) | a != b |
|
assertTrue(x) | bool(x) is True |
|
assertFalse(x) | bool(x) is False |
|
assertIs(a, b) | a is b |
2.7 |
assertIsNot(a, b) | a is not b |
2.7 |
assertIsNone(x) | x is None |
2.7 |
assertIsNotNone(x) | x is not None |
2.7 |
assertIn(a, b) | a in b |
2.7 |
assertNotIn(a, b) | a not in b |
2.7 |
assertIsInstance(a, b) | isinstance(a, b) |
2.7 |
assertNotIsInstance(a, b) | not isinstance(a, b) |
2.7 |
异常检测
如果我们在 factorial.py
中调用 div(0)
,我们能看到异常被抛出。
我们也能测试这些异常,就像这样:
- self.assertRaises(ZeroDivisionError, div, 0)
完整代码:
- import unittest
- from factorial import fact, div
- class TestFactorial(unittest.TestCase):
- """
- 我们的基本测试类
- """
- def test_fact(self):
- """
- 实际测试
- 任何以 `test_` 开头的方法都被视作测试用例
- """
- res = fact(5)
- self.assertEqual(res, 120)
- def test_error(self):
- """
- 测试由运行时错误引发的异常
- """
- self.assertRaises(ZeroDivisionError, div, 0)
- if __name__ == '__main__':
- unittest.main()
mounttab.py
mounttab.py 中只有一个 mount_details()
函数,函数分析并打印挂载详细信息。
- import os
- def mount_details():
- """
- 打印挂载详细信息
- """
- if os.path.exists('/proc/mounts'):
- fd = open('/proc/mounts')
- for line in fd:
- line = line.strip()
- words = line.split()
- print('{} on {} type {}'.format(words[0],words[1],words[2]), end=' ')
- if len(words) > 5:
- print('({})'.format(' '.join(words[3:-2])))
- else:
- print()
- fd.close()
- if __name__ == '__main__':
- mount_details()
重构 mounttab.py
现在我们在 mounttab2.py 中重构了上面的代码并且有一个我们能容易的测试的新函数 parse_mounts()
。
- import os
- def parse_mounts():
- """
- 分析 /proc/mounts 并 返回元祖的列表
- """
- result = []
- if os.path.exists('/proc/mounts'):
- fd = open('/proc/mounts')
- for line in fd:
- line = line.strip()
- words = line.split()
- if len(words) > 5:
- res = (words[0],words[1],words[2],'({})'.format(' '.join(words[3:-2])))
- else:
- res = (words[0],words[1],words[2])
- result.append(res)
- fd.close()
- return result
- def mount_details():
- """
- 打印挂载详细信息
- """
- result = parse_mounts()
- for line in result:
- if len(line) == 4:
- print('{} on {} type {} {}'.format(*line))
- else:
- print('{} on {} type {}'.format(*line))
- if __name__ == '__main__':
- mount_details()
同样我们测试代码,编写 mounttest.py 文件:
- #!/usr/bin/env python
- import unittest
- from mounttab2 import parse_mounts
- class TestMount(unittest.TestCase):
- """
- 我们的基本测试类
- """
- def test_parsemount(self):
- """
- 实际测试
- 任何以 `test_` 开头的方法都被视作测试用例
- """
- result = parse_mounts()
- self.assertIsInstance(result, list)
- self.assertIsInstance(result[0], tuple)
- def test_rootext4(self):
- """
- 测试找出根文件系统
- """
- result = parse_mounts()
- for line in result:
- if line[1] == '/' and line[2] != 'rootfs':
- self.assertEqual(line[2], 'ext4')
- if __name__ == '__main__':
- unittest.main()
运行程序
- $ python3 mounttest.py
- ..
- ----------------------------------------------------------------------
- Ran 2 tests in 0.001s
- OK
测试覆盖率
测试覆盖率是找到代码库未经测试的部分的简单方法。它并不会告诉你的测试好不好。
在 Python 中我们已经有了一个不错的覆盖率工具来帮助我们。你可以在实验楼环境中安装它:
- $ sudo pip3 install coverage
覆盖率示例
- $ coverage3 run mounttest.py
- ..
- ----------------------------------------------------------------------
- Ran 2 tests in 0.013s
- OK
- $ coverage3 report -m
- Name Stmts Miss Cover Missing
- --------------------------------------------
- mounttab2.py 22 7 68% 16, 25-30, 34
- mounttest.py 14 0 100%
- --------------------------------------------
- TOTAL 36 7 81%
我们还可以使用下面的命令以 HTML 文件的形式输出覆盖率结果,然后在浏览器中查看它。
总结
本实验了解了什么是单元测试,unittest 模块怎么用,测试用例怎么写。以及最后我们使用第三方模块 coverage 进行了覆盖率测试。
在实际生产环境中,测试环节是非常重要的的一环,即便志不在测试工程师,但以后的趋势就是 DevOps,所以掌握良好的测试技能也是很有用的。
参考链接:https://www.shiyanlou.com/courses/596
Python3基础教程(十八)—— 测试的更多相关文章
- ComicEnhancerPro 系列教程十八:JPG文件长度与质量
作者:马健邮箱:stronghorse_mj@hotmail.com 主页:http://www.comicer.com/stronghorse/ 发布:2017.07.23 教程十八:JPG文件长度 ...
- Python3基础(十二) 学习总结·附PDF
Python是一门强大的解释型.面向对象的高级程序设计语言,它优雅.简单.可移植.易扩展,可用于桌面应用.系统编程.数据库编程.网络编程.web开发.图像处理.人工智能.数学应用.文本处理等等. 在学 ...
- Bootstrap <基础二十八>列表组
列表组.列表组件用于以列表形式呈现复杂的和自定义的内容.创建一个基本的列表组的步骤如下: 向元素 <ul> 添加 class .list-group. 向 <li> 添加 cl ...
- css3基础教程十六变形与动画animation
前面我们讲过的变形与动画一般都是通过鼠标的单击.获得焦点,被点击或对元素进行一定改变后以后触发效果的,那么有没有像Flash一样自动播放的动画效果呢?答案当然是肯定的,这就是我们今天要讲到的anima ...
- SpringCloud2.0 Zuul 网关路由 基础教程(十)
1.启动基础工程 1.1.启动[服务注册中心],工程名称:springcloud-eureka-server 参考 SpringCloud2.0 Eureka Server 服务中心 基础教程(二) ...
- Python3基础教程(二十)—— flask介绍
基本概念 什么是Flask? Flask 是一个 web 框架.也就是说 Flask 为你提供工具,库和技术来允许你构建一个 web 应用程序.这个 web 应用程序可以是一些 web 页面.博客.w ...
- Python3基础教程(十九)—— 项目结构
本节阐述了一个完整的 Python 项目结构,你可以使用什么样的目录布局以及怎样发布软件到网络上. 创建Python项目 我们的实验项目名为 factorial,放到 /home/shiyanlou/ ...
- Python3基础教程(十五)—— PEP8 代码风格指南
编程语言不是艺术,而是工作或者说是工具,所以整理并遵循一套编码规范是十分必要的. 这篇文章原文实际上来自于这里:https://www.python.org/dev/peps/pep-0008/ 有很 ...
- Python3基础教程(十六)—— 迭代器、生成器、装饰器
在这个实验里我们学习迭代器.生成器.装饰器有关知识. 这几个概念是 Python 中不容易理解透彻的概念,务必把所有的实验代码都完整的输入并理解清楚其中每一行的意思. 迭代器 Python 迭代器(I ...
随机推荐
- DP专辑之线性DP
POJ1390 题目链接:http://poj.org/problem?id=1390 分类:记忆化搜索 dp[i][j][k] 表示,从i到j块且j后面有k块与第j块的颜色一样.dp[l][r][k ...
- win7Setx修改环境变量
SETX.exe (Resource Kit, Windows 7) Set environment variables permanently, SETX can be used to set En ...
- .NETFramework:HttpContext
ylbtech-.NETFramework:HttpContext 1.返回顶部 1. #region 程序集 System.Web, Version=4.0.0.0, Culture=neutral ...
- linux 下 读取某个文件的某一行或者某几行
wc -l a.txt 统计a.txt 行数 查看文件a.txt的第190行到196行, sed -n '190,196p' a.txt 如果查看某一行用 sed -n 'a,bp' a.txt ...
- Integrate Your Code with the Frameworks---整合你的代码和框架
Back to Frameworks Integrate Your Code with the Frameworks When you develop an app for OS X or iOS, ...
- hdoj1024【DP.最 大 m 字 段 和】(写完我都怕。。。不忍直视。。)
弱弱上路,看了好多题解....[A的] 题意就是求最大m子段和. 我们先用a[1e6+7]存入数据: 定义:DP[ i , j ] 为前 j 个元素的 i 个子段的最大和,且第 i 个子段中包含了元素 ...
- robotframework自动化系列:文本类型的下拉框
对于下拉框定位和输入,这里主要遇到有两种类型的下拉选择. 其中一个类型是select-options格式,如图 这种方式的定位可以使用select from list by value或select ...
- CSS选择器优先级【转】
样式的优先级 多重样式(Multiple Styles):如果外部样式.内部样式和内联样式同时应用于同一个元素,就是使多重样式的情况. 一般情况下,优先级如下: (外部样式)External styl ...
- 解决 Cordova命令突然无法使用问题.
问题背景 之前一直在做 Cordova 方面, 然后准备自己尝试使用 Vue + WebPack 再配合 Cordova 做一个 App . 更新了 npm , 然后然后, 我的 cordova 这个 ...
- h5-21-文件操作-读取文件内容
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...