【六:pytest框架介绍】

image.png

			常见的请求对象requests.get()requests.post()requests.delete()requests.put()requests.request()常见的响应对象rep=requests.request()//返回字符串格式数据print(req.text)//返回字节格式数据print(req.content)//返回字典格式数据print(req.json)#状态码print(req.status_code)#返回状态信息print(req.reason)print(req.cookies)print(req.encoding)print(req.headers)

pytes用例框架

pytes用例框架默认规则:1、py文件必须以test_开头,或者_test结尾2、类名必须以Test开头3、测试用例必须以test_开头总结:文件名,类名,方法名,必须符合规则post请求参数到底是传data还是json,此时要看请求头里的content-type类型请求头中content-type为application/json, 为json形式,post请求使用json参数请求头中content-type为application/x-www-form-urlencoded为表单形式,post请求时使用使用data参数json.dumps(data)   将字典格式转换成str格式json.loads(data)    将str格式转换成字典格式pytest用例管理框架的作用发现用例:从多个py文件中找执行测试用例:升序条件判断测试结果:断言生成测试报告:html测试框架python: unittest或者pytestjava:  junit,testng————————————————# 只执行冒烟用例#addopts = -vs -m "smoke"命令执行:pytest -vs       (-v:输出详细信息  -s:输出调试信息 -n 多线程执行

pytest插件的用法

			pytest强大的插件	pytestpip install pytest-ordering  控制用例的执行顺序(重点)pip install pytest-xdist    分布式并发执行测试用例(重点)pip install pytest-dependency   控制用例的依赖关系 (了解)pip install pytest-rerunfailures   失败重跑(了解)pip install pytest-assume          多重较验(了解)pip install pytest-random-order  用例随机执行(了解)pip install pytest-html            测试报告(了解)pip install -r requests.txt

pytest-ordering 用法

没加排序


"""pytest 调整测试用例执行顺序
"""def test_01():print("test 01")def test_02():print("test 02")def test_03():print("test 03")def test_04():print("test 04")E:\Home_Work\Home_Work2\pytest01\test>pytest test_oder.py
collected 4 items                                                                                                                    
=========================执行结果================================
test_oder.py::test_01 test 01
PASSED
test_oder.py::test_02 test 02
PASSED
test_oder.py::test_03 test 03
PASSED
test_oder.py::test_04 test 04
PASSED========================================================= 4 passed in 0.54s =========================================================E:\Home_Work\Home_Work2\pytest01\test>

加了排序

"""pytest 调整测试用例执行顺序
"""
import pytest@pytest.mark.run(order=4)
def test_01():print("test 01")@pytest.mark.run(order=2)
def test_02():print("test 02")@pytest.mark.run(order=3)
def test_03():print("test 03")@pytest.mark.run(order=1)
def test_04():print("test 04")E:\Home_Work\Home_Work2\pytest01\test>pytest test_oder.py
collected 4 items                                                                                                                                                                       
========================执行结果=================================test_oder.py::test_04 test 04
PASSED
test_oder.py::test_02 test 02
PASSED
test_oder.py::test_03 test 03
PASSED
test_oder.py::test_01 test 01
PASSED========================================================= 4 passed in 0.61s =========================================================E:\Home_Work\Home_Work2\pytest01\test>

pytest-xdist 用法

PYTEST 多进程并行与分布式执行 (PS:分布式采用的是多进程)


"""pytest 分布式执行测试用例
"""
import timeimport pytest@pytest.mark.run(order=4)
def test_01():time.sleep(1)print("test 01")@pytest.mark.run(order=2)
def test_02():time.sleep(1)print("test 02")@pytest.mark.run(order=3)
def test_03():time.sleep(1)print("test 03")@pytest.mark.run(order=1)
def test_04():time.sleep(1)print("test 04")==============================执行结果==========================E:\Home_Work\Home_Work2\pytest01\test>pytest -n 8 test_oder.py
testconfig-0.2.0, xdist-2.3.0
[gw0] win32 Python 3.8.7 cwd: E:\Home_Work\Home_Work2\pytest01\test
[gw1] win32 Python 3.8.7 cwd: E:\Home_Work\Home_Work2\pytest01\test
[gw2] win32 Python 3.8.7 cwd: E:\Home_Work\Home_Work2\pytest01\test
[gw3] win32 Python 3.8.7 cwd: E:\Home_Work\Home_Work2\pytest01\test
[gw4] win32 Python 3.8.7 cwd: E:\Home_Work\Home_Work2\pytest01\test
[gw5] win32 Python 3.8.7 cwd: E:\Home_Work\Home_Work2\pytest01\test
[gw6] win32 Python 3.8.7 cwd: E:\Home_Work\Home_Work2\pytest01\test
[gw7] win32 Python 3.8.7 cwd: E:\Home_Work\Home_Work2\pytest01\test
[gw0] Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)]
[gw1] Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)]
[gw2] Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)]
[gw3] Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)]
[gw4] Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)]
[gw5] Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)]
[gw6] Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)]
[gw7] Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)]
gw0 [4] / gw1 [4] / gw2 [4] / gw3 [4] / gw4 [4] / gw5 [4] / gw6 [4] / gw7 [4]
scheduling tests via LoadSchedulingtest_oder.py::test_02
test_oder.py::test_04
test_oder.py::test_03
test_oder.py::test_01
[gw1] PASSED test_oder.py::test_02
[gw3] PASSED test_oder.py::test_01
[gw0] PASSED test_oder.py::test_04
[gw2] PASSED test_oder.py::test_03======================================================== 4 passed in 14.16s =========================================================E:\Home_Work\Home_Work2\pytest01\test>pytest -n 8 test_oder.py

pytest.ini

pytest.ini:
pytest.ini 可以修改 pytest 的默认行为注意: pytest.ini 不能使用任何中文符号,包括汉字、空格、引号、冒号等等;更改默认命令行参数:
将常用的命令行参数设置为默认,省去重复输入的工作;# pytest.ini[pytest]addopts = -rsxX -l -strict --tb=short==========================================================================================
注册 mark 标记:
# pytest.ini[pytest]markers =demo : marks tests as demosmoke: marks tests as smoketest : marks tests as test切记,添加这个需要在每个用例的签名添加@pytest.markers.smoke(order=2)
==========================================================================================
控制台实时输出日志:
# pytest.ini[pytest]log_cli = 1==========================================================================================
指定 pytest 最低版本号:
# pytest.ini[pytest]minversion = 3.0==========================================================================================
指定 pytest 忽略某些目录:
pytest 收集测试用例时,会递归遍历所有子目录,包括某些你明知道没必要遍历的目录,遇到这种情况,可以使用 norecursedirs 参数简化 pytest 的搜索工作;norecursedirs 默认的设置是:.* build dist CVS _darcs {arch} *.egg ,多个路径用空格隔开。# pytest.ini[pytest]norecursedirs = .* build dist CVS _darcs {arch} *.egg venv src==========================================================================================
指定测试目录:
testpaths 限定测试用例的搜索范围,只有在 pytest 范围指定文件目录参数或测试用例标识符时,该选项才会启用;testpaths 指定的路径是以 testpaths 所在的目录为基准的相对路径;# pytest.ini[pytest]testpaths = test_path==========================================================================================
更改测试用例收集规则:
pytest 默认的用例收集规则:1、测试模块必须以 test_ 开头或以 _test 结尾;
2、测试类必须以 Test 开头,且不能有 __init__()3、测试方法必须以 test_ 开头;下面我们来添加自己的收集规则:1、添加 check_ 开头的测试模块;
2、添加 Check 开头的测试类;
3、添加 check_ 开头的测试方法;
# pytest.ini[pytest]python_files =     test_*  *_test  check_*
python_classes =   Test*   Check*
python_functions = test_*  check_*==========================================================================================
禁用 XPASS:
将标记为 @pytest.mark.xfail 但实际通过的测试用例报告为失败;# pytest.ini[pytest]xfail_strict = true==========================================================================================
避免文件名冲突:
为所有的测试目录添加 __init__.py,当多个测试目录拥有重名文件时,__init__.py 可以避免文件名冲突;==========================================================================================
动态添加及获取 ini 配置参数:
# conftest.pyimport pytestdef pytest_addoption(parser):parser.addini('nice', type='bool', default=True, help='添加 ini 参数')@pytest.fixture(autouse=True)
def get_ini(pytestconfig):"""获取 ini 参数"""nice = pytestconfig.getini('nice')print(nice)执行时,只需要在相应的用例文件中输入执行命令pytest.main()

前后置夹具:

def setup(self):print("在每个用例前执行一次")def teardown(self):print("在每个用例后执行一次")setup_class/teardown_class  在每个类之前或者之后执行一次实现部分前置  如果想在其中一个用例做数据库的验证  @pytest.fixtrue(scope="作用域",params="数据驱动",autouse="自动执行",ids="自定义参数",name="重命名")  一般回合conftest.py一起使用作用域: 可以是个 functionclass,模块,或者是个包conftest.py 名称是固定的 功能强大conftest.py 文件是单独存放@pytest.fixtrue方法的,用处是可以在读个py文件中共享前置的配	conftest.py里面的方法调用时不需要导入,可以直接使用

用法:

# coding=utf-8
import json
import re
import pytest as pytest
import requests@pytest.fixture(scope="function")
def conn_databases():print("连接数据库的操作在里面")yieldprint("关闭数据库")# 发送 按住ctrl+鼠标左键
class TestSecondRequest:# 类变量,通过类名来访问access_token = ""csrf_token = ""cookies = ""session = requests.session()def test_get_toke(self, conn_databases):url = "https://api.weixin.qq.com/cgi-bin/token"data = {"grant_type": "client_credential","appid": "wx4a6bb065c448f76f","secret": "e2219aa93e7e2c788961be3edfe7654e"}# self.get_session()# 发送get请求  ,直接使用session来发送请求,这样会话就不会断# 原来  rep = requests.request('get',url=url, params=data)rep = TestSecondRequest.session.request('get', url=url, params=data)print(rep.json())# 通过key取value值TestSecondRequest.access_token = rep.json()['access_token']if __name__ == '__main__':pytest.main(['-vs'])def test_get_toke(self, conn_databases): 只有这个用例才会去执行链接数据库

执行结果:
image.png

conftest.py文件

@pytest.fixtrue()一般回和contftest文件一起使用

1.上面一个案例是在同一个.py文件中,多个用例调用一个登陆功能,如果有多个.py的文件都需要调用这个登陆功能的话,那就不能把登陆写到用例里面去了。
此时应该要有一个配置文件,单独管理一些预置的操作场景,pytest里面默认读取conftest.py里面的配置
conftest.py配置需要注意以下点:

  • conftest.py配置脚本名称是固定的,不能改名称
  • conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件
  • 不需要import导入 conftest.py,pytest用例会自动查找

1、作用是可以在多个文件中共享前置配置
2、contftest.py调用时不需要导入
3、contftest可以有多个,或者多个层级

目录结构
image.png

image.png
image.png

接口关联的封装

生成allure-pytest级别的报告

接口自动化测试框架yaml数据驱动封装

@pytest.mark.parametrize(args_name,args_value)

列表-基础用法

import pytestclass TestApi:@pytest.mark.parametrize('args',['百里','一新','娜扎'])def test_api(self,args):print(args)if __name__ == '__main__':pytest.main(['test_api.py'])

image.png

元组-相当于解剖

import pytestclass TestApi:@pytest.mark.parametrize('name,age',[['百里',10],['一新',11],['娜扎',12]])def test_api2(self,name,age):print(name,age)if __name__ == '__main__':pytest.main(['test_api.py'])

image.png

YAML详解

主要作用:
1、配置文件
2、测试用例

数据包的组成
1、map对象 : 键值 name:号的
2、列表,用- 开头

get_token.yml

msxy- name1:1- name2:2- name3:3

基础结构:

-name: 获得统一鉴权码tokenrequest:method:geturl:https://api.weixin.qq.com/cgi-bin/tokendata:grant_type: client_credentialappid: wx4a6bb065c448f76fsecret: "e2219aa93e7e2c788961be3edfe7654evalidate:None

一个接口就用一个yaml文件

pytest运行的几种方式

运行用例的几种方式:主函数模式:1、运行所有: pytest.main()2、指定模块:pytest.main(['vs','test_login.py'])3、指定目录:pytest.main(['vs','./test_login.py'])4、通过nodeid指定运行用例:pytest.main(['vs','./文件夹名/test_login.py::test+04_fun'])  通过函数名称执行命令模式:1、运行所有:pytest2、指定模块:pytest -vs test_login.py3、指定目录:pytest -vs ./test_login.py

实战训练

目录结构

image.png

common.yaml_util.py

import os
import yamlclass YamlUtil:# 读取extract_yml文件 access_token值的获取def read_extract_yaml(self, key):with open(os.getcwd() + "/extract.yml", mode='r', encoding='utf-8')as f:value = yaml.load(stream=f, Loader=yaml.FullLoader)return value[key];# 写入extract_yml文件def write_extract_yaml(self, data):with open(os.getcwd() + "/extract.yml", mode='w', encoding='utf-8')as f:yaml.dump(data=data, stream=f, allow_unicode=True)def clear_yaml(self):with open(os.getcwd() + "/extract.yml", mode='w', encoding='utf-8')as f:f.truncate()# 读取测试用例的get_token/根据文件名而来的ymal文件def read_testcase_yaml(self,yaml_name):with open(os.getcwd() + "/testDemo/"+yaml_name, mode='r', encoding='utf-8')as f:value = yaml.load(stream=f, Loader=yaml.FullLoader)return value;

common.requests_util.py

import jsonimport requestsclass RequestsUtil:#类变量,通过session =requests.session()def send_request(self,method,url,data,**keyword):method = str(method).lower()rep =Noneif method == 'get':rep = RequestsUtil.session.request(method, url=url, params=data, **keyword)else:data=json.dumps(data)rep = RequestsUtil.session.request(method, url=url, data=data, **keyword)return rep.text

reports

报告的生成文件

temp

临时文件

testDemo.conftest 前置文件

import pytestfrom common.yaml_util import YamlUtil@pytest.fixture(scope="function")
def conn_databases():print("连接数据库的操作在里面")yieldprint("关闭数据库")# 自动清除会话,就不需要在方法里面加
@pytest.fixture(scope="session",autouse=True)
def clear_all_yaml():YamlUtil().clear_yaml()

testDemo.get_token.ymal 用例文件,一个接口一个用例

-name: 获得统一鉴权码tokenrequest:method: geturl: https://api.weixin.qq.com/cgi-bin/tokendata:grant_type: client_credentialappid: wx4a6bb065c448f76fsecret: e2219aa93e7e2c788961be3edfe7654evalidate: None

testDemo.post_edit_id.ymal

-name: 编辑接口用例request:method: posturl: https://api.weixin.qq.com/cgi-bin/tags/updatedata: {"tag": {"id": 134, "name": "广东人"}}validate: None

testDemo.test_second_request.py

# coding=utf-8
import json
import re
from unittest import resultimport pytest as pytest
import requests# @pytest.fixture(scope="function")
# def conn_databases():
#     print("连接数据库的操作在里面")
#     yield
#     print("关闭数据库")# 发送 按住ctrl+鼠标左键
from common.requests_util import RequestsUtil
from common.yaml_util import YamlUtilclass TestSecondRequest:@pytest.mark.parametrize('caseinfo',YamlUtil().read_testcase_yaml('get_token.yml'))def test_get_toke(self, caseinfo):print(caseinfo['name'])print(caseinfo['request']['method'])print(caseinfo['request']['url'])print(caseinfo['request']['data'])print(caseinfo['validate'])method=caseinfo['request']['method']url = caseinfo['request']['url']data = caseinfo['request']['data']# 请求result=RequestsUtil().send_request(method,url,data)# self.get_session()# 发送get请求  ,直接使用session来发送请求,这样会话就不会断# 原来  rep = requests.request('get',url=url, params=data)# result = TestSecondRequest.session.request(method, url=url, params=data)result=json.load(result)print(result)if 'access_token' in  result:# 通过key取value值 把access_token写入yaml中YamlUtil().write_extract_yaml({'access_token': result['access_token']})# 是否有返回这个字符船assert 'access_token' in resultelse:print("异常用例")# assert  result['errcode'] == 200

all.py

import osimport pytestif __name__ == '__main__':pytest.main()# os.system("allure generate temp -o reports --clear")// 生成allure报告

pytest.ini

[pytest]
addopts=-vs  --alluredir ./temp
testpaths=./testDemo
python_classes=Test*
python_functions=test_*markers =smoke:maoyan

问题:
在yml文件没法打实现动态参数
在yml没法实现动态上传
在yml没法实现断言
在yml文件里面数据量太大了怎么办

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

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

相关文章

GC overhead limit exceeded问题

1.问题现象 程序包运行时候发生了java.lang.OutOfMemoryError: GC overhead limit exceeded异常, 详细信息如下 org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.jboss.util.NestedSQLException: Error; - nested t…

【斗罗二】冰帝两次险些杀死雨浩,天梦哥求助伊老遭拒绝,霍云儿现身救儿子

Hello,小伙伴们,我是小郑继续为大家深度解析绝世唐门。 斗罗大陆动画第二部绝世唐门已经更新了,霍雨浩与冰帝完美融合,成功觉醒了第二武魂,霍挂的时代正式到来。只是在整个第19集中,官方做了大量的改编,不但…

AC修炼计划(AtCoder Regular Contest 167)

传送门:AtCoder Regular Contest 167 - AtCoder 再次感谢樱雪喵大佬的题解,讲的很详细,Orz。 大佬的博客链接如下:Atcoder Regular Contest 167 - 樱雪喵 - 博客园 (cnblogs.com) 第一题很签到,就省略掉了。 第二题…

数据结构——二叉树的公共祖先问题

数据结构——二叉树的公共祖先问题 236. 二叉树的最近公共祖先思路 235. 二叉搜索树的最近公共祖先思路1.递归2.迭代 236. 二叉树的最近公共祖先 236. 二叉树的最近公共祖先 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#x…

(矩阵) 289. 生命游戏 ——【Leetcode每日一题】

❓ 289. 生命游戏 难度:中等 根据 百度百科 , 生命游戏 ,简称为 生命 ,是英国数学家约翰何顿康威在 1970 年发明的细胞自动机。 给定一个包含 m n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一…

16-spring AOP核心对象的创建

文章目录 1. aop的几个重要概念2. aop bean definition3. AspectJPointcutAdvisor4.AopConfigUtils5.AnnotationAwareAspectJAutoProxyCreator6. 循环依赖 1. aop的几个重要概念 参考官方解释:https://docs.spring.io/spring-framework/docs/5.2.9.RELEASE/spring-…

大数据之LibrA数据库系统概览

实时监控 “实时监控”页面如图1所示,用户可单击刷新按钮手动刷新当前页面,也可在点刷新按钮前选择自动刷新时长,刷新时长包括:每30秒刷新一次、每60秒刷新一次、停止刷新。 实时监控数据(监控时间轴产生的新曲线&am…

Oracle数据中如何在 where in() 条件传参

一、问题场景描述 在sql 条件中,如何在 where in()中想传入参数,如果直接 where in(:seqList),当传入单个值,seqList: ‘80’ 是没问题的,但是初入多个值时,seqList: ‘80,90’ ,因缺少单引号&…

2 用TensorFlow构建一个简单的神经网络

上一篇:1 如何入门TensorFlow-CSDN博客 环境搭建 后续介绍的相关代码都是在pycharm运行,pycharm安装略。 打开pycharm,创建一个新的项目用于tensorflow编码练习,在Terminal输入命令: # 依赖最新版本的pip pip inst…

Pytest自动化测试框架之Allure报告详解

简介 Allure Framework是一种灵活的、轻量级、多语言测试报告工具。 不仅可以以简洁的网络报告形式非常简洁地显示已测试的内容, 而且还允许参与开发过程的每个人从日常执行中提取最大程度的有用信息和测试。 从开发/测试的角度来看: Allure报告可以…

线性代数的本质笔记

课程来自b站发现的《线性代数的本质》,可以帮助从直觉层面理解线性代数的一些基础概念,以及把一些看似不同的数学概念解释之后,发现其实有内在的关联。 这里只对部分内容做一个记录,完整内容请自行观看视频~ 01-向量究竟是什么 …

LeetCode 2316. 统计无向图中无法互相到达点对数::广度优先搜索(BFS)

【LetMeFly】2316.统计无向图中无法互相到达点对数:广度优先搜索(BFS) 力扣题目链接:https://leetcode.cn/problems/count-unreachable-pairs-of-nodes-in-an-undirected-graph/ 给你一个整数 n ,表示一张 无向图 中…

2023年中国酒类新零售行业发展概况分析:线上线下渠道趋向深度融合[图]

近年来,我国新零售业态不断发展,线上便捷性和个性化推荐的优势逐步在放大,线下渠道智慧化水平持续提升,线上线下渠道趋向深度融合。2022年,我国酒类新零售市场规模约为1516亿元,预计2025年酒类新零售市场规…

Mysql——查询sql语句练习

一、单表查询 素材: 表名:worker-- 表中字段均为中文,比如 部门号 工资 职工号 参加工作 等 1、显示所有职工的基本信息。 select * from worker; 2、查询所有职工所属部门的部门号,不显示重复的部门号。 select distinct 部门号…

使用Chrome浏览器进行网页截图

在需要截图的网页上,按F12打开开发调试页面,再按下ShiftCtrlP,打开命令输入框,输入Capture, 此时会弹出4中截图模式,我个人比较喜欢用Capture full size screenshot Capture area screenshot,…

2023年中国纸箱机械优点、市场规模及发展前景分析[图]

纸箱机械行业是指涉及纸箱生产和加工的机械设备制造、销售和相关服务的产业。这个行业的主要任务是设计、制造和提供用于生产各种类型和规格纸箱的机械设备,以满足包装行业对纸箱的不同需求。 纸箱机械行业优点 资料来源:共研产业咨询(共研网…

将语义分割的标注mask转为目标检测的bbox

1. 语义分割标签 1.1 labelme工具 语义分割的标签是利用labelme工具进行标注的,标注的样式如下: 1.2 语义分割的标签样式 2. 转换语义分割的标注到目标检测的bbox 实现步骤 (1) 利用标注的json文件生成mask图片(2) 在mask图片中找到目标的bbox矩形框的左上角点和右下角点(…

d3dx9_43.dll丢失怎么解决,四个解决方法帮你解决d3dx9_43.dll丢失

随着科技的不断发展,我们越来越依赖各种软件和硬件设备来提高生活和工作效率。然而,有时候我们可能会遇到一些技术问题,如“d3dx9_43.dll丢失”的问题。这个问题可能导致某些程序无法正常运行,给我们的生活带来诸多不便。因此&…

java中的容器(集合),HashMap底层原理,ArrayList、LinkedList、Vector区别,hashMap加载因子0.75原因

一、java中的容器 集合主要分为Collection和Map两大接口;Collection集合的子接口有List、Set;List集合的实现类有ArrayList底层是数组、LinkedList底层是双向非循环列表、Vector;Set集合的实现类有HashSet、TreeSet;Map集合的实现…

优化销售策略,突破企业全面预算管理难题

传统的企业年度销售计划往往会消耗企业内部人员很多精力和时间,比如需要收集数据、处理电子表格、确定项目优先级、预测未来发展以及为次年的费用制定预算等。然而随着这些繁琐的工作不断进行,其中的准确性和价值也受到了一定的怀疑。虽然销售计划仍按着…