Python自定义密码验证:重构与单元测试实战

七月
七月
发布时间:2026-10-16| 2人浏览过
原创

本文详解Python中自定义密码验证功能的单元测试与重构过程。通过编写针对contains_character、is_valid_size及is_valid_password的测试用例,发现并修复了未检查有效特殊字符的逻辑缺陷。最终实现符合长度、大小写、数字及特殊字符要求的健壮密码验证逻辑。

本系列文章共分为三个部分。

第一部分介绍密码验证功能。

第二部分对密码验证函数进行重构。

第三部分对密码验证功能进行单元测试。

这是Python系列中自定义密码验证的第三部分,也是最后一部分。我们将重点讨论如何对密码验证功能进行单元测试。

下面是重构后的代码:

from string import (
    ascii_lowercase, ascii_uppercase,
    digits, punctuation, whitespace)
    
def contains_character(password: str = "", sack: str = "") -> bool:
    has_char = False
    for char in password:
        if char in sack:
            has_char = True
            break
    return has_char
    
def is_valid_size(password: str = "") -> bool:
    MIN_SIZE = 6
    MAX_SIZE = 20
    password_size = len(password)

    return MIN_SIZE <= password_size <= MAX_SIZE
    
def get_invalid_chars():
    valid_chars = {'-', '_', '.', '!', '@', '#', '$', '^', '&', '(', ')'}
    invalid_chars = set(punctuation + whitespace) - valid_chars

    return "".join(invalid_chars)

def is_valid_password(password: str = "") -> bool:
    try:
        if not password:
            return False

        new_password = password.strip()

        if not is_valid_size(new_password):
            return False

        invalid_chars = get_invalid_chars()

        if contains_character(new_password, invalid_chars):
            return False

        if not contains_character(new_password, digits):
            return False

        if not contains_character(new_password, ascii_lowercase):
            return False

        if not contains_character(new_password, ascii_uppercase):
            return False

        return True
    except:
        return False

我们的目标是为上述代码片段编写单元测试。通过这种方式,我们可以捕捉隐藏的错误,并在修复代码以通过测试的过程中持续进行重构。

在运行测试之前

在开始之前,有几点需要注意:

  • 这将是一个单元测试。
  • 我们将利用Python内置的unittest模块。
  • 我们将分别测试contains_character、is_valid_size和is_valid_password函数。
  • 测试文件命名为test.py,而上述代码片段可能位于app.py中(文件名由你决定)。
  • 我们将参考以下测试用例。

测试contains_character

contains_character返回布尔值,即True或False。因此,我们可以使用assertTrue和assertFalse方法。

我们将测试以下情况:

  • 当未传递密码和sack(无参数)时。
  • 当字符"i"在字符串"python"中时。
  • 当字符串"py"在字符串"python"中时。
  • 当字符串"python"在字符串"python"中时。

某些情况,例如传递int作为password或list作为sack,我们不会在此测试。(你应该为此编写测试)。

TestContainsCharacter类

import unittest
from app import contains_character


class TestContainsCharacter(unittest.TestCase):
    def test_empty_password_or_and_empty_sack(self):
        self.assertFalse(contains_character())

    def test_char_i_in_str_python(self):
        self.assertFalse(contains_character("i", "python"))

    def test_str_py_in_str_python(self):
        self.assertTrue(contains_character("py", "python"))

    def test_str_python_in_str_python(self):
        self.assertTrue(contains_character("python", "python"))

if __name__ == "__main__":
    unittest.main()

我们可以按Ctrl+F5运行此脚本(test.py)而无需调试。我们可以通过以下方式运行脚本:python3 test.py 或 python3 -m unittest test.py。所有这些测试都应该通过。

测试is_valid_size

is_valid_size返回布尔值,即True或False。因此,我们可以使用assertTrue和assertFalse方法。

我们将测试以下情况:

  • 对于空密码或未传递参数时。
  • 四个字符的密码。
  • 六个字符的密码。
  • 十六个字符的密码。
  • 二十个字符的密码。
  • 21个字符的密码。

TestIsValidSize类

import unittest
from app import is_valid_size


class TestIsValidSize(unittest.TestCase):
    def test_empty_password(self):
        self.assertFalse(is_valid_size(""))

    def test_4_char_password(self):
        self.assertFalse(is_valid_size("pass"))

    def test_6_char_password(self):
        self.assertTrue(is_valid_size("passwd"))

    def test_16_char_password(self):
        self.assertTrue(is_valid_size("ThisIs1Password!"))

    def test_20_char_password(self):
        self.assertTrue(is_valid_size("ThisIs1Password!+20"))

    def test_21_char_password(self):
        self.assertFalse(is_valid_size("ThisIs1Password!+20&"))


if __name__ == "__main__":
    unittest.main()

所有这些测试都应该通过。

测试is_valid_password

is_valid_password返回布尔值,即True或False。因此,我们可以使用assertTrue和assertFalse方法。

我们将测试以下情况:

1. 对于空密码。

2. 三个字符的密码。

3. 十个字符的密码。

4. 二十个字符的密码。

5. 对于包含无效特殊字符(如分号)的密码。

6. 对于没有数字的密码。

7. 对于没有小写字母的密码。

8. 对于没有大写字母的密码。

9. 对于没有有效特殊字符的密码。

10. 对于有效的密码。

  • 密码长度在[6-20]以内。
  • 至少包含一个小写和大写字符。
  • 至少包含一个数字。
  • 不包含无效的特殊字符。

TestIsValidPassword类

class TestIsValidPassword(unittest.TestCase):
    def test_empty_password(self):
        self.assertFalse(is_valid_password())

    def test_password_of_size_three(self):
        self.assertFalse(is_valid_password("pas"))

    def test_password_of_size_ten(self):
        self.assertFalse(is_valid_password("Password12"))
        self.assertTrue(is_valid_password("Password1_"))

    def test_password_of_size_twenty(self):
        self.assertFalse(is_valid_password("Password12Password_$"))

    def test_password_with_invalid_special_character_semicolon(self):
        self.assertFalse(is_valid_password("Password1_;"))
        self.assertFalse(is_valid_password("Password1;"))

    def test_password_with_no_digit(self):
        self.assertFalse(is_valid_password("Password_"))

    def test_password_with_no_lowercase(self):
        self.assertFalse(is_valid_password("PASSWORD1_"))

    def test_password_with_no_uppercase(self):
        self.assertFalse(is_valid_password("password1_"))

    def test_password_without_valid_special_character(self):
        self.assertFalse(is_valid_password("Password1"))

    def test_valid_password(self):
        self.assertTrue(is_valid_password("Password1_"))
        self.assertTrue(is_valid_password("PassWord34$"))


if __name__ == "__main__":
    unittest.main()

并非所有测试都通过了。这些测试用例本应失败——我们期望它们不会通过。因此,当我们期望False时却得到了True。这表明代码中存在缺陷或错误。

以下测试未通过:

  • test_password_of_size_ten : self.assertFalse(is_valid_password("Password12")) 应该是False,因为即使长度有效,它也没有特殊字符。
  • test_password_without_valid_special_character : self.assertFalse(is_valid_password("Password1")) 应该是False,因为没有有效的特殊字符。

is_valid_password函数未检查是否存在有效的特殊字符。它仅检查无效字符,但不检查有效字符。这是由有缺陷的假设造成的,即只要密码不包含无效字符,它就包含有效字符(包括有效的特殊字符)。

重构is_valid_password

既然我们已经指出了bug,我们应该做出更改并重新运行测试。

需要进行的更改:

在get_invalid_chars中,我们定义了有效特殊字符集合valid_chars。让我们使其对所有函数全局可见(例如,将其从get_invalid_chars函数中提取并放在文件顶部)。为了确保其他地方没有损坏,运行测试(我们预计有两种情况会失败)。请注意,即使我们移动valid_chars,get_invalid_chars也应能正常工作。

valid_chars是一个集合,它可以用作get_invalid_chars中的一组。contains_character需要string类型的sack作为参数。我们必须将valid_chars解析为string。让我们在下面创建一个函数get_valid_chars,返回string版本的valid_chars。

def get_valid_chars():
    return "".join(valid_chars)

进行测试。

让我们检查is_valid_password中的有效字符,通过在return True语句中的try块内添加检查。

if not contains_character(new_password, get_valid_chars()):
    return False

进行测试。现在,所有测试都通过了。万岁!!:clap::clap::clap:

这更多的是重新排列代码,使is_valid_password在另一种环境中自然运行良好。我们将按以下顺序重新排列is_valid_password的测试:size、lower case、upper case、digit、invalid special character 和 valid special character。

结论

is_valid_password在app.py中类似于以下代码片段:

from string import (ascii_lowercase, ascii_uppercase, digits, punctuation,
                    whitespace)
valid_chars = {'-', '_', '.', '!', '@', '#', '$', '^', '&', '(', ')'}


def contains_character(password: str = "", sack: str = "") -> bool:
    has_char = False

    for char in password:
        if char in sack:
            has_char = True
            break

    return has_char


def is_valid_size(password: str = "") -> bool:
    MIN_SIZE = 6
    MAX_SIZE = 20
    password_size = len(password)

    return MIN_SIZE <= password_size <= MAX_SIZE


def get_invalid_chars():
    invalid_chars = set(punctuation + whitespace) - valid_chars
    return "".join(invalid_chars)


def get_valid_chars():
    return "".join(valid_chars)


def is_valid_password(password: str = "") -> bool:
    try:
        if not password:
            return False

        new_password = password.strip()

        if not is_valid_size(new_password):
            return False

        if not contains_character(new_password, ascii_lowercase):
            return False

        if not contains_character(new_password, ascii_uppercase):
            return False

        if not contains_character(new_password, digits):
            return False

        if contains_character(new_password, get_invalid_chars()):
            return False

        if not contains_character(new_password, get_valid_chars()):
            return False

        return True
    except:
        return False

单元测试在test.py中类似于以下代码片段:

import unittest
from app import (contains_character, is_valid_size, is_valid_password)


class TestContainsCharacter(unittest.TestCase):
    def test_empty_password_or_and_empty_sack(self):
        self.assertFalse(contains_character())

    def test_char_i_in_str_python(self):
        self.assertFalse(contains_character("i", "python"))

    def test_str_py_in_str_python(self):
        self.assertTrue(contains_character("py", "python"))

    def test_str_python_in_str_python(self):
        self.assertTrue(contains_character("python", "python"))


class TestIsValidSize(unittest.TestCase):
    def test_empty_password(self):
        self.assertFalse(is_valid_size(""))

    def test_4_char_password(self):
        self.assertFalse(is_valid_size("pass"))

    def test_6_char_password(self):
        self.assertTrue(is_valid_size("passwd"))

    def test_16_char_password(self):
        self.assertTrue(is_valid_size("ThisIs1Password!"))

    def test_20_char_password(self):
        self.assertTrue(is_valid_size("ThisIs1Password!/+20"))

    def test_21_char_password(self):
        self.assertFalse(is_valid_size("ThisIs1Password!/+20&"))


class TestIsValidPassword(unittest.TestCase):
    def test_empty_password(self):
        self.assertFalse(is_valid_password())

    def test_password_of_size_three(self):
        self.assertFalse(is_valid_password("pas"))

    def test_password_of_size_ten(self):
        self.assertFalse(is_valid_password("Password12"))
        self.assertTrue(is_valid_password("Password1_"))

    def test_password_of_size_twenty(self):
        self.assertTrue(is_valid_password("Password12Password_$"))

    def test_password_with_invalid_special_character_semicolon(self):
        self.assertFalse(is_valid_password("Password1_;"))
        self.assertFalse(is_valid_password("Password1;"))

    def test_password_with_no_digit(self):
        self.assertFalse(is_valid_password("Password_"))

    def test_password_with_no_lowercase(self):
        self.assertFalse(is_valid_password("PASSWORD1_"))

    def test_password_with_no_uppercase(self):
        self.assertFalse(is_valid_password("password1_"))

    def test_password_without_valid_special_character(self):
        self.assertFalse(is_valid_password("Password1"))

    def test_valid_password(self):
        self.assertTrue(is_valid_password("Password1_"))
        self.assertTrue(is_valid_password("PassWord34$"))


if __name__ == "__main__":
    unittest.main()

以上就是详解Python中的自定义密码验证的详细内容,更多关于Python密码验证的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。