python测验4_Python接口测试实战4(上) - 接口测试框架实战

如有任何学习问题,可以添加作者微信:lockingfree

课程目录

本节内容

数据分离 - 从Excel中读取数据

增加log功能

发送邮件

使用配置文件

框架整理

1240

数据分离 - 从Excel中读取数据

之前的用例中,数据直接写在代码文件里,不利于修改和构造数据

这里我们使用Excel保存测试数据,实现代码和数据的分离

新建Excel文件test_user_data.xlsx包含两个工作簿TestUserLogin和TestUserReg,并复制到项目根目录下

1240

更新: excel表格中,增加一个headers列,内容为json格式, 如下

TestUserLogin

case_name

url

method

headers

data

expect_res

test_user_login_normal

POST

{}

{"name": "张三","password":"123456"}

登录成功

test_user_login_password_wrong

POST

{}

{"name": "张三","password":"1234567"}

失败,用户名或密码错误

TestUserReg

case_name

url

method

headers

data

expect_res

test_user_reg_normal

POST

{}

{"name": "范冰冰","password":"123456"}

"{"code: "100000","msg": "成功,"data":

test_user_reg_exist

POST

{}

{"name": "张三","password":"123456"}

"{"code": "100001","msg": "失败,用户已存在","data": {"name": "张三","password":"e10adc3949ba59abbe56e057f20f883e"}}"

Excel读取方法:

Python我们使用三方库xlrd来读取Excel

安装方法: pip install xlrd

import xlrd

wb = xlrd.open_workbook("test_user_data.xlsx") # 打开excel

sh = wb.sheet_by_name("TestUserLogin") # 按工作簿名定位工作表

print(sh.nrows) # 有效数据行数

print(sh.ncols) # 有效数据列数

print(sh.cell(0, 0).value) # 输出第一行第一列的值`case_name`

print(sh.row_values(0)) # 输出第1行的所有值(列表格式)

# 将数据和标题组装成字典,使数据更清晰

print(dict(zip(sh.row_values(0), sh.row_values(1))))

# 遍历excel,打印所有的数据

for i in range(sh.nrows):

print(sh.row_values(i))

结果:

3

5

case_name

['case_name', 'url', 'method', 'data', 'expect_res']

{'case_name': 'test_user_login_normal', 'url': 'http://115.28.108.130:5000/api/user/login/', 'method': 'POST', 'data': '{"name": "张三","password":"123456"}', 'expect_res': '

登录成功

'}

['case_name', 'url', 'method', 'data', 'expect_res']

['test_user_login_normal', 'http://115.28.108.130:5000/api/user/login/', 'POST', '{"name": "张三","password":"123456"}', '

登录成功

']

['test_user_login_password_wrong', 'http://115.28.108.130:5000/api/user/login/', 'POST', '{"name": "张三","password":"1234567"}', '

失败,用户不存在

']

封装读取excel操作:

新建read_excel.py

1240

我们的目的是获取某条用例的数据,需要3个参数,excel数据文件名(data_file),工作簿名(sheet),用例名(case_name)

如果我们只封装一个函数,每次调用(每条用例)都要打开一次excel并遍历一次,这样效率比较低。

我们可以拆分成两个函数,一个函数excel_to_list(data_file, sheet),一次获取一个工作表的所有数据,另一个函数get_test_data(data_list, case_name)从所有数据中去查找到该条用例的数据。

import xlrd

def excel_to_list(data_file, sheet):

data_list = [] # 新建个空列表,来乘装所有的数据

wb = xlrd.open_workbook(data_file) # 打开excel

sh = wb.sheet_by_name(sheet) # 获取工作簿

header = sh.row_values(0) # 获取标题行数据

for i in range(1, sh.nrows): # 跳过标题行,从第二行开始取数据

d = dict(zip(header, sh.row_values(i))) # 将标题和每行数据组装成字典

data_list.append(d)

return data_list # 列表嵌套字典格式,每个元素是一个字典

def get_test_data(data_list, case_name):

for case_data in data_list:

if case_name == case_data['case_name']: # 如果字典数据中case_name与参数一致

return case_data

# 如果查询不到会返回None

if __name__ == '__main__': # 测试一下自己的代码

data_list = excel_to_list("test_user_data.xlsx", "TestUserLogin") # 读取excel,TestUserLogin工作簿的所有数据

case_data = get_test_data(data_list, 'test_user_login_normal') # 查找用例'test_user_login_normal'的数据

print(case_data)

输出结果:

{'case_name': 'test_user_login_normal', 'url': 'http://115.28.108.130:5000/api/user/login/', 'method': 'POST', 'data': '{"name": "张三","password":"123456"}', 'expect_res': '

登录成功

'}

用例中使用方法

test_user_login.py 部分

import unittest

import requests

from read_excel import * # 导入read_excel中的方法

import json # 用来转化excel中的json字符串为字典

class TestUserLogin(unittest.TestCase):

@classmethod

def setUpClass(cls): # 整个测试类只执行一次

cls.data_list = excel_to_list("test_user_data.xlsx", "TestUserLogin") # 读取该测试类所有用例数据

# cls.data_list 同 self.data_list 都是该类的公共属性

def test_user_login_normal(self):

case_data = get_test_data(self.data_list, 'test_user_login_normal') # 从数据列表中查找到该用例数据

if not case_data: # 有可能为None

print("用例数据不存在")

url = case_data.get('url') # 从字典中取数据,excel中的标题也必须是小写url

data = case_data.get('data') # 注意字符串格式,需要用json.loads()转化为字典格式

expect_res = case_data.get('expect_res') # 期望数据

res = requests.post(url=url, data=json.loads(data)) # 表单请求,数据转为字典格式

self.assertEqual(res.text, expect_res) # 改为assertEqual断言

if __name__ == '__main__': # 非必要,用于测试我们的代码

unittest.main(verbosity=2)

test_user_reg.py部分

import unittest

import requests

from db import *

from read_excel import *

import json

class TestUserReg(unittest.TestCase):

@classmethod

def setUpClass(cls):

cls.data_list = excel_to_list("test_user_data.xlsx", "TestUserReg") # 读取TestUserReg工作簿的所有数据

def test_user_reg_normal(self):

case_data = get_test_data(self.data_list, 'test_user_reg_normal')

if not case_data:

print("用例数据不存在")

url = case_data.get('url')

data = json.loads(case_data.get('data')) # 转为字典,需要取里面的name进行数据库检查

expect_res = json.loads(case_data.get('expect_res')) # 转为字典,断言时直接断言两个字典是否相等

name = data.get("name") # 范冰冰

# 环境检查

if check_user(name):

del_user(name)

# 发送请求

res = requests.post(url=url, json=data) # 用data=data 传字符串也可以

# 响应断言(整体断言)

self.assertDictEqual(res.json(), expect_res)

# 数据库断言

self.assertTrue(check_user(name))

# 环境清理(由于注册接口向数据库写入了用户信息)

del_user(name)

if __name__ == '__main__': # 非必要,用于测试我们的代码

unittest.main(verbosity=2)

增加log功能

新建config.py文件

import logging

logging.basicConfig(level=logging.DEBUG, # log level

format='[%(asctime)s] %(levelname)s [%(funcName)s: %(filename)s, %(lineno)d] %(message)s', # log格式

datefmt='%Y-%m-%d %H:%M:%S', # 日期格式

filename='log.txt', # 日志输出文件

filemode='a') # 追加模式

if __name__ == '__main__':

logging.info("hello")

运行后在当前目录下生成log.txt,内容如下:

[2018-09-11 18:08:17] INFO [: config.py, 38] hello

Log Level:

CRITICAL: 用于输出严重错误信息

ERROR: 用于输出错误信息

WARNING: 用于输出警示信息

INFO: 用于输出一些提升信息

DEBUG: 用于输出一些调试信息

优先级 CRITICAL > ERROR > WARNING > INFO > DEBUG

指定level = logging.DEBUG所有等级大于等于DEBUG的信息都会输出

若指定level = logging.ERROR WARNING,INFO,DEBUG小于设置级别的信息不会输出

日志格式:

%(levelno)s: 打印日志级别的数值

%(levelname)s: 打印日志级别名称

%(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]

%(filename)s: 打印当前执行程序名

%(funcName)s: 打印日志的当前函数

%(lineno)d: 打印日志的当前行号

%(asctime)s: 打印日志的时间

%(thread)d: 打印线程ID

%(threadName)s: 打印线程名称

%(process)d: 打印进程ID

%(message)s: 打印日志信息

项目使用log

将所有print改为log,如db.py 部分

import pymysql

from config import *

# 封装数据库查询操作

def query_db(sql):

conn = get_db_conn()

cur = conn.cursor()

logging.debug(sql) # 输出执行的sql

cur.execute(sql)

conn.commit()

result = cur.fetchall()

logging.debug(result) # 输出查询结果

cur.close()

conn.close()

return result

# 封装更改数据库操作

def change_db(sql):

conn = get_db_conn()

cur = conn.cursor()

logging.debug(sql) # 输出执行的sql

try:

cur.execute(sql)

conn.commit()

except Exception as e:

conn.rollback()

logging.error(str(e)) # 输出错误信息

finally:

cur.close()

conn.close()

用例中使用

import unittest

import requests

from read_excel import * # 导入read_excel中的方法

import json # 用来转化excel中的json字符串为字典

from config import *

class TestUserLogin(unittest.TestCase):

@classmethod

def setUpClass(cls): # 整个测试类只执行一次

cls.data_list = excel_to_list("test_user_data.xlsx", "TestUserLogin") # 读取该测试类所有用例数据

# cls.data_list 同 self.data_list 都是该类的公共属性

def test_user_login_normal(self):

case_data = get_test_data(self.data_list, 'test_user_login_normal') # 从数据列表中查找到该用例数据

if not case_data: # 有可能为None

logging.error("用例数据不存在")

url = case_data.get('url') # excel中的标题也必须是小写url

data = case_data.get('data') # 注意字符串格式,需要用json.loads()转化为字典格式

expect_res = case_data.get('expect_res') # 期望数据

res = requests.post(url=url, data=json.loads(data)) # 表单请求,数据转为字典格式

logging.info("测试用例:{}".format('test_user_login_normal'))

logging.info("url:{}".format(url))

logging.info("请求参数:{}".format(data))

logging.info("期望结果:{}".format(expect_res))

logging.info("实际结果:{}".format(res.text)

self.assertEqual(res.text, expect_res) # 断言

if __name__ == '__main__':

unittest.main(verbosity=2)

项目下log.txt输出结果:

[2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 8] 测试用例:test_user_login_normal

[2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 9] url:http://115.28.108.130:5000/api/user/login/

[2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 10] 请求参数:{"name": "张三","password":"123456"}

[2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 11] 期望结果:

登录成功

[2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 12] 实际结果:

登录成功

因为每个用例都要输出很多log信息,我们封装一个case_log的函数

项目下新建case_log.py

from config import *

import json

def log_case_info(case_name, url, data, expect_res, res_text):

if isinstance(data,dict):

data = json.dumps(data, ensure_ascii=False) # 如果data是字典格式,转化为字符串

logging.info("测试用例:{}".format(case_name))

logging.info("url:{}".format(url))

logging.info("请求参数:{}".format(data))

logging.info("期望结果:{}".format(expect_res))

logging.info("实际结果:{}".format(res_text)

简化后的用例log输出

import unittest

import requests

from read_excel import *

import json

from config import *

from case_log import log_case_info # 导入方法

class TestUserLogin(unittest.TestCase):

@classmethod

def setUpClass(cls):

cls.data_list = excel_to_list("test_user_data.xlsx", "TestUserLogin")

def test_user_login_normal(self):

case_data = get_test_data(self.data_list, 'test_user_login_normal')

if not case_data:

logging.error("用例数据不存在")

url = case_data.get('url')

data = case_data.get('data')

expect_res = case_data.get('expect_res')

res = requests.post(url=url, data=json.loads(data))

log_case_info('test_user_login_normal', url, data, expect_res, res_text) # 输出用例log信息

self.assertEqual(res.text, expect_res)

if __name__ == '__main__':

unittest.main(verbosity=2)

发送邮件

在生成报告后我们希望框架能自动把报告发送到我们的邮箱中。和outlook,foxmail等邮件客户端一样,Python中发送邮件需要通过Email的smtp服务发送。

首先需要确认用来发送邮件的邮箱是否启用了smtp服务

发送邮件分3步

编写邮件内容(Email邮件需要专门的MIME格式)

组装Email头(发件人,收件人,主题)

连接smtp服务器并发送邮件

import smtplib # 用于建立smtp连接

from email.mime.text import MIMEText # 邮件需要专门的MIME格式

# 1. 编写邮件内容(Email邮件需要专门的MIME格式)

msg = MIMEText('this is a test email', 'plain', 'utf-8') # plain指普通文本格式邮件内容

# 2. 组装Email头(发件人,收件人,主题)

msg['From'] = 'test_results@sina.com' # 发件人

msg['To'] = '2375247815@qq.com' # 收件人

msg['Subject'] = 'Api Test Report' # 邮件主题

# 3. 连接smtp服务器并发送邮件

smtp = smtplib.SMTP_SSL('smtp.sina.com') # smtp服务器地址 使用SSL模式

smtp.login('自己的邮箱地址', '自己的邮箱密码') # 用户名和密码

smtp.sendmail("接收邮件地址1", "接收邮件地址2", msg.as_string())

smtp.quit()

中文邮件主题、HTML邮件内容,及附件

import smtplib

from email.mime.text import MIMEText

from email.mime.multipart import MIMEMultipart # 混合MIME格式,支持上传附件

from email.header import Header # 用于使用中文邮件主题

# 1. 编写邮件内容

with open('report.html', encoding='utf-8') as f: # 打开html报告

email_body = f.read() # 读取报告内容

msg = MIMEMultipart() # 混合MIME格式

msg.attach(MIMEText(email_body, 'html', 'utf-8')) # 添加html格式邮件正文(会丢失css格式)

# 2. 组装Email头(发件人,收件人,主题)

msg['From'] = 'test_results@sina.com' # 发件人

msg['To'] = '2375247815@qq.com' # 收件人

msg['Subject'] = Header('接口测试报告', 'utf-8') # 中文邮件主题,指定utf-8编码

# 3. 构造附件1,传送当前目录下的 test.txt 文件

att1 = MIMEText(open('report.html', 'rb').read(), 'base64', 'utf-8') # 二进制格式打开

att1["Content-Type"] = 'application/octet-stream'

att1["Content-Disposition"] = 'attachment; filename="report.html"' # filename为邮件中附件显示的名字

msg.attach(att1)

# 4. 连接smtp服务器并发送邮件

smtp = smtplib.SMTP_SSL('smtp.sina.com') # smtp服务器地址 使用SSL模式

smtp.login('test_results@sina.com', 'hanzhichao123') # 用户名和密码

smtp.sendmail("test_results@sina.com", "2375247815@qq.com", msg.as_string())

smtp.sendmail("test_results@sina.com", "superhin@126.com", msg.as_string()) # 发送给另一个邮箱

smtp.quit()

封装发送邮件方法

import smtplib

from email.mime.text import MIMEText

from email.mime.multipart import MIMEMultipart # 混合MIME格式,支持上传附件

from email.header import Header # 用于使用中文邮件主题

from config import *

def send_email(report_file):

msg = MIMEMultipart() # 混合MIME格式

msg.attach(MIMEText(open(report_file, encoding='utf-8').read(), 'html', 'utf-8')) # 添加html格式邮件正文(会丢失css格式)

msg['From'] = 'test_results@sina.com' # 发件人

msg['To'] = '2375247815@qq.com' # 收件人

msg['Subject'] = Header('接口测试报告', 'utf-8') # 中文邮件主题,指定utf-8编码

att1 = MIMEText(open(report_file, 'rb').read(), 'base64', 'utf-8') # 二进制格式打开

att1["Content-Type"] = 'application/octet-stream'

att1["Content-Disposition"] = 'attachment; filename="report.html"' # filename为邮件中附件显示的名字

msg.attach(att1)

try:

smtp = smtplib.SMTP_SSL('smtp.sina.com') # smtp服务器地址 使用SSL模式

smtp.login('test_results@sina.com', 'hanzhichao123') # 用户名和密码

smtp.sendmail("test_results@sina.com", "2375247815@qq.com", msg.as_string())

smtp.sendmail("test_results@sina.com", "superhin@126.com", msg.as_string()) # 发送给另一个邮箱

logging.info("邮件发送完成!")

except Exception as e:

logging.error(str(e))

finally:

smtp.quit()

run_all.py中结束后发送邮件

import unittest

from HTMLTestReportCN import HTMLTestRunner

from config import *

from send_email import send_email

logging.info("====================== 测试开始 =======================")

suite = unittest.defaultTestLoader.discover("./")

with open("report.html", 'wb') as f: # 改为with open 格式

HTMLTestRunner(stream=f, title="Api Test", description="测试描述", tester="卡卡").run(suite)

send_email('report.html') # 发送邮件

logging.info("======================= 测试结束 =======================")

1240

使用配置文件

和项目的log配置一样,数据库服务器地址,邮件服务地址我们一般放到配置文件config.py中

import logging

import os

# 项目路径

prj_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件的绝对路径的上一级,__file__指当前文件

data_path = prj_path # 数据目录,暂时在项目目录下

test_path = prj_path # 用例目录,暂时在项目目录下

log_file = os.path.join(prj_path, 'log.txt') # 也可以每天生成新的日志文件

report_file = os.path.join(prj_path, 'report.html') # 也可以每次生成新的报告

# log配置

logging.basicConfig(level=logging.DEBUG, # log level

format='[%(asctime)s] %(levelname)s [%(funcName)s: %(filename)s, %(lineno)d] %(message)s', # log格式

datefmt='%Y-%m-%d %H:%M:%S', # 日期格式

filename=log_file, # 日志输出文件

filemode='a') # 追加模式

# 数据库配置

db_host = '127.0.0.1' # 自己的服务器地址

db_port = 3306

db_user = 'test'

db_passwd = '123456'

db = 'api_test'

# 邮件配置

smtp_server = 'smtp.sina.com'

smtp_user = 'test_results@sina.com'

smtp_password = 'hanzhichao123'

sender = smtp_user # 发件人

receiver = '2375247815@qq.com' # 收件人

subject = '接口测试报告' # 邮件主题

修改db.py,send_email.py,run_all.py等对配置文件的引用

db.py部分

import pymysql

from config import *

# 获取连接方法

def get_db_conn():

conn = pymysql.connect(host=db_host, # 从配置文件中读取

port=db_port,

user=db_user,

passwd=db_passwd, # passwd 不是 password

db=db,

charset='utf8') # 如果查询有中文,需要指定测试集编码

send_email.py

import smtplib

from email.mime.text import MIMEText

from email.mime.multipart import MIMEMultipart

from email.header import Header

from config import *

def send_email(report_file):

msg = MIMEMultipart()

msg.attach(MIMEText(open(report_file, encoding='utf-8').read(), 'html', 'utf-8'))

msg['From'] = 'test_results@sina.com'

msg['To'] = '2375247815@qq.com'

msg['Subject'] = Header(subject, 'utf-8') # 从配置文件中读取

att1 = MIMEText(open(report_file, 'rb').read(), 'base64', 'utf-8') # 从配置文件中读取

att1["Content-Type"] = 'application/octet-stream'

att1["Content-Disposition"] = 'attachment; filename="{}"'.format(report_file) # 参数化一下report_file

msg.attach(att1)

try:

smtp = smtplib.SMTP_SSL(smtp_server) # 从配置文件中读取

smtp.login(smtp_user, smtp_password) # 从配置文件中读取

smtp.sendmail(sender, receiver, msg.as_string())

logging.info("邮件发送完成!")

except Exception as e:

logging.error(str(e))

finally:

smtp.quit()

run_all.py

import unittest

from HTMLTestReportCN import HTMLTestRunner

from config import *

from send_email import send_email

logging.info("==================== 测试开始 =======================")

suite = unittest.defaultTestLoader.discover(test_path) # 从配置文件中读取用例路径

with open(report_file, 'wb') as f: # 从配置文件中读取

HTMLTestRunner(stream=f, title="Api Test", description="测试描述").run(suite)

send_email(report_file) # 从配置文件中读取

logging.info("==================== 测试结束 =======================")

框架整理

1240

当前所有文件(配置文件,公共方法,测试用例,数据,报告,log)都在项目根目录下,随着用例的增加和功能的补充,文件会越来越多,不便于维护和管理,因此我们要建立不同的文件夹,对文件进行分类组织

1.在项目中新建以下文件夹:

config: 存放项目配置文件

data: 存放用例数据文件

lib: 公共方法库

log: 存放日志文件

report: 存放报告文件

test: 存放测试用例

user: 存放user模块用例 (模块下要有__init__.py,这样里面的用例才能读取到)

2.将配置文件config.py移动到config目录下,将数据文件test_user_data.xlsx移动到data目录下,将公共方法db.py send_email.py case_log.py read_excel.py HTMLTestReportCN.py移动到lib目录下,将测试用例test_user_login.py test_user_reg.py移动到test/user目录下,保留run_all.py在项目根目录下,如图:

1240

3.修改配置文件

config/config.py部分

import logging

import os

# 项目路径

prj_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 当前文件的上一级的上一级目录(增加一级)

data_path = os.path.join(prj_path, 'data') # 数据目录

test_path = os.path.join(prj_path, 'test') # 用例目录

log_file = os.path.join(prj_path, 'log', 'log.txt') # 更改路径到log目录下

report_file = os.path.join(prj_path, 'report', 'report.html') # 更改路径到report目录下

4.修改对配置文件及公共方法的引用

为避免相对路径导包出错的问题,我们统一把导包搜索路径(sys.path)提升到项目根目录下,如lib/db.py

lib/db.py 部分

import pymysql

import sys

sys.path.append('..') # 提升一级到项目更目录下

from config.config import * # 从项目根目录下导入

测试用例test_user_login.py部分

import unittest

import requests

import json

import os # 增加了一个os,需要用来组装路径

import sys

sys.path.append("../..") # 提升2级到项目根目录下

from config.config import * # 从项目路径下导入

from lib.read_excel import * # 从项目路径下导入

from lib.case_log import log_case_info # 从项目路径下导入

class TestUserLogin(unittest.TestCase):

@classmethod

def setUpClass(cls): # 整个测试类只执行一次

cls.data_list = excel_to_list(os.path.join(data_path, "test_user_data.xlsx"),"TestUserLogin") # 增加data路径

run_all.py

import unittest

from lib.HTMLTestReportCN import HTMLTestRunner # 修改导入路径

from config.config import * # 修改导入路径

from lib.send_email import send_email # 修改导入路径

logging.info("================================== 测试开始 ==================================")

suite = unittest.defaultTestLoader.discover(test_path) # 从配置文件中读取

with open(report_file, 'wb') as f: # 从配置文件中读取

HTMLTestRunner(stream=f, title="Api Test", description="测试描述").run(suite)

send_email(report_file) # 从配置文件中读取

logging.info("================================== 测试结束 ==================================")

如果同一文件夹下的方法相互引用(如lib/read_excel.py假如需要引用lib/db.py),也需要采用这种从项目路径下导入的方式

run_all.py直接在项目路径下,不需要提升sys.path,无需相对导入我们自己的包时,如read_excel.py,不需要提升

5.运行run_all.py,根据log和报告调试代码,直至所有用例全部通过

此为北京龙腾育才 Python高级自动化(接口测试部分)授课笔记

课程介绍

想要参加现场(北京)/网络课程的可以联系作者微信:lockingfree

高效学习,快速掌握Python自动化所有领域技能

同步快速解决各种问题

配套实战项目练习

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值