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 ConfigParserclass 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 argvdef runTest(case_dir, patter):import TestRunnerreload(TestRunner)discover = unittest.defaultTestLoader.discover(case_dir+"\\testcases", pattern=patter)runner = TestRunner.AutoTestRunner()result, infos = runner.run(discover)return result, infosdef run(cadir):filename = Utils.createFolder(cadir[0])  #创建文件夹import Loggerlog = 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):passelse: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.displayif (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))) * 100htmlstr = '''<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=1j=1for 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=1for key1 in value.keys(): takentime = ((value[key1])["stoptime"] - (value[key1])["starttime"]).secondstakentime = 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+=1elif (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+=1f.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"])) * 100htmlstr = '''<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=1for 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_tolistsendmailinfo = "" 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.mzph.cn/news/617558.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电脑/设备网络共享给其他设备上网

文章目录 一、概述二、设置网络共享2.1 电脑可以上网&#xff0c;通过网络共享让其他设备也可以上网2.2 手机如何使用USB数据线共享网络给电脑 一、概述 现在有如下几种情况&#xff1a; 设备本身不能上网&#xff0c;需要通过电脑上网 笔记本WIFI连热点上网&#xff0c;然后…

C#,卡特兰数(Catalan number,明安图数)的算法源代码

一、概要 卡特兰数&#xff08;英语&#xff1a;Catalan number&#xff09;&#xff0c;又称卡塔兰数、明安图数&#xff0c;是组合数学中一种常出现于各种计数问题中的数列。以比利时的数学家欧仁查理卡特兰的名字来命名。1730年左右被蒙古族数学家明安图使用于对三角函数幂…

运动模型非线性扩展卡尔曼跟踪融合滤波算法(Matlab仿真)

卡尔曼滤波的原理和理论在CSDN已有很多文章&#xff0c;这里不再赘述&#xff0c;仅分享个人的理解和Matlab仿真代码。 1 单目标跟踪 匀速转弯&#xff08;CTRV&#xff09;运动模型下&#xff0c;摄像头输出目标状态camera_state [x, y, theta, v]&#xff0c;雷达输出目标状…

js逆向第19例:猿人学第17题天杀的Http2.0

文章目录 一、前言二、定位关键参数三、代码实现四、参考文献一、前言 任务十七:抓取这5页的数字,计算加和并提交结果 题目已经给出来标准答案,而且此题设置为“非常简单”其关键就是HTTP/2.0请求,打开控制台查看请求接口数据如下: 二、定位关键参数 可以看到控制台显示…

Linux系统——DNS解析详解

目录 一、DNS域名解析 1.DNS的作用 2.域名的组成 2.1域名层级结构关系特点 2.2域名空间构成 2.3域名的四种不同类型 2.3.1延伸 2.3.2总结 3.DNS域名解析过程 3.1递归查询 3.2迭代查询 3.3一次DNS解析的过程 4.DNS系统类型 4.1缓存域名服务器 4.2主域名服务器 4…

MES系统如何进行产品的质量管理

质量管理重点是对产品的检验&#xff0c;这里面包括&#xff1a;采购来料检验、工序检验、入库前检验等几个检验环节&#xff0c;并根据系统设定的检验标准进行检验&#xff0c;检验不通过的不能留到下个环节。质量管理也是万界星空科技云MES中的一个重要组成部分&#xff0c;旨…

Java接入Apache Spark(入门环境搭建、常见问题)

Java接入Apache Spark&#xff08;环境搭建、常见问题&#xff09; 背景介绍 Apache Spark 是一个快速的&#xff0c;通用的集群计算系统。它对 Java&#xff0c;Scala&#xff0c;Python 和 R 提供了的高层 API&#xff0c;并有一个经优化的支持通用执行图计算的引擎。它还支…

Unity 工具 之 Azure 微软连续语音识别ASR的简单整理

Unity 工具 之 Azure 微软连续语音识别ASR的简单整理 目录 Unity 工具 之 Azure 微软连续语音识别ASR的简单整理 一、简单介绍 二、实现原理 三、注意实现 四、实现步骤 五、关键脚本 一、简单介绍 Unity 工具类&#xff0c;自己整理的一些游戏开发可能用到的模块&#x…

Vue:将以往的JQ页面,重构成Vue组件页面的大致思路梳理(组件化编码大致流程)

一、实现静态组件 组件要按照功能点拆分&#xff0c;命名不要与HTML元素冲突。 1、根据UI提供的原型图&#xff0c;进行结构拆分&#xff0c;拆分的粒度以是否方便给组件起名字为依据。并梳理好对应组件的层级依赖关系。 2、拆分好结构后&#xff0c;开始对应的写组件&#x…

vue3-响应式基础之reactive

reactive() 还有另一种声明响应式状态的方式&#xff0c;即使用 reactive() API。与将内部值包装在特殊对象中的 ref 不同&#xff0c;reactive() 将使对象本身具有响应性&#xff1a; 「点击按钮1」 <script lang"ts" setup> import { reactive } from vuec…

ATECLOUD-POWER测试系统如何检测电源稳定性?

电源模块做为一种电源供应器为电子设备提供供电&#xff0c;广泛应用于汽车电子、航空航天、医疗、通信等各个领域&#xff0c;因此检测电源模块的稳定性是非常重要的&#xff0c;确保其为电子设备提供稳定的电压和电流&#xff0c;保证电子设备可以正常稳定工作。 电源模块的稳…

四川古力未来科技有限公司:抖音小店的崛起之路

随着互联网的飞速发展&#xff0c;电子商务已经成为人们日常生活中不可或缺的一部分。作为一家以科技为核心的公司&#xff0c;四川古力未来科技有限公司在电子商务领域中崭露头角&#xff0c;特别是其抖音小店的发展引人注目。 四川古力未来科技有限公司的抖音小店自开业以来&…

RT-Thread:STM32 PHY 调试,使用软件包 WIZNET 驱动 W5500

说明&#xff1a; 1. 本文记录使用 RT-Thread 软件包 WIZNET驱动 W5500 的调试笔记。 2. 采用 RT-Thread Studio 工程 STM32F407VET6 芯片&#xff0c;W5500 PHY芯片&#xff0c;两者之间使用SPI接口链接 。 注意&#xff1a; 1.在按流程建立工程&#xff0c;和移植完 wizn…

LeetCode---121双周赛---数位dp

题目列表 2996. 大于等于顺序前缀和的最小缺失整数 2997. 使数组异或和等于 K 的最少操作次数 2998. 使 X 和 Y 相等的最少操作次数 2999. 统计强大整数的数目 一、大于等于顺序前缀和的最小缺失整数 简单的模拟题&#xff0c;只要按照题目的要求去写代码即可&#xff0c;…

2-Linux-应用-部署icwp-Linux虚拟机【Django+Vue+Nginx+uwsgi+Linux】

本文概述 本文章讲述基于Linux CentOS 7系统&#xff08;虚拟机&#xff09;&#xff0c;部署DjangoVue开发的前后端分离项目。 项目源码不开放&#xff0c;但是操作步骤可以借鉴。 该文章将项目部署在Linux虚拟机上&#xff0c;暂不使用Docker 相关指令尽量展示执行路径&am…

通义灵码 - 免费的阿里云 VS code Jetbrains AI 编码辅助工具

系列文章目录 前言 通义灵码&#xff0c;是阿里云出品的一款基于通义大模型的智能编码辅助工具&#xff0c;提供行级/函数级实时续写、自然语言生成代码、单元测试生成、代码注释生成、代码解释、研发智能问答、异常报错排查等能力&#xff0c;并针对阿里云 SDK/OpenAPI 的使用…

最强联网Chat GPT 火爆全网高速 永久免费

&#x1f534;高速联网 秒响应支持语音通话&#x1f388; 首先介绍一下她的功能吧&#x1f601; 女友消息代回机&#x1f44c;&#x1f3fb; 朋友圈文案&#x1f44c;&#x1f3fb; 聊天话术&#x1f44c;&#x1f3fb; 高情商回复&#x1f44c;&#x1f3fb; 脱单助…

redis缓存雪崩、穿透和击穿

缓存雪崩 对于系统 A&#xff0c;假设每天高峰期每秒 5000 个请求&#xff0c;本来缓存在高峰期可以扛住每秒 4000 个请求&#xff0c;但是缓存机器意外发生了全盘宕机或者大量缓存集中在某一个时间段失效。缓存挂了&#xff0c;此时 1 秒 5000 个请求全部落数据库&#xff0c;…

Salesforce财务状况分析

纵观Salesforce发展史和十几年财报中的信息&#xff0c;Salesforce从中小企业CRM服务的蓝海市场切入&#xff0c;但受限于中小企业的生命周期价值和每用户平均收入小且获客成本和流失率不对等&#xff0c;蓝海同时也是死海。 Salesforce通过收购逐渐补足品牌和产品两块短板&am…

golang 记录一次协程和协程池的使用,利用ants协程池来处理定时器导致服务全部阻塞

前言 在实习的项目中有一个地方遇到了需要协程池的地方&#xff0c;在mt推荐下使用了ants库。因此在此篇记录一下自己学习使用此库的情况。 场景描述 此服务大致是一个kafka消息接收、发送相关。接收消息&#xff0c;根据参数设置定时器进行重发。 通过这里新建kafka服务&a…