• Day28 单元测试


    系列文章目录

    Day01 软件测试基础总结

    Day02 测试用例知识点总结(上)

    Day03 测试用例知识点总结(下)

    Day04 禅道-从安装到卸载

    Day05 MySql的基础使用

    Day06 MySql知识点总结

    Day07 MySql知识点再总结与多表查询

    Day08 redis的基础知识

    Day08 VMware的安装、Linux系统安装和Linux基础命令

    Day09 Linux常用命令总结

    Day10 Linux环境部署和项目构建

    Day11 shell脚本基础知识

    Day12 接口和协议

    Day13 Postman的使用

    Day13 Windows环境下的JDK安装与Tomcat的启动

    Day14 jenkins部署

    Day15 jenkins的简单使用

    Day16 charles的基本使用

    Day17 考试

    Day18 考试

    Day19 Fiddler的简单使用

    Day20 Python基础

    Day21 python 语句基础

    Day22 Python数据类型(上)

    Day23 Python数据类型(下)

    Day24 Python函数

    Day25 Python的文件操作和异常处理

    Day26 Python面向对象

    Day27 Python的部分算法

    Day28 单元测试 unittest


    目录

    系列文章目录

    前言

    一、Unittest的重要组成部分

    1.TestFixture

    2.TestCase(测试用例)

    3.TestSuite

    4.TextRunner

    二、断言

    三、生成测试报告

    四、流程

    五、读取文件

    1.读取xml文件

     2.读取csv文件

    六、数据驱动

    1.ddt

    2. ddt处理各种类型数据

    1.分析ddt工作原理

    2.测试元组数据

     3.测试列表数据

    4.测试字典数据

     5.ddt测试字典列表数据结合

     6.ddt测试读取文件数据

    总结


    前言

            单元测试(unittest)是颗粒度最小,一般由开发小组采用白盒方式来测试,主要测试单元是否符合“设计”;是指对软件中的最小可测试单元进行检查和验证。


    一、Unittest的重要组成部分

            Python中有一个自带的单元测试框架是unittest模块,用它来做单元测试,它里面封装好了一些校验返回的结果方法(断言)和一些用例执行前的初始化操作。

            unittest中最核心的部分是:TestFixture,TestCase,TestSuite,TestRuuner

    1.TestFixture

    作用:用于一个测试环境的准备和销毁还原。

    功能:当测试用例每次执行之前需要准备测试环境,每次测试完成后还原测试环境,比如执行前连接数据库、打开浏览器等,执行完成后还需要还原数据库、关闭浏览器等操作。这时就可以启用testfixture

    主要方法:

    setup():准备环境,执行每个测试用例的前置条件;

    tearDown():环境还原,执行每个测试用例的后置条件;

    setUpClass():必须使用@classmethod装饰器,所有case执行的前置条件,只运行一次;

    tearDownClass():必须使用@classmethod装饰器,所有case运行完后只运行一次;

    1. import unittest
    2. class UnitTest(unittest.TestCase):
    3. @classmethod
    4. def setUpClass(cls) -> None:
    5. print('setUpClass')
    6. @classmethod
    7. def tearDownClass(cls) -> None:
    8. print('tearDownClass')
    9. def setUp(self) -> None:
    10. print('setUp')
    11. def tearDown(self) -> None:
    12. print('tearDown')
    13. def test_001(self):
    14. print('test_001')
    15. def test_002(self):
    16. print('test_002')
    17. if __name__ == '__main__':
    18. unittest.main()
    19. '''
    20. Ran 2 tests in 0.002s
    21. OK
    22. setUpClass
    23. setUp
    24. test_001
    25. tearDown
    26. setUp
    27. test_002
    28. tearDown
    29. tearDownClass
    30. '''

    2.TestCase(测试用例)

    定义:一个类(class)继承 unittest.TestCase 就是一个测试用例

            测试用例就是个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。

    测试用例命名规则:

            继承自unittest.TestCase的类中,测试方法的名称要以test开头。且只会执行以test开头定义的方法(测试方法),测试用例执行的顺序会按照方法名的ASCII值排序。
            如果想跳过某个测试用例,需要添加@unittest.skip(‘描述信息')

    1. import unittest
    2. class UnitTest(unittest.TestCase):
    3. @classmethod
    4. def setUpClass(cls) -> None:
    5. print('setUpClass')
    6. @classmethod
    7. def tearDownClass(cls) -> None:
    8. print('tearDownClass')
    9. def setUp(self) -> None:
    10. print('setUp')
    11. def tearDown(self) -> None:
    12. print('tearDown')
    13. @unittest.skip('不测试这个用例')
    14. def test_001(self):
    15. print('test_001')
    16. def test_002(self):
    17. print('test_002')
    18. if __name__ == '__main__':
    19. unittest.main()
    20. '''
    21. Ran 2 tests in 0.002s
    22. OK (skipped=1)
    23. setUpClass
    24. Skipped: 不测试这个用例
    25. setUp
    26. test_002
    27. tearDown
    28. tearDownClass
    29. '''

    3.TestSuite

    测试套件,可以将多个测试用例集合在一起,能一起执行选中的测试用例

    方式1:

    1. import unittest
    2. class UnitTest(unittest.TestCase):
    3. @classmethod
    4. def setUpClass(cls) -> None:
    5. print('setUpClass')
    6. @classmethod
    7. def tearDownClass(cls) -> None:
    8. print('tearDownClass')
    9. def setUp(self) -> None:
    10. print('setUp')
    11. def tearDown(self) -> None:
    12. print('tearDown')
    13. def test_001(self):
    14. print('test_001')
    15. def test_002(self):
    16. print('test_002')
    17. if __name__ == '__main__':
    18. suite=unittest.TestSuite()#创建测试套件
    19. case_list=['test_001','test_002']
    20. for case in case_list:
    21. suite.addTest(UnitTest(case))
    22. '''
    23. setUpClass
    24. setUp
    25. test_001
    26. tearDown
    27. setUp
    28. test_002
    29. tearDown
    30. tearDownClass
    31. Ran 2 tests in 0.002s
    32. OK
    33. '''

     方式2:

    1. import unittest
    2. class UnitTest(unittest.TestCase):
    3. @classmethod
    4. def setUpClass(cls) -> None:
    5. print('setUpClass')
    6. @classmethod
    7. def tearDownClass(cls) -> None:
    8. print('tearDownClass')
    9. def setUp(self) -> None:
    10. print('setUp')
    11. def tearDown(self) -> None:
    12. print('tearDown')
    13. def test_001(self):
    14. print('test_001')
    15. def test_002(self):
    16. print('test_002')
    17. if __name__ == '__main__':
    18. suite=unittest.TestSuite()#创建测试套件
    19. suite.addTest(UnitTest('test_001'))
    20. suite.addTest(UnitTest('test_002'))
    21. '''
    22. Ran 2 tests in 0.002s
    23. OK
    24. setUpClass
    25. setUp
    26. test_001
    27. tearDown
    28. setUp
    29. test_002
    30. tearDown
    31. tearDownClass
    32. '''

    方式3: 

    1. import unittest
    2. class UnitTest(unittest.TestCase):
    3. @classmethod
    4. def setUpClass(cls) -> None:
    5. print('setUpClass')
    6. @classmethod
    7. def tearDownClass(cls) -> None:
    8. print('tearDownClass')
    9. def setUp(self) -> None:
    10. print('setUp')
    11. def tearDown(self) -> None:
    12. print('tearDown')
    13. def test_001(self):
    14. print('test_001')
    15. def test_002(self):
    16. print('test_002')
    17. if __name__ == '__main__':
    18. suite=unittest.TestSuite()#创建测试套件
    19. loader=unittest.TestLoader()#创建一个加载对象
    20. suite.addTest(loader.loadTestsFromTestCase(UnitTest))
    21. '''
    22. setUpClass
    23. setUp
    24. test_001
    25. tearDown
    26. setUp
    27. test_002
    28. tearDown
    29. tearDownClass
    30. Ran 2 tests in 0.002s
    31. OK
    32. Process finished with exit code 0
    33. '''

    4.TextRunner

    执行测试用例
    通过TextTestRunner类提供的run()方法来执行test suite/test cas

    格式:

    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

    verbosity :表示测试报告信息的详细程度,一共三个值,默认是2
    0 (静默模式):你只能获得总的测试用例数和总的结果,如:总共100个 失败10 成功90
    1 (默认模式):类似静默模式,只是在每个成功的用例前面有个. 每个失败的用例前面有个F
    2 (详细模式):测试结果会显示每个测试用例的所有相关的信息

    二、断言

    验证预期结果和实际结果

    assertEqual(a,b):断言a和b是否相等,相等则测试用例通过。
    assertNotEqual(a,b):断言a和b是否相等,不相等则测试用例通过。
    assertTrue(x):断言x是否True,是True则测试用例通过。
    assertFalse(x):断言x是否False,是False则测试用例通过。
    assertIs(a,b):断言a是否是b,是则测试用例通过。
    assertNotIs(a,b):断言a是否是b,不是则测试用例通过。
    assertIsNone(x):断言x是否None,是None则测试用例通过。
    assertIsNotNone(x):断言x是否None,不是None则测试用例通过。
    assertIn(a,b):断言a是否在b中,在b中则测试用例通过。
    assertNotIn(a,b):断言a是否在b中,不在b中则测试用例通过。
    assertIsInstance(a,b):断言a是是b的一个实例,是则测试用例通过。
    assertNotIsInstance(a,b):断言a是是b的一个实例,不是则测试用例通过。

    三、生成测试报告

            生成html格式的测试报告是使用HTMLTestRunner,HTMLTestRunner是 Python 标准库的 unittest 框架的一个扩展,它可以生成一个直观清晰的 HTML 测试报告。使用的前提就是要下载 HTMLTestRunner.py

    1. from test.democsv import UnitTest #导入用例
    2. from common.HTMLTestRunner import HTMLTestRunner #导入HTMLTestRunner文件,外部扩展
    3. import unittest
    4. class TestHtmlReportClass():
    5. #生成测试报告
    6. def export_testhtmlreport(self):
    7. suite= unittest.TestSuite() #创建测试套件
    8. list_i=["测试用例名(即方法名)","test_001"]
    9. for i in list_i:
    10. suite.addTest(UnitTest(i)) #注意是用例类名
    11. with open("生成的报告的位置与报告名", "wb") as f:
    12. HTMLTestRunner(
    13. stream=f,
    14. title="单元测试",
    15. description="描述单元测试",
    16. verbosity=2
    17. ).run(suite)
    18. t = TestHtmlReportClass()
    19. t.export_testhtmlreport()

    四、流程

    1:导入unittest模块 >>>import unittest
    2:编写一个类继承unittest.TestCase
    3:调用setUp(self), tearDown(self)方法实现测试用例前后阶段的操作
    4:编写测试用例方法
            (1)该方法必须以test开头,否则在unittest.main()中调用测试找不到该方法
            (2)设置断言进行判断,输入数据和输出数据的预期结果
    5:创建套件,将多个测试用例存放套件中,一并执行()
    6:生成测试报告(python自带或者导入HTMLTestRunner生成html格式的测试报告)
    7:运行测试用例unittest.main(),调用测试用例中以test开头的方法

    五、读取文件

    1.读取xml文件

    xml文件存储数据使用尖括号,标签名可以自定义

    接下来展示完整流程:

    1.开发人员给的需要测试的代码块

    1. class Arithmetical(object):
    2. def sum(self,a,b):
    3. return a+b
    4. def subtract(self,a,b):
    5. return a-b

     2.写入的测试数据

    1. "1.0" encoding="utf-8" ?>
    2. <root>
    3. <add>
    4. <add1>1add1>
    5. <add2>5add2>
    6. <export>6export>
    7. add>
    8. <subtract>
    9. <subtract1>8subtract1>
    10. <subtract2>4subtract2>
    11. <export>4export>
    12. subtract>
    13. root>

    3. 读取xml数据

    1. from xml.dom import minidom
    2. class ReadXml(object):
    3. def readXml(self,filename,c1,c2):
    4. root=minidom.parse(filename)
    5. first=root.getElementsByTagName(c1)[0]
    6. second=first.getElementsByTagName(c2)[0].firstChild.data
    7. return second

     4.测试用例

    1. import unittest
    2. from tool.readxml import ReadXml
    3. from src.Arithmetical import Arithmetical
    4. t=ReadXml()
    5. a=Arithmetical()
    6. # t.readXml('../data/data.xml','add','add1')
    7. d='../data/data.xml'
    8. add_e=int(t.readXml(d,'add','export'))
    9. add_a1=int(t.readXml(d,'add','add1'))
    10. add_a2=int(t.readXml(d,'add','add2'))
    11. subtract_e=int(t.readXml(d,'subtract','export'))
    12. subtract_s1=int(t.readXml(d,'subtract','subtract1'))
    13. subtract_s2=int(t.readXml(d,'subtract','subtract2'))
    14. class UnitText(unittest.TestCase):
    15. def test_001(self):
    16. self.assertEqual(add_e,a.sum(add_a1,add_a2))
    17. def test_002(self):
    18. self.assertEqual(subtract_e,a.subtract(subtract_s1,subtract_s2))
    19. if __name__ == '__main__':
    20. unittest.main()

    5.生成测试报告 

    1. import unittest
    2. from test.demoxml import UnitText
    3. from common.HTMLTestRunner import HTMLTestRunner
    4. class HtmlTextReport():
    5. def createReport(self):
    6. suite=unittest.TestSuite()
    7. list_i=['test_001','test_002']
    8. for i in list_i:
    9. suite.addTest(UnitText(i))
    10. with open("../report/reportxml.html", "wb") as f:
    11. HTMLTestRunner(
    12. stream=f,
    13. title="单元测试",
    14. description="描述单元测试",
    15. verbosity=2
    16. ).run(suite)
    17. t = HtmlTextReport()
    18. t.createReport()

     2.读取csv文件

    csv中的数据使用英文逗号隔开,不同用例数据换行,最后读取,一行一个列表,所有列表整合在一个列表中

    1.开发人员给的需要测试的代码块

    1. class Arithmetical(object):
    2. def sum(self,a,b):
    3. return a+b
    4. def subtract(self,a,b):
    5. return a-b

     2.写入的测试数据

    1. 3,1,2
    2. 9,4,5
    3. 15,7,8

     3. 读取csv数据

    一行一个列表,所有列表整合在一个列表 l 中

    1. import csv
    2. class ReadCscv():
    3. def readCsv(self,file):
    4. l=[]
    5. c=csv.reader(open(file))
    6. for i in c:
    7. l.append(i)
    8. return l

    4. 测试用例

    1. import unittest
    2. from tool.readcsv import ReadCscv
    3. from src.Arithmetical import Arithmetical
    4. a=Arithmetical()
    5. t=ReadCscv()
    6. l=t.readCsv('../data/data.csv')
    7. class UnitTest(unittest.TestCase):
    8. def test_001(self):
    9. for i in l:
    10. self.assertEqual(int(i[0]),a.sum(int(i[1]),int(i[2])))
    11. if __name__ == '__main__':
    12. unittest.main()

     5.生成测试报告

    1. from test.democsv import UnitTest
    2. from common.HTMLTestRunner import HTMLTestRunner
    3. import unittest
    4. class TestHtmlReportClass():
    5. #生成测试报告
    6. def export_testhtmlreport(self):
    7. suite= unittest.TestSuite() #创建测试套件
    8. list_i=["test_001"]
    9. for i in list_i:
    10. suite.addTest(UnitTest(i))
    11. with open("../report/report.html", "wb") as f:
    12. HTMLTestRunner(
    13. stream=f,
    14. title="单元测试",
    15. description="描述单元测试",
    16. verbosity=2
    17. ).run(suite)
    18. t = TestHtmlReportClass()
    19. t.export_testhtmlreport()

    六、数据驱动

    1.ddt

    ddt:data driver tests ,数据驱动测试,是一种单元测试框架

    作用:在设计用例的时候,有些用例只是参数数据的输入不一样,比如登录这个功能,操作过程是一样的。如果用例重复去写操作过程会增加代码量,对应这种多组数据的测试用例,可以用数据驱动设计模式,一组数据对应一个测试用例,用例自动加载生成

    导入ddt模块:

    ddt属于第三方模块,需要安装,安装方法(安装只安装在一个项目中,如果换项目需要重新安装):
    方法一:在cmd命令窗口中输入:pip install ddt
    方法二:pycharm中打开终端,输入:pip install ddt

    方法三:pycharm中file->setting->Project:pythonProject_pytest->Python Interpreter(在这个界面可以看到安装的模块,因此可以用此方法验证是否安装了ddt模块)->+->搜索ddt->选择ddt模块->Install Package

    ddt要与unittest单元测试框架一起结合使用
    @ddt 引入ddt模块
    @data 导入数据
    @unpack 拆分数据
    @file_data导入外部数据

    2. ddt处理各种类型数据

    需注意以下几点:
    1.使用ddt模块要在测试类前用@ddt进行修饰
    2.要导入测试数据需在测试用例(以test_开头的方法)前用@data修饰
    3.若需对测试数据进行拆分需用@unpack修饰
    4.若需导入外部数据需用@file_data修饰

    1.分析ddt工作原理

    test_NotDdt使用的.csv数据文件

    1. 1
    2. 2
    3. 3

     读取csv文件的模块

    1. import csv
    2. class ReadCsvClass():
    3. def readCsvMethod(self,f):
    4. return csv.reader(open(f))

     测试用例

    1. import unittest
    2. from ddt import ddt,data,unpack
    3. from tools.readcsv import ReadCsvClass
    4. t=ReadCsvClass()
    5. f='../data/data_csv.csv'
    6. @ddt()
    7. class TestDdtClass(unittest.TestCase):
    8. def test_NotDdt(self):
    9. print('test_NotDdt')
    10. for i in t.readCsvMethod(f):
    11. print(i[0])
    12. @data(1,2,3)
    13. def test_UseDdt(self,a):
    14. print('test_UseDdt')
    15. print(a)
    16. if __name__ == '__main__':
    17. unittest.main()
    18. '''
    19. test_NotDdt
    20. 1
    21. 2
    22. 3
    23. ResourceWarning
    24. test_UseDdt
    25. 1
    26. test_UseDdt
    27. 2
    28. test_UseDdt
    29. 3
    30. Ran 4 tests in 0.006s
    31. OK
    32. '''

    上述代码可以看出,不适用ddt的函数只算一次用例,而使用了ddt的模块算3次用例

    因此有了ddt之后,可以一次性向测试方法中传入多个测试数据,而生成测试用例的个数则是根据@data中传入的测试数据的元素个数而定的

    2.测试元组数据

    ddt的数据驱动使用就是在data中输入数据,并在方法里定义变量即可

    根据情况,可以分为单组元素(test_Print_Tuple1),多组分解元素(test_Print_Tuple2),

    多组未分解元素(test_Print_Tuple3),可以看下方代码的结果

    1. import unittest
    2. from ddt import ddt,data,unpack
    3. @ddt
    4. class TestTuple(unittest.TestCase):
    5. @data(1,2,3,4)
    6. def test_Print_Tuple1(self, a):
    7. print('test_Print_Tuple1')
    8. print(a)
    9. @data((1,2),(3,4))
    10. @unpack
    11. def test_Print_Tuple2(self,a,b):
    12. print('test_Print_Tuple2')
    13. print(a,b)
    14. @data((1, 2), (3, 4))
    15. def test_Print_Tuple3(self, a):
    16. print('test_Print_Tuple3')
    17. print(a)
    18. if __name__ == '__main__':
    19. unittest.main()
    20. '''
    21. test_Print_Tuple1
    22. 1
    23. test_Print_Tuple1
    24. 2
    25. test_Print_Tuple1
    26. 3
    27. test_Print_Tuple1
    28. 4
    29. test_Print_Tuple2
    30. 1 2
    31. test_Print_Tuple2
    32. 3 4
    33. test_Print_Tuple3
    34. (1, 2)
    35. test_Print_Tuple3
    36. (3, 4)
    37. Ran 8 tests in 0.006s
    38. OK
    39. Process finished with exit code 0
    40. '''

     3.测试列表数据

    多组列表拆分数据

    1. import unittest
    2. from ddt import ddt,data,unpack
    3. @ddt
    4. class TestList(unittest.TestCase):
    5. @data([1,2,3],[4,5,6])
    6. @unpack
    7. def test_Print_list1(self,a,b,c):
    8. print('test_Print_list1')
    9. print(a,b,c)
    10. if __name__ == '__main__':
    11. unittest.main()
    12. '''
    13. test_Print_list1
    14. 1 2 3
    15. test_Print_list1
    16. 4 5 6
    17. Ran 2 tests in 0.002s
    18. OK
    19. '''

     复杂列表(含字典)拆分数据

    1. import unittest
    2. from ddt import ddt,data,unpack
    3. @ddt
    4. class TestList(unittest.TestCase):
    5. @data([{"username":"ljx","password":"123456"},{"addresss":"wuhan","phone":"152738748"}])
    6. @unpack
    7. def test_Print_list1(self,a,b):
    8. print('test_Print_list1')
    9. print(a,b)
    10. if __name__ == '__main__':
    11. unittest.main()
    12. '''
    13. test_Print_list1
    14. {'username': 'ljx', 'password': '123456'} {'addresss': 'wuhan', 'phone': '152738748'}
    15. Ran 1 test in 0.001s
    16. OK
    17. Process finished with exit code 0
    18. '''

    4.测试字典数据

     多组字典数据拆分

    1. import unittest
    2. from ddt import ddt,data,unpack
    3. @ddt
    4. class TestDict(unittest.TestCase):
    5. @data({"username":"tom","password":"123456"},{"username":"jack","password":"654321"})
    6. @unpack
    7. def test_Print_Dict(self,username,password):
    8. print('test_Print_Dict')
    9. print(username,password)
    10. if __name__ == '__main__':
    11. unittest.main()
    12. '''
    13. test_Print_Dict
    14. tom 123456
    15. test_Print_Dict
    16. jack 654321
    17. Ran 2 tests in 0.002s
    18. OK
    19. '''

     5.ddt测试字典列表数据结合

            在实际应用中会存在数据比较多的情况,如果都直接把数据传入@data中会显得代码非常冗杂,以包含多个字典的列表数据为例,可将包含多个字典的元素存放在一个列表变量中,在@data中传入列表变量即可,注意data中列表名前加*号。

    1. import unittest
    2. from ddt import ddt,data,unpack
    3. @ddt
    4. class TestDict(unittest.TestCase):
    5. list_d=[{"username":"tom","password":"123456"},
    6. {"username":"jack","password":"654321"},
    7. {"username": "rose", "password": "123123"},
    8. {"username": "eva", "password": "321321"}]
    9. @data(*list_d)
    10. @unpack
    11. def test_Print_Dict(self,username,password):
    12. print('test_Print_Dict')
    13. print(username,password)
    14. if __name__ == '__main__':
    15. unittest.main()
    16. '''
    17. test_Print_Dict
    18. tom 123456
    19. test_Print_Dict
    20. jack 654321
    21. test_Print_Dict
    22. rose 123123
    23. test_Print_Dict
    24. eva 321321
    25. Ran 4 tests in 0.006s
    26. OK
    27. '''

     6.ddt测试读取文件数据

    数据格式必须为json,且必须为双引号的键值对形式

    1. {
    2. "username": "tom",
    3. "password": "123456"
    4. }
    1. import unittest
    2. from ddt import ddt,file_data
    3. @ddt()
    4. class TestFileClass(unittest.TestCase):
    5. @file_data('../data/data_json.json')
    6. def test_File(self,value):
    7. print(value)
    8. if __name__ == '__main__':
    9. unittest.main()
    10. '''
    11. tom
    12. 123456
    13. Ran 2 tests in 0.001s
    14. OK
    15. '''


    总结

    如何做单元测试:
       创建一个data包,存放测试数据,使业务代码和测试数据进行分离
       创建一个readdata包,读取测试数据
       创建用例包,导入unitest模块,开发代码块,读取数据代码块
          测试用例模块,定义一个类继承unittest.TestCase
          测试用例必须以test开头
          在测试用例中添加断言,验证读取的数据的预期结果和开发代码中返回的实际结果
          接着导入htmltestrunner.py模块,生成测试报告
          最后在main方法中通过unittest.main()执行测试用例即可
  • 相关阅读:
    LVS+Keepalived 高可用群集
    leetcode 4. Median of Two Sorted Arrays 寻找两个正序数组的中位数(困难)
    【Netty】一、高性能NIO通信框架Netty-快速入门
    程序员的实用神器之——通义灵码
    Python手动安装Jieba库(Win11)
    【ES6 新特性】
    x264 编码器 AArch64 汇编函数模块关系分析
    clang-前端插件-给各种无花括号的“块”加花括号-基于llvm15--clang-plugin-add-brace
    js sm4实现加密解密
    力扣刷题-哈希表-四数之和
  • 原文地址:https://blog.csdn.net/lookout99/article/details/126507802