这些帖子将分为三个部分。
- 密码验证功能
- 重构密码验证函数
- 对密码验证功能进行单元测试
这是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
我们的目标是为上面的代码片段编写单元测试。我们可以捕捉隐藏的错误,并在修复代码以通过测试时继续重构。
在测试:nut_and_bolt:?之前
有些事你应该知道:
- 这将是一个单元测试
- 我们将利用python的内置测试模块,
unittest - 我们将测试,
contains_character ?,?is_valid_size 和is_valid_password 整齐 - 测试将在
test.py 所以上面的片段可能在app.py (你选择你想要的名字) -
我们将参考
试验contains_character
contains_character 返回bool ,要么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 返回bool ,要么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 返回bool ,要么True 或者False 。所以我们可以使用assertTrue 还有assertFalse 方法。
我们将测试:
- 对于空密码
- 三个字符的密码
- 十个字符的密码
- 二十个字符的密码
- 对于包含无效特殊字符(如分号)的密码
- 对于没有数字的密码
- 对于没有小写字母的密码
- 对于没有大写字母的密码
- 对于没有有效特殊字符的密码
- 对于有效的密码
- 一个尺寸以内,[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 ,我们有set 有效的特殊字符,valid_chars 。让我们让它对所有函数都是全局的(例如,把它从get_invalid_chars 函数并将其放在函数的顶部)。为了确保某处没有损坏,运行测试(我们预计有两种情况会失败)。请注意,即使我们移动valid_chars 由于get_invalid_chars ?,?get_invalid_chars 应该还能正常工作。 -
这valid_chars 是一个set ,它可以用作中的一组get_invalid_chars ?.?contains_character 需要一段时间string ?sack 作为论据。我们必须解析valid_chars 如同string 。让我们在下面创建一个函数get_invalid_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 and 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()
请随意表达你的观点,如果你觉得有什么不足,请告诉我如何改进。
关注下方公众号领取完整代码
|