Python web自动化测试框架搭建(功能接口)——通用模块

1、通用模块:

  • config.conf: 公共配置文件,配置报告、日志、截图路径,以及邮件相关配置
  • [report]
    reportpath = E:\workspace\WebAutomation\src\functiontest\Report\2017-07-18
    screen_path = E:\workspace\WebAutomation\src\functiontest\Report\2017-07-18\Screenshoots
    report_path = E:\workspace\WebAutomation\src\functiontest\Report\2017-07-18\Report\TestReport-2017-07-18-15-23-06.html
    log_path = E:\workspace\WebAutomation\src\functiontest\Report\2017-07-18\Logs\2017-07-18-15-23-06.log
    
    [mail]
    mail_from = xxx
    mail_tolist = xxx
    mail_host = mail.xx.com
    mail_user = xxxx
    mail_pass = aGFpbmFuNVU1Ng==

  • logger: 日志模块

  • main.py: 执行器,负责执行整体测试任务模块

  • testrunner.py: 负责测试用例执行和结果收集

  • utils.py: 公共方法,如创建报告文件夹、生成测试报告、发送邮件


2、日志模块:


#coding:utf-8
import logging.handlers
import ConfigParser

class Loger(logging.Logger):
    def __init__(self, filename=None):
        super(Loger, self).__init__(self)
        # 日志文件名
        conf = ConfigParser.ConfigParser()
        conf.read("config.conf")
        filename = conf.get("report", "log_path")
        self.filename = filename
        
        # 创建一个handler,用于写入日志文件
        fh = logging.handlers.RotatingFileHandler(self.filename, 'a')
        fh.setLevel(logging.DEBUG) 

        # 再创建一个handler,用于输出到控制台 
        ch = logging.StreamHandler() 
        ch.setLevel(logging.DEBUG) 

        # 定义handler的输出格式 
        formatter_fh = logging.Formatter('[%(asctime)s] - %(filename)s [Line:%(lineno)d] - [%(levelname)s] - %(message)s') 
        formatter_ch = logging.Formatter('[%(asctime)s] - %(message)s') 
        fh.setFormatter(formatter_fh) 
        ch.setFormatter(formatter_ch) 

        # 给logger添加handler 
        self.addHandler(fh) 
        #self.addHandler(ch) 


3、执行模块:


# coding=utf-8
import unittest
import os
import Utils
from sys import argv
 
def runTest(case_dir, patter):
    import TestRunner
    reload(TestRunner)
    discover = unittest.defaultTestLoader.discover(case_dir+"\\testcases", pattern=patter)
    runner = TestRunner.AutoTestRunner()
    result, infos = runner.run(discover)
    return result, infos

def run(cadir):
    filename = Utils.createFolder(cadir[0])  #创建文件夹
    import Logger
    log = Logger.Loger()
    log.info(cadir[2] + u"测试开始")
    log.info(u"开始创建文件夹和文件")
    log.info(u"日志文件:"+filename[0])
    log.info(u"报告文件:"+filename[1])
    log.info(u"文件夹和文件创建成功")
    log.info(u"开始执行测试用例")
    result, infos = runTest(cadir[0], cadir[1])  #收集和执行测试用例
    log.info(u"测试用例执行完成,开始写入报告")
    if cadir[2] == "functiontest":
        Utils.createReport(result, infos, filename, cadir[3])  #测试结果写入报告
    log.info(u"报告写入结束,测试结束")
    log.info(u"开始发送邮件……")
    isSuccess = Utils.sendMail(filename[1],cadir[3])
    log.info(isSuccess)
    log.info("================================================================\n")

if __name__ == '__main__':
    projectpath = os.path.dirname(os.path.realpath(__file__))
    test_dir = projectpath + '\\functiontest\\'  #功能测试用例路径
    test_dir1 = projectpath + '\\interfacetest\\'  #接口测试用例路径
    casedirs = []
    #argv=["","all"]
    if argv[1] == "interface":
        casedirs.append([test_dir1, "*TestCase.py", "interfacetest","接口"])
    elif argv[1] == "function":
        casedirs.append([test_dir, "TestCase*.py", "functiontest","功能"])
    else:
        casedirs.append([test_dir1, "*TestCase.py", "interfacetest","接口"])
        casedirs.append([test_dir, "TestCase*.py", "functiontest","功能"])
        
    for cadir in casedirs:
        run(cadir)


4、创建报告文件夹:


"""创建报告文件夹、日志文件夹、截图文件夹、日志文件、报告文件"""    
def createFolder(test_path):
    # conf = ConfigParser.ConfigParser()
    # conf.read("config.conf")    
    # reportFolder = conf.get("result", "resultpath") + time.strftime('%Y-%m-%d', time.localtime(time.time()))
    reportFolder = test_path + "Report\\" + time.strftime('%Y-%m-%d', time.localtime(time.time()))
    log_path = reportFolder + "\\Logs"
    screen_path = reportFolder + "\\Screenshoots"
    report_path = reportFolder + "\\Report"
    pathlist = [report_path, log_path, screen_path]
    for paths in pathlist:
        if os.path.exists(paths):
            pass
        else:
            os.makedirs(paths)
    logFile = log_path + "\\" + time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time())) + ".log"
    f = open(logFile, 'a')
    f.close()
    
    reportname = report_path + "\\TestReport-" + time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time())) + ".html"
    f = open(reportname, 'w')
    
    htmlStr = '''
            <html>
            <head>
            <meta charset='utf-8' />
            <style>
            body{counter-reset:num;}
            li{list-style: none;text-indent:10px;}
            li:after{content: counter(num);counter-increment:num;}
            pre { 
                white-space: pre-wrap;
                word-wrap: break-word; 
                display:block;
                padding:5px;
                font-size:13px;
                color:#333;
                background-color:#f5f5f5;
                border:1px solid #ccc;
                border-radius:4px;
                font-family:'Consolas';
            }
        
            h1 {
                font-size: 16pt;
                color: gray;
            }
            .heading {
                margin-top: 0ex;
                margin-bottom: 1ex;
            }
            
            .heading .attribute {
                margin-top: 1ex;
                margin-bottom: 0;
            }
            
            .heading .description {
                margin-top: 2ex;
                margin-bottom: 3ex;
            }
            
            /* -- table --- */
            table {
            border-collapse: collapse; /* IE7 and lower */
            border-spacing: 0;
            width: 100%;    
            }
            
            .bordered {
                border: solid #ccc 1px;
                -moz-border-radius: 5px;
                -webkit-border-radius: 5px;
                border-radius: 5px;
                -webkit-box-shadow: 0 1px 1px #ccc; 
                -moz-box-shadow: 0 1px 1px #ccc; 
                box-shadow: 0 1px 1px #ccc;
               
            }
                
            .bordered td {
                border: 1px solid #ccc;
                padding: 5px; 
                font-size:14px;  
            }
            
            .header_row {
                font-weight: bold;
                background-color: #dce9f9;
            }
            
            /* -- css div popup --- */
            a {
                color:#428bca;
                text-decoration:none;
            }
            
            a:hover {
                text-decoration:underline;
            }
            
            .popup_window {
                display: none;
                /*position: relative;*/
                /*border: solid #627173 1px; */
                padding: 10px;
                background-color: #E6E6D6;
                text-align: left;
                font-size: 8pt;
                width: 500px;
            }
            
            .hiddenRow {
                display: none;
            }
            
            .displayRow {
                display: block;
            }
            .testcase   { margin-left: 2em; color: #000; font-weight: bold;}
            
            /* -- report -- */
            #show_detail_line {
                margin-top: 3ex;
                margin-bottom: 1ex;
            }
            
            </style>
            
            <script language="javascript" type="text/javascript">
            function showCase(level) {
                trs = document.getElementsByTagName("tr");
                for (var i = 0; i < trs.length; i++) {
                    tr = trs[i];
                    id = tr.id;
                    if (id.substr(0,2) == 'ft') {
                        if (level == 0) {
                            tr.className = 'none';
                        }
                        if (level == 1) {
                            tr.className = 'hiddenRow';
                        }
                        if (level == 2) {
                            tr.className = 'none';
                        }
                        if (level == 3) {
                            tr.className = 'hiddenRow';
                        }
                    }
                    if (id.substr(0,2) == 'pt') {
                        if (level == 0) {
                            tr.className = 'none';
                        }
                        if (level == 1) {
                            tr.className = 'none';
                        }
                        if (level == 2) {
                            tr.className = 'hiddenRow';
                        }
                        if (level == 3) {
                            tr.className = 'hiddenRow';
                        }
                    }
                    if (id.substr(0,2) == 'st') {        
                        if (level == 0) {
                            tr.className = 'none';
                        }
                        if (level == 1) {
                            tr.className = 'hiddenRow';
                        }
                        if (level == 2) {
                            tr.className = 'hiddenRow';
                        }
                        if (level == 3) {
                            tr.className = 'none';
                        }
                    }
                }
            }
            
            function showTestDetail(tr_id){
                table = document.getElementById('result_table')
                trs = table.getElementsByTagName("tr");
                for (var i = 0; i < trs.length; i++) {
                    tr = trs[i];
                    id = tr.id;
                    if ((id.split('_'))[1] == tr_id){
                        trn = document.getElementById(id)
                        if (trn.className == 'none' ) {
                            trn.className = 'hiddenRow';
                        }
                        else {
                            trn.className = 'none';
                        }
                    }
        
                }
            }
            
            
            function showFailDetail(div_id){
                var details_div = document.getElementById(div_id)
                var displayState = details_div.style.display
                if (displayState != 'block' ) {
                    displayState = 'block'
                    details_div.style.display = 'block'
                }
                else {
                    details_div.style.display = 'none'
                }
            }
                
            </script>    
            </head><body style='font-family:微软雅黑'>
            '''
    
    f.write(htmlStr)
    f.close()
    conf = ConfigParser.ConfigParser()
    conf.read("config.conf")
    conf.set("report", "reportpath", reportFolder)    
    conf.set("report", "screen_path", screen_path)
    conf.set("report", "log_path", logFile)
    conf.set("report", "report_path", reportname)
    conf.write(open("config.conf", "w"))
    
    conf.read(test_path + "\\TestCases\\config.conf")
    conf.set("report", "reportpath", reportFolder)    
    conf.set("report", "screen_path", screen_path)
    conf.set("report", "log_path", logFile)
    conf.set("report", "report_path", reportname)
    conf.write(open("config.conf", "w"))
    filenames = [logFile, reportname]
    return filenames


5、创建创建功能测试报告


"""创建html格式的测试报告"""
def createReport(t_result, t_info, filename, reporttype):
    #localMachine = socket.getfqdn(socket.gethostname())
    #localIP = socket.gethostbyname(localMachine)      
    f = open(filename[1], 'a')
    
    unskip = (t_info)["CaseNum"]-(t_info)["Skip"]
    passrate = (float((t_info)["Success"]) / (float(unskip))) * 100
    htmlstr = '''
            <h3>执行概述</h3>
            <p style='font-size:12px;'>点击各数字可以筛选对应结果的用例。</p>
            <table class='bordered' style='width:1100px; text-align:center'>
            <tr class='header_row'>
            <td style='width:100px'>用例总数</td>
            <td style='width:100px'>通过</td>
            <td style='width:100px'>失败</td>
            <td style='width:100px'>跳过</td>
            <td style='width:100px'>错误</td>
            <td style='width:100px'>通过率</td>
            <td>开始时间</td><td>运行时间</td><td>日志文件</td></tr>
            <tr><td><a href='javascript:showCase(0)'>%s</a></td>
            <td><a href='javascript:showCase(1)'>%s</a></td>
            <td><a href='javascript:showCase(2)'>%s</a></td>
            <td><a href='javascript:showCase(3)'>%s</a></td>
            <td><a href='javascript:showCase(2)'>%s</a></td>
            <td>%.2f%%</td><td>%s</td><td>%s</td>
            <td><a href='%s'>%s</a></td>
            </tr></table>
            ''' % ((t_info)["CaseNum"],(t_info)["Success"], (t_info)["Fail"], (t_info)["Skip"], (t_info)["Error"],passrate, \
                   (t_info)["StartTime"],(t_info)["TakeTime"],filename[0], os.path.split(filename[0])[-1])    
    f.write(htmlstr)
    
    htmlstr = '''
            <h3>执行详情</h3>
            <p style='font-size:12px;'>Pass:通过,Failed:失败,Skip:跳过,Error:错误。点击Failed可以查看错误详情。</p>
            <table id='result_table' class="bordered">
            <tr class='header_row'>
                <td>编号</td>
                <td style='width:300px'>测试用例</td>
                <td style='width:300px'>中文描述</td>
                <td>耗时</td>
                <td style='width:300px'>测试结果</td>
                <td>查看</td>
            </tr>
            '''
    f.write(htmlstr) 
    
    i=1
    j=1
    for key in t_result.results.keys():
        htmlstr = '''
                <tr><td colspan='6' class='testcase'>
                <a class="popup_link" onfocus="this.blur();" href="javascript:showTestDetail('%s')">%s</a></td></tr>
                ''' % (str(j),key)
        f.write(htmlstr) 
        
        value = t_result.results[key]
        count=1
        for key1 in value.keys(): 
            takentime = ((value[key1])["stoptime"] - (value[key1])["starttime"]).seconds
            takentime = str(datetime.timedelta(seconds=takentime))
            if (value[key1])["Result"] == "Failed" or (value[key1])["Result"] == "Error":
                htmlstr = "<tr id='ft_%s_%s' class='none' style='color:red'>" % (str(j),str(count))
                f.write(htmlstr)
                
                htmlstr = '''
                        <td><li></li></td>
                        <td>%s</td>
                        <td>%s</td>
                        <td>%s</td>
                        <td>
                        <a class="popup_link" style='color:red;text-decoration:underline;' onfocus='this.blur();' href="javascript:showFailDetail('div_ft%s')" >%s</a>
                        <div id='div_ft%s' class="popup_window">
                        <div style='text-align: right; color:red;cursor:pointer'>
                        <a onfocus='this.blur();' onclick="document.getElementById('div_ft%s').style.display = 'none' " >X</a></div>
                        <pre>%s</pre></td>
                        <td><a href='%s' target='_blank'>查看截图</a></td></tr>
                        ''' % (key1, (value[key1])["name"], takentime, str(i),(value[key1])["Result"], str(i),str(i),(value[key1])["Reason"], (value[key1])["Screenshoot"])
                f.write(htmlstr) 
                i+=1
                     
            elif (value[key1])["Result"] == "Pass":
                htmlstr = '''
                        <tr id='pt_%s_%s' class='none'>
                        <td><li></li></td>
                        <td>%s</td>
                        <td>%s</td>
                        <td>%s</td>
                        <td style='color:green;'>%s</td>
                        <td></td></tr>
                        ''' % ((str(j), str(count), key1, (value[key1])["name"], takentime, (value[key1])["Result"]))
                f.write(htmlstr)
                
            else:
                htmlstr = '''
                        <tr id='st_%s_%s' class='none'>
                        <td><li></li></td>
                        <td>%s</td>
                        <td>%s</td>
                        <td>%s</td>
                        <td>%s</td>
                        <td></td></tr>
                        ''' % ((str(j), str(count), key1, (value[key1])["name"], takentime, (value[key1])["Result"]))
                f.write(htmlstr)
            count += 1            
            
        j+=1
    f.write("</table></body></html>")
    f.close()


6、创建接口测试报告


"""创建html格式的接口测试报告"""
def createInterfaceReport(resultlist, t_info, reportfile, logfile):
    #localMachine = socket.getfqdn(socket.gethostname())
    #localIP = socket.gethostbyname(localMachine)      
    f = open(reportfile, 'a')
    
    passrate = (float((t_info)["pass"]) / float((t_info)["total"])) * 100
    htmlstr = '''
            <h3>执行概述</h3>
            <table class='bordered' style='width:1000px; text-align:center'>
            <tr class='header_row'>
                <td style='width:100px'>用例总数</td>
                <td style='width:100px'>通过</td>
                <td style='width:100px'>失败</td>
                <td style='width:100px'>通过率</td>
                <td>开始时间</td><td>运行时间</td><td>日志文件</td></tr>
                <tr><td><a href='javascript:showCase(0)'>%s</a></td>
                <td><a href='javascript:showCase(1)'>%s</a></td>
                <td><a href='javascript:showCase(2)'>%s</a></td>
                <td>%.2f%%</td>
                <td>%s</td>
                <td>%.3fs</td>
                <td><a href='%s'>%s</a></td>
            </tr></table>
            ''' % ((t_info)["total"], (t_info)["pass"], (t_info)["fail"], passrate,t_info['starttime'], t_info['takentime'], logfile, os.path.split(logfile)[-1])
    f.write(htmlstr)
    
    htmlstr = '''
            <h3>执行详情</h3>
            <table id='result_table' class="bordered">
            <tr class='header_row'>
                <td>编号</td>
                <td>模块</td>
                <td>用例描述</td>
                <td>接口</td>
                <td>参数</td>
                <td>请求结果</td>
                <td>测试结果</td>
                <td>耗时(毫秒)</td>
            </tr>
            '''
    f.write(htmlstr) 
    
    i=1
    for result in resultlist:            
        if result[5] == "Failed":
            htmlstr = "<tr id='ft_%s' class='none' style='color:red'>" % str(i) 
        else:
            htmlstr = "<tr id='pt_%s' class='none' style='color:green'>" % str(i)
        htmlstr += '''
                <td><li></li></td>
                <td>%s</td>
                <td>%s</td>
                <td>%s</td>
                <td>%s</td>
                <td style='word-wrap:break-word; max-width:500px;'>%s</td>
                <td>%s</td>
                <td>%s ms</td></tr>
                ''' % (result[0], result[1], result[2], result[3], result[4], result[5], result[6])
        f.write(htmlstr.encode('utf-8')) 
        i+=1  
            
    f.write("</table></body></html>")
    f.close()


7、发送邮件


import ConfigParser
import smtplib  
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
import datetime
import base64
#import socket

"""发送邮件"""
def sendMail(reportname, reporttype): 
    conf = ConfigParser.ConfigParser()
    conf.read("config.conf")    
    mail_from = conf.get("mail", "mail_from")  # 发件箱
    mail_tolist = conf.get("mail", "mail_tolist")  # 收件人列表
    mail_host = conf.get("mail", "mail_host")  # 服务器
    mail_user = conf.get("mail", "mail_user")  # 用户名
    mail_pass = conf.get("mail", "mail_pass")  # 密码
    mail_pass = base64.decodestring(mail_pass)
    
    f = open(reportname, 'r')
    content = f.read()
    msg = MIMEMultipart()
    puretext = MIMEText(content, _subtype='html', _charset='utf-8')  # 设置html格式邮件
    htmlpart = MIMEApplication(open(reportname, 'rb').read())
    htmlpart.add_header('Content-Disposition', 'attachment', filename=os.path.basename(reportname))
    msg.attach(puretext)
    msg.attach(htmlpart)
    sub = reporttype + "自动化测试报告-" + time.strftime("%Y/%m/%d", time.localtime(time.time()))
    msg['Subject'] = sub  # 设置主题
    msg['From'] = mail_from  
    msg['To'] = mail_tolist
    sendmailinfo = "" 
    try: 
        s = smtplib.SMTP()  
        s.connect(mail_host)  # 连接smtp服务器
        s.login(mail_user, mail_pass)  # 登陆服务器
        s.sendmail(mail_from, mail_tolist, msg.as_string())  # 发送邮件
        s.close()
        sendmailinfo = "邮件发送成功!" 
    except Exception, e:  
        sendmailinfo = "邮件发送失败,错误信息:" + str(e)
    return sendmailinfo

Python接口自动化测试零基础入门到精通(2024最新版)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/314956.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Pygame程序的屏幕显示

不同对象的绘制与显示过程 在Pygame中&#xff0c;需要将所有需要在屏幕上显示的内容都绘制在一个display surface上。该Surface通常称为screen surface&#xff0c;它是pygame.display.set_mode()函数返回的Surface对象。 在绘制不同对象时&#xff0c;可以使用不同的绘制方…

Linux - No space left on device

问题描述 No space left on device 原因分析 说明在服务器设备上的存储空间已经满了&#xff0c;不能再上传或者新建文件夹或者文件等。 解决方案 确认查看服务器系统的磁盘使用情况是否是真的已经没有剩余空间&#xff0c;复制下面命令在服务器上运行&#xff0c;然后发现如果…

用友U8 BI数据可视化报表怎么做?秘籍在这!

首先要找到一款顺利对接用友U8的BI数据可视化分析工具&#xff0c;简称BI工具、BI软件。这款BI工具需符合以下要求&#xff1a; 1、能对接用友U8系统。 2、有专门根据用友系统特性量身打造的标准化BI方案&#xff0c;也就是有标准化的用友U8 BI方案。 3、数据可视化图表丰富…

有没有游泳可以戴的耳机?2024年高性价比游泳耳机推荐

科技的不断进步为我们的生活带来了更多的便利和乐趣&#xff0c;游泳耳机作为一种专门设计用于水中活动的耳机也在不断演进。在畅游的时候&#xff0c;能够携带一款高性价比的游泳耳机&#xff0c;不仅可以让您更好地享受音乐&#xff0c;还能为游泳时的独特体验增色不少。 因…

HarmonyOS——ArkUI状态管理

一、状态管理 在声明式UI编程框架中&#xff0c;UI是程序状态的运行结果&#xff0c;用户构建了一个UI模型&#xff0c;其中应用的运行时的状态是参数。当参数改变时&#xff0c;UI作为返回结果&#xff0c;也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染&…

ES索引原理

ES在检索时底层使用的就是倒排索引&#xff0c;正向索引是通过key找value&#xff0c;反向索引则是通过value找key。 索引会分为两个区域&#xff1a;索引区和元数据区。数据是这样存储在里面的&#xff1a; 简单理解就是&#xff1a;当要录入一条数据时&#xff0c;首先会将完…

红黑树(RBTree)

目录​​​​​​​ 一、红黑树简介 二、红黑树的来源 三、什么是红黑树 四、红黑树的性质 五、红黑树的节点定义 六、红黑树的操作 6.1、红黑树的查找 6.2、红黑树的插入 七、红黑树的验证 八、红黑树和AVL树的比较 一、红黑树简介 红黑树是一种自平衡的二叉查找树…

C++内存管理机制(侯捷)笔记4(完结)

C内存管理机制&#xff08;侯捷&#xff09; 本文是学习笔记&#xff0c;仅供个人学习使用。如有侵权&#xff0c;请联系删除。 参考链接 Youtube: 侯捷-C内存管理机制 Github课程视频、PPT和源代码: https://github.com/ZachL1/Bilibili-plus 介绍 下面是第四讲和第五讲…

02. 坦克大战项目-准备工作和绘制坦克

02. 坦克大战项目-准备工作和绘制坦克 01. 准备工作 1. 首先我们要创建四个类 1. Tank类 介绍&#xff1a;Tank 类主要用来表示坦克的基本属性和行为 public class Tank {private int x;//坦克的横坐标private int y;//坦克的纵坐标public int getX() {return x;}public v…

HTML 链接 图片引入

文章目录 链接图片引入 链接 准备工作 新建一个名为link.html和suc.html suc.html <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><title>显示结果</title></head><body>注册成功...&l…

[AutoSar]基础部分 RTE 08 runnable mapping

目录 关键词平台说明一、runnable mapping的必要性二、runnable mapping 通用规则三、Task type四、可以不用mapping的runnbale 关键词 嵌入式、C语言、autosar、runnable 平台说明 项目ValueOSautosar OSautosar厂商vector芯片厂商TI编程语言C&#xff0c;C编译器HighTec (…

chat-plus部署指南

目录 1.下载代码 2.启动 3.测试 1.下载代码 cd /optwget https://github.com/yangjian102621/chatgpt-plus/archive/refs/tags/v3.2.4.1.tar.gz 2.启动 cd /opt/chatgpt-plus-3.2.4.1/deploydocker-compose up -d 3.测试 管理员地址xxx:8080/admin 账号密码admin/admin1…

【回顾2023,展望2024】砥砺前行

2023年总结 转眼间&#xff0c;迎来了新的一年2024年&#xff0c;回顾2023&#xff0c;对于我来说是一个充满平凡但又充实又幸运的一年。这一年经历了很多的事情&#xff0c;包括博客创作、技术学习、出书、买房等&#xff0c;基本上每件事情都是一个前所未有的挑战和机遇、使…

Java 设计模式

1.单例设计模式 对某个类只能存在一个对象实例&#xff0c;并且该类只提供一个取得其对象实例的方法。 1.1 饿汉式 构造器私有化 --> 防止直接new类的内部创建对象提供一个static的public方法 getInstance class GirlFriend {private String name;private static GirlFri…

共融共生:智慧城市与智慧乡村的协调发展之路

随着科技的飞速发展和全球化的不断深入&#xff0c;智慧城市和智慧乡村作为现代社会发展的重要组成部分&#xff0c;正逐渐成为人们关注的焦点。然而&#xff0c;在追求经济发展的过程中&#xff0c;城乡发展不平衡的问题也日益凸显。因此&#xff0c;如何实现智慧城市与智慧乡…

HTTP 常见协议:选择正确的协议,提升用户体验(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

MES系统中的设备管理及设备数据采集

随时工厂数字化建设的大力推进&#xff0c;设备管理的效率得到了很大的提升&#xff0c;特别是作为机加工企业&#xff0c;设备是整个企业非常重要的核心资产。 一、设备进行数据采集面临痛点&#xff1a; 设备数据状况无法获取与掌握 设备老旧&#xff0c;信息化基础差&…

【Unity】Attribute meta-data#com.google.android.play.billingclient.version 多版本库冲突

文章目录 一、背景二、问题描述三、解决方案 一、背景 1、Unity 2021.3.9f1 2、Max由6.0.1至最新版本6.1.0 二、问题描述 错误信息 Attribute meta-data#com.google.android.play.billingclient.versionvalue value(6.1.0) from [com.android.billingclient:billing:6.1.0] An…

docker搭建部署minio 存储文件

1. 介绍 MinIO是一个开源的对象存储服务器&#xff0c;它允许你在自己的硬件上构建高性能的对象存储。本文将指导你如何使用Docker搭建和部署MinIO&#xff0c;并挂载外部目录以实现文件的持久化存储。 2. 安装Docker 首先&#xff0c;确保你的系统上已经安装了Docker。你可…

搞定 Postman 接口自动化测试,看这篇文章就够了!

postman 本文适合已经掌握Postman基本用法的读者&#xff0c;即对接口相关概念有一定了解、已经会使用Postman进行模拟请求等基本操作。 工作环境与版本&#xff1a; Window 7&#xff08;64位&#xff09; Postman &#xff08;Chrome App v5.5.3&#xff09; P.S. 不同版…