最初,Python单元测试模块unittest是JUnit的一部分,该部分由Kent Beck和Erich Gamma于1997年创建。Unittest在2001年首次包含在Python 2.1版中,当时,JUnit是一个新兴标准,被翻译成多种编程语言。所有这些小弟都被称为xUnit框架,它们的工作方式基本上相同。当然,从那时起,JUnit和unittest模块都进行了很多改进,实际上它们不再完全相似。但是随着时间的流逝,这些原因变得不那么重要了。
废话不多说,直接上干货。
小试牛刀 - 第一个小案例
我现在有一个电话本的简单类,我想要对它进行简单的单元测试。
class PhoneBook:
def __init__(self):
self.numbers = {}
def add(self, name, number):
self.numbers[name] = number
def lookup(self, name):
return self.numbers[name]
给定你一个姓名和电话号码列表,可以制作电话本。也就是说,如果你知道某人的姓名,则可以在该目录中查找该人的电话号码。按照惯例,它的名称为test_phonebook,然后是你要测试的事物的名称,在本例中为电话本。在这里,我将使用单元测试框架。因此,我声明了一个新的测试类PhoneBookTest,它需要子类unittest.TestCase。然后,在此类中,我声明一个测试方法,该方法必须以test_开头。
import unittest
class PhoneBookTest(unittest.TestCase):
def test_lookup_by_name(self):
phonebook = PhoneBook()
phonebook.add("Bob","12345")
number = phonebook.lookup("Bob")
self.assertEqual("12345", number)
使用一下命令来进行测试
python -m unittest
得到如下的结果
Ran 1 test in 0.001s
OK
这是一个非常简单且正常的案例,再来看一个可能出现错误的案例Exception测试
def test_missing_name(self):
phonebook = PhoneBook()
with self.assertRaises(KeyError):
phonebook.lookup("missing")
同样的使用以上的命令,你会得到以下的结果
Ran 2 tests in 0.002s
OK
看,我还能大跳呢
现在,我们的测试是不是已经有了基本的功能了,可以测试正常的情况,也可以测试Exception的情况。
好,我们再来看一下其他的情况。比如你有一个测试用例正写了一半,同事叫你去吃饭,可是你又不希望运行的时候出现不必要的错误,这个时候,你可以把这个测试跳过,这个注解就是@unittest.skip(),我们的代码看上去就是这样子的。
@unittest.skip("work in progress")
def test_name_consistent(self):
pass
同样的使用运行命令之后,你会得到以下的结果,有没有看到OK之后,出现了一个skipped=1,就是表示跳过一个。
Ran 3 tests in 0.002s
OK (skipped=1)
官方文档里,实际上skip的注释有多个
-
@unittest.skipIf(condition, reason) Skip the decorated test if
condition is true. 只有满足条件的时候才会跳过测试。 -
@unittest.skipUnless(condition, reason) Skip the decorated test
unless condition is true. 除非条件满足否则跳过这个测试。 -
@unittest.expectedFailure Mark the test as an expected failure or
error. If the test fails or errors it will be considered a success.
If the test passes, it will be considered a failure.将测试标记为预期失败或
错误。如果测试失败或错误,则视为成功。如果测试通过,将被视为失败。
我这里就不一一举例了,记住你是一个有经验的程序员。自己搞定。
我要变得更好
你如果仔细观察我现有的例子中,是不是每一个都声明了一个PhoneBook(),我真的需要这样吗?是不是不需要。那怎么改进呢?我们将在每个测试用例执行之前构造一个新的电话本实例。这意味着我可以删除在每个测试用例中都重复的这一行,并将其替换为设置方法。这里就是要使用setUp,tearDown。在每个测试方法之前都会调用SetUp,然后在之后调用tearDown。它使你有机会释放你在setUp方法中或测试期间可能已保留的所有资源。在我们的例子中,电话本完全在内存中,实际上不需要释放。不过,在更一般的情况下,例如,如果您要创建文件或设置数据库集合,则可能需要使用tearDown方法来释放这些资源,但是由于我在这里不需要它,我可以只是使用pass。
import unittest
from phone_book import PhoneBook
class PhoneBookTest(unittest.TestCase):
def setUp(self) -> None:
self.phonebook = PhoneBook()
def tearDown(self) -> None:
pass
def test_lookup_by_name(self):
self.phonebook.add("Bob", "12345")
number = self.phonebook.lookup("Bob")
self.assertEqual("12345", number)
def test_missing_name(self):
with self.assertRaises(KeyError):
self.phonebook.lookup("missing")
@unittest.skip("work in progress")
def test_name_consistent(self):
pass
Test Case Design
看你这么有慧根,就传授一点官方文档看不到的东西。
Test Case的Name很重要
你写的Test case Name一定要做到看名之意。这样不仅显得你很专业,很会让你和别人将来看代码的时候省时间。比如我上面的例子,可以使用以下的名字作为Test Case。
- test_lookup_by_name
- test_missing_name
- test_consistent_when_empty
- test_consistent_when_all_different
- test_inconsistent_when_duplicates
如何构造方法的主体
说完了名字,现在来说一下构造方法的主体,就好像踢球时的433阵型一样,我认为测试用例应该包括三部分。首先,你需要安排设置要测试的对象以及所需的其他东东。然后就是一个开干,对被测单元进行测试,最后断言- 你日后必定大红大紫。这三个部分应该按此顺序进行,并且编写良好的测试,应该能够轻松挑选出每个部分。比如我上面的这个例子
def test_lookup_by_name(self):
# 给你安排的明明白白的
self.phonebook.add("Bob", "12345")
# 开干
number = self.phonebook.lookup("Bob")
# 断言
self.assertEqual("12345", number)
下面也是从官网抄下来的assert断言可以使用的方法。
我已经看到你激动的小手已经安奈不住了。赶紧来试着给你的程序加一点unittest,让它们更健壮吧。喜欢的我,别忘了一键三连哦。原创不易,感谢观看