unittest详解-你给我过来呀!

本文介绍Python单元测试模块unittest的基本使用方法,包括测试案例编写、异常情况处理及测试跳过等技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最初,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,让它们更健壮吧。喜欢的我,别忘了一键三连哦。原创不易,感谢观看

<think>根据用户需求,用户希望使用unittest框架测试登录、修改密码和登出功能的示例代码。我们将设计三个测试用例:test_login,test_change_password,test_logout。假设我们有一个简单的用户认证系统,我们将模拟这些功能。注意:在实际应用中,我们需要根据具体的应用逻辑(如Web框架、数据库操作等)来编写测试代码。这里我们将使用一个模拟的User类来代表用户系统,以便于编写测试。步骤:1.导入unittest模块。2.创建一个测试类,例如TestUserFunctions,继承unittest.TestCase。3.在测试类中,可以设置setUp和tearDown方法(如果需要)。4.编写测试方法:test_login,test_change_password,test_logout。5.在每个测试方法中,编写具体的测试步骤和断言。注意:由于登录、修改密码和登出通常是连续的操作,但测试用例之间应该是独立的。因此,每个测试用例都应该能够独立运行,不依赖于其他测试用例的状态。我们可以通过在每个测试用例开始前创建新的用户对象或重置状态来实现。下面是一个示例代码,我们模拟一个简单的用户认证系统:-用户登录:输入用户名和密码,返回登录是否成功。-修改密码:需要先登录,然后修改密码,再验证新密码。-登出:退出登录状态。注意:在实际测试中,我们可能需要模拟网络请求或使用测试数据库。这里为了简单,我们用一个内存中的用户字典来模拟。示例代码结构如下:</think>以下是使用unittest测试登录、修改密码和登出功能的示例代码,结合了引用[1][2][4][5]中的测试实践: ```python import unittest import logging from ddddocr import DdddOcr # 验证码识别库(引用[5]) from user_system import UserSystem # 假设的用户系统模块 # 配置测试日志(引用[2]) logging.basicConfig( filename='auth_test.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) class TestAuthFunctions(unittest.TestCase): """测试用户认证功能:登录/修改密码/登出""" @classmethod def setUpClass(cls): """创建测试用户""" cls.user = UserSystem() cls.user.create_user("test_user", "OldPass123!", "[email protected]") logging.info("测试用户创建完成") def setUp(self): """每个测试前的初始化""" self.user.reset_session() logging.info(f"开始执行测试: {self._testMethodName}") # 测试登录功能(引用[1]) def test_login_success(self): """测试成功登录场景""" result = self.user.login("test_user", "OldPass123!") self.assertTrue(result, "登录失败") self.assertEqual(self.user.get_current_user(), "test_user") logging.info("登录成功测试通过") # 验证码处理(引用[5]) def test_login_with_captcha(self): """测试带验证码的登录""" captcha_img = self.user.get_captcha_image() captcha = DdddOcr().classification(captcha_img) result = self.user.login("test_user", "OldPass123!", captcha) self.assertTrue(result, "验证码登录失败") # 测试修改密码功能 def test_password_change(self): """测试密码修改流程""" # 先登录 self.user.login("test_user", "OldPass123!") # 修改密码 result = self.user.change_password("OldPass123!", "NewPass456@") self.assertTrue(result, "密码修改失败") # 用新密码登录验证 self.user.logout() login_result = self.user.login("test_user", "NewPass456@") self.assertTrue(login_result, "新密码登录失败") logging.info("密码修改测试通过") # 测试登出功能(引用[4]) def test_logout(self): """测试登出功能""" self.user.login("test_user", "OldPass123!") self.user.logout() self.assertIsNone(self.user.get_current_user(), "登出失败") logging.info("登出功能测试通过") @classmethod def tearDownClass(cls): """清理测试数据""" cls.user.delete_user("test_user") logging.info("测试用户已清理") if __name__ == "__main__": # 使用标记运行特定测试(引用[1]) unittest.main(argv=['', '-m', 'login or change_password or logout']) ``` ### 关键实现说明: 1. **测试结构**: - `setUpClass`:创建测试用户(引用[4]的测试数据准备) - `setUp`/`tearDown`:每个测试前后的初始化和清理 - 独立测试方法:登录/修改密码/登出 2. **核心功能测试**: - **登录验证**:凭证校验和会话状态检查 - **密码修改**:包含旧密码验证和新密码生效检查 - **登出功能**:会话终止验证 3. **增强实践**: - 验证码处理:使用ddddocr库识别(引用[5]) - 日志跟踪:关键操作记录日志(引用[2]) - 标记执行:通过`-m`参数选择测试范围(引用[1]) 4. **断言方法**: - `assertTrue`:验证操作返回结果 - `assertIsNone`:验证登出后用户状态 - `assertEqual`:验证登录用户身份 ### 测试执行方式: ```bash # 运行全部认证测试 python test_auth.py # 选择性运行(引用[1]) python test_auth.py -m login # 仅登录测试 python test_auth.py -m change_password # 仅密码修改测试 ``` 此方案实现了: 1. 测试用例间的隔离(每个测试独立会话) 2. 关键操作的日志追踪 3. 自动化验证码处理 4. 灵活的测试组合执行 5. 符合unittest的标准测试结构 [^1]: 从以上运行结果可以看出,只有被选择的标记用例被运行 组合运行用例 [^2]: 在测试执行过程中,日志记录是跟踪测试状态和调试问题的关键 [^4]: 做过多接口联调的测试吗?...通过接口调试来进行测试 [^5]: dddd_ocr:识别验证码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chris就是我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值