电子邮件的在网络中传输和网页一样需要遵从特定的协议,常用的电子邮件协议包括 SMTP,POP3,IMAP。其中邮件的创建和发送只需要用到 SMTP协议,所以本文也只会涉及到SMTP协议。SMTP 是 Simple Mail Transfer Protocol 的简称,即简单邮件传输协议。
SMTP(简单邮件传输协议),邮件传送代理程序使用SMTP协议来发送电邮到接收者的邮件服务器。SMTP协议只能用来发送邮件,不能用来接收邮件,而大多数的邮件发送服务器都是使用SMTP协议。SMTP协议的默认TCP端口号是25。
因为邮件发送,涉及多端(本地代码端、邮件发送服务器端、邮件接收服务器端),保证写代码没问题,不代表能成功,你的邮件提交到邮件发送服务器,发送的服务器可以给你拒绝服务(比如认为发送的内容是垃圾广告,或者你频繁请求发送),成功发送到对方邮件接收服务器后,对方的服务器也可以根据你的内容拒绝收你的邮件(如认为内容是广告诈骗等信息,或者发送过于频繁),在测试项目中我们一般用公司的邮箱,成功率会很高。
关于邮件格式 (RFC 2822),每封邮件都有两个部分:邮件头和邮件体,两者使用一个空行分隔。邮件头每个字段 (Field) 包括两部分:字段名和字段值,两者使用冒号分隔。有两个字段需要注意:From和Sender字段。From字段指明的是邮件的作者,Sender字段指明的是邮件的发送者。如果From字段包含多于一个的作者,必须指定Sender字段;如果From字段只有一个作者并且作者和发送者相同,那么不应该再使用Sender字段,否则From字段和Sender字段应该同时使用。
邮件体包含邮件的内容,它的类型由邮件头的Content-Type字段指明。RFC 2822定义的邮件格式中,邮件体只是单纯的ASCII编码的字符序列。而MIME (Multipurpose Internet Mail Extensions) (RFC 1341)是扩展邮件的格式,用以支持非ASCII编码的文本、非文本附件以及包含多个部分 (multi-part) 的邮件体等。
Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。Python对SMTP支持有smtplib和email两个模块,email负责构造邮件,smtplib负责发送邮件。
1. smtplib
smtplib库用来发送邮件。其中smtplib.SMTP([host[, port[, local_hostname[, timeout]]]]):此为SMTP类构造函数,表示与SMTP服务器之间的连接,并根据这个连接向smtp服务器发送指令,执行相关操作(如:登陆、发送邮件),且每个参数都是可选的。其中最重要的参数是host:smtp服务器主机名;port:smtp服务的端口,默认是25;
如果在创建SMTP对象的时候提供了这两个参数,在初始化的时候会自动调用connect方法去连接服务器。
smtplib.SMTP还提供了如下方法:
2.email
email负责构造邮件,模块提供如下方法:
实例:发送纯文本邮件:
from email.mime.text import MIMEText #导入email模块
msg = MIMEText('你好,seleniumwebdriver自动化测试...', 'plain', 'utf-8')
构造MIMEText对象时,第一个参数是邮件正文,第二个参数为MIME的subtype,传入'plain',最终的MIME就是'text/plain',最后要用utf-8编码保证多语言兼容性。实现过程如下:
A. qq邮箱SMTP
B. SMTP服务器需要身份验证。
1) 开启qq邮箱的smtp服务
为了保障用户邮箱的安全,QQ邮箱设置了POP3/SMTP/IMAP的开关。系统缺省设置是“关闭”,在用户需要这些功能时请“开启”。 首先,登录邮箱,进入设置-帐户;
在“帐户”设置中,开启Pop3/SMTP服务,获得授权码,保存设置,即打开了相应的服务。
2) 实例代码:
import smtplib
from email.header import Header
from email.mime.text import MIMEText
mail_info={
"from": "[email protected]",
"to": "[email protected]",
"hostname": "smtp.qq.com",
"username": "[email protected]",
"password": "tlnajaxviwhjbcgi",
"mail_subject": "自动化测试报告",
"mail_text": "Selenium Webdriver自动化测试报告",
"mail_encoding": "utf-8"
}
技术解释:用mail_info字典构造保存发送邮件信息的基本信息,包含发送邮件人,接收邮件人。From键保存的值是发送邮件人,to键保存的值是收邮件人,hostname键保存的邮件服务器地址,username键保存的值是登陆邮件服务器的用户名,password键保存的值是用户登录邮件服务器的密码,因为登陆的是qq服务器,所以是临时的授权码。
if __name__ == '__main__':
smtp =smtplib.SMTP_SSL(mail_info["hostname"],465) # 这里使用SMTP_SSL就是默认使用465端口
smtp.set_debuglevel(1)
smtp.ehlo(mail_info["hostname"])
smtp.login(mail_info["username"], mail_info["password"])
msg = MIMEText(mail_info["mail_text"], "plain", mail_info["mail_encoding"])
msg["Subject"] = Header(mail_info["mail_subject"], mail_info["mail_encoding"])
msg["from"] = mail_info["from"]
msg["to"] = mail_info["to"]
smtp.sendmail(mail_info["from"], mail_info["to"], msg.as_string())
smtp.quit()
注意:如果收件人地址错误,但代码还是会提示"邮件发送成功",如果是地址错误在qq邮箱中会收到"来自http://qq.com的退信"。
利用class的知识把sendmail进行封装,代码如下:
import smtplib
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr
class SendEmail():
outbox = "[email protected] "
password = "tlnajaxviwhjbcgi"
inbox = "[email protected]"
smtp_server = "smtp.qq.com"
技术解释:封装Sendmail类,outbox,password,inbox,smtp_server都是类的属性
def _format_address(self, text):
name, address = parseaddr(text)
return formataddr((Header(name, "utf-8").encode(), address))
技术解释:_format_address是私有方法,不能直接通过类实例调用。parseaddr 是email.utils.parseaddr中的方法,它的作用是解析邮件地址,原因是邮件地址很多时候在原文里是这样写的:user1
def send_email_text(self):
msg = MIMEText("Selenium Webdriver自动化测试报告", "plain", "utf-8") # 第一个参数:邮件正文 第二个参数:邮件类型 纯文本 第三个参数:编码
msg["From"] = self._format_address("测试人员 <%s>" % self.outbox) # 发件人姓名与发件箱地址
msg["To"] = self._format_address("开发经理 <%s>" % self.inbox) # 收件人姓名与收件箱地址
msg["Subject"] = Header("自动化测试报告", "utf-8").encode() # 邮件标题
try:
server = smtplib.SMTP_SSL(self.smtp_server,465) # 构造smtp服务器连接
server.set_debuglevel(1) # debug输出模式 默认关闭
server.login(self.outbox, self.password) # 登录smtp服务器
server.sendmail(self.outbox, [self.inbox], msg.as_string()) # 发送邮件
server.quit()
print("邮件发送成功")
except:
print("邮件发送失败")
if __name__ == '__main__':
SendEmail.send_email_text()
实例:发送HTML邮件:
自动化结果报告中用HTML会很直观,那么要发送这种HTML邮件,主要是构造MIMEText对象时,把HTML字符串传进去,把第二个参数由plain变为html就可以了:
import smtplib
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr
class SendEmail():
outbox = "[email protected] "
password = "tlnajaxviwhjbcgi"
inbox = "[email protected]"
smtp_server = "smtp.qq.com"
def _format_address(self, text):
name, address = parseaddr(text)
return formataddr((Header(name, "utf-8").encode(), address))
def send_email_html(self):
msg = MIMEText( """
测试结果分析more>>
1)HTTP状态
500:105 404:3264 503:214
2)用户浏览器统计
IE:50% firefox:10% chrome:30% 其他浏览器:10%
3)错误页面链接
/index.php 42153
/login.php 5112
""","html","utf-8") # 第一个参数:邮件正文 第二个参数:邮件类型 纯文本 第三个参数:编码
msg["From"] = self._format_address("来自测试人员一封邮件 <%s>" % self.outbox) # 发件人姓名与发件箱地址
msg["To"] = self._format_address("管理员 <%s>" % cls.inbox) # 收件人姓名与收件箱地址
msg["Subject"] = Header("test", "utf-8").encode() # 邮件标题
try:
server = smtplib.SMTP_SSL(cls.smtp_server,465) # 构造smtp服务器连接
server.set_debuglevel(1) # debug输出模式 默认关闭
server.login(cls.outbox, cls.password) # 登录smtp服务器
server.sendmail(cls.outbox, [cls.inbox], msg.as_string()) # 发送邮件
server.quit()
print("邮件发送成功")
except:
print("邮件发送失败")
技术解释:MIMEText第一个参数的邮件正文,用了HTML语句生成一个HTML页面,由于邮件正文长度超出了行长,所以用到了三引号。HTML报告代码是这部分代码是写死的,可以写一个生成也HTML邮件正文内容的代码,这样每次执行的信息可以作为参数传入,测试报告的内容是实时的。
if __name__ == '__main__':
SendEmail.send_email_html ()
技术解释:SenEmail类实现了send_email_html()方法,上个列子实现了发送文本内容的邮件,可以把两种方法结合在一起,进一步完善Sendemail类。
实例:发送正文带图片的邮件
在测试过程中我们需要提交给开发人员更加详细的信息,有利于其快速定位bug,修改程序。那么在邮件添加bug的截图是有必要的。从技术实现上带附件的邮件可以看做包含若干部分的邮件:文本和各个附件本身,所以,构造一个MIMEMultipart对象代表邮件本身,然后加上一个MIMEText作为邮件正文,再加上表示附件的MIMEBase对象即可,本例中我们在邮件正文添加一张图片。
import smtplib
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.utils import parseaddr, formataddr
from email.mime.image import MIMEImage
技术解释:email是构造邮件的类,Message类是email的核心类,它是email对象模型中基类,提供了设置和查询邮件头部,访问消息体的核心方法。从概念上讲,Message对象构成了邮件头部(Headers)和消息体(payloads)。头部格式在RFC 2822中进行了定义,每个头部由该项名字和值组成,并由冒号分割。消息体可以是简单消息对象的字符串或多个MIME容器的Message对象组成的多部分邮件。MIME中定义了Content-Type类型有7种:text(文本,例如一个文档),image(图像),audio(音频),video(视频),application(应用程序,程序的原始数据),multipart(多邮件部分,每个有单独的内容类型和编码方式),message(邮件,一个完整的电子邮件或对邮件的外部应用,如一个FTP服务器和文件名)。
MIMEMultipart类用于实现多部分邮件的功能,缺省情况下它会创建Content-Type类型为mulitpart/mixed邮件。
MIMEText实现了text类型。MIMEImage类实现了image类型。email.utils.parseaddr专门用来解析地址。它们类的继承关系:
Message
+- MIMEBase
+- MIMEMultipart
+- MIMENonMultipart
+- MIMEMessage
+- MIMEText
+- MIMEImage
MIMEBase可以表示任何对象,它们这种嵌套关系就可以构造出任意复杂的邮件。你可以通过email.mime文档查看它们所在的包以及详细的用法。
class SendEmail():
outbox = "[email protected] "
password = "tlnajaxviwhjbcgi"
inbox = "[email protected]"
smtp_server = "smtp.qq.com"
def _format_address(self, text):
name, address = parseaddr(text)
return formataddr((Header(name, "utf-8").encode(), address))
def send_email_html_img(self):
msg = MIMEMultipart()
msg_text = MIMEText( """
测试结果分析more>>
1)HTTP状态
500:105 404:3264 503:214
2)用户浏览器统计
IE:50% firefox:10% chrome:30% 其他浏览器:10%
3)错误页面链接
/index.php 42153
/login.php 5112
""","html","utf-8") # 第一个参数:邮件正文 第二个参数:邮件类型 纯文本 第三个参数:编码
msg.attach(msg_text) # 定义图片 ID,在 HTML 文本中引用
with open(r'C:SeleniumBooktimg.jpg', 'rb') as f: # 添加附件就是加上一个MIMEBase,从本地读取一个图片:
# 设置附件的MIME和文件名,这里是png类型:
mime = MIMEBase('image', 'jpg', filename='timg.jpg')
# 加上必要的头信息:
mime.add_header('Content-Disposition', 'attachment', filename='timg.jpg')
mime.add_header('Content-ID', '')
mime.add_header('X-Attachment-Id', '0')
mime.set_payload(f.read()) # 把附件的内容读进来:
encoders.encode_base64(mime) # 用Base64编码:
msg.attach(mime) # 添加到MIMEMultipart:
msg["From"] = self._format_address("来自测试人员一封邮件 <%s>" % self.outbox) # 发件人姓名与发件箱地址
msg["To"] = self._format_address("管理员 <%s>" % self.inbox) # 收件人姓名与收件箱地址
msg["Subject"] = Header("test", "utf-8").encode() # 邮件标题
try:
server = smtplib.SMTP_SSL(self.smtp_server,465) # 构造smtp服务器连接
server.set_debuglevel(1) # debug输出模式 默认关闭
server.login(self.outbox, self.password) # 登录smtp服务器
server.sendmail(self.outbox, [self.inbox], msg.as_string()) # 发送邮件
server.quit()
print("邮件发送成功")
except:
print("邮件发送失败")
技术解释:发送带图片的邮件是利用 email.mime.multipart 的 MIMEMultipart 以及 email.mime.image 的 MIMEImage。前面介绍了MIMEImage:定义邮件的图片数据,MIMEText:定义邮件的文字数据,MIMEMultipart:负责将文字图片音频组装在一起和添加附件。邮件正文是图片的格式,用OPEN打开文件读入图片,传给mime对象,然后构造协议头添加Content-ID,用X-Attachment-Id和后面HTML代码中的图片关联起来,如果有多个图片,给它们依次编号,然后引用不同的cid:x即可。最后添加到MIMEMultipart()对象中,它把图片和html整合在一起。
if __name__ == '__main__':
SendEmail. send_email_html_img ()
添加邮件发送功能
创建sendmail.py文件放置在框架目录Common文件夹下,sendmail.py代码如下:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
class mailsender(object):
_from = None
_attachments = []
def __init__(self, smtpsvr, port):
print("connecting....")
self.smtp = smtplib.SMTP_SSL(smtpsvr, port)
self.smtp.set_debuglevel(1)
print("connected!")
def login(self, user, pwd):
self._from = user
print("login ...")
self.smtp.login(user, pwd)
def add_attachment(self, filename):
att = MIMEBase('application', 'octet-stream')
att.set_payload(open(filename,'rb').read())
att.add_header('Content-Disposition', 'attachment', filename = ('gbk','',filename))
encoders.encode_base64(att)
self._attachments.append(att)
def send(self, subject, content, to_addr):
msg = MIMEMultipart('alternative')
contents = MIMEText(content, 'html', _charset ='utf-8')
msg['subject'] = subject
msg['from'] = self._from
msg['to'] = to_addr
for att in self._attachments:
msg.attach(att)
msg.attach(contents)
try:
self.smtp.sendmail(self._from, to_addr, msg.as_string())
return True
except Exception as e:
print(str(e))
return False
def close(self):
self.smtp.quit
print("logout!")
技术解释:mailsender继承自Object,拥有5个方法:
修改run.py代码
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(contactsTests)
fp = open(report_path,"wb")
runner = HTMLTestRunner.HTMLTestRunner(stream=fp,title="联系人测试结果",description="Anadroid联系人测试结果")
runner.run(suite)
fp.close()
mail = mailsender(smtp_server,465)
mail.login('[email protected]',‘???’)
mail.send('测试结果已出','请查看测试报告',to_addr)
mail.close()
在上例中发送邮件功能,用户名密码都是直接写入到脚本中,这样不利于维护,还有就是每次测试完成后可能由于错误不同,发送邮件的对象不同,所以在框架中增加配置文件功能来解决这个问题。设计配置文件放入框架目录config文件夹下,格式如下:
[email]
from_addr = '[email protected]'
password = 'pplwzqiphxrjbfdg'
to_addr = '[email protected]'
smtp_server = 'smtp.qq.com'
smtp_port=465
创建readConfig.py文件放置在框架目录Common文件夹下,readConfig.py代码如下:添加readConfig类:
import configparser
import os
class ReadConfig():
filepath=os.getcwd()+'configconfig.ini'
def get_section_itme(self,section,item):
cf = configparser.ConfigParser()
cf.read(self.filepath)
return cf.get(section, item)
技术解释:ReadConfig类,拥有1个方法:get_section_itme(self,section,item):获得配置文件配置项的值,参数section表示配置项的部分,参数item具体配置项。Filepath保存配置文件路径,它通过os.getcwd()得到项目文件夹的路径与'configconfig.ini'组合,获得配置文件所在路径。
修改驱动run.py代码:
import unittest
import HTMLTestRunner
from testcase.test_contacts import contactsTests
from Common.readConfig import ReadConfig
report_path = ".reportappium_report.html"
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(contactsTests)
fp = open(report_path,"wb")
runner = HTMLTestRunner.HTMLTestRunner(stream=fp,title="联系人测试结果",description="Anadroid联系人测试结果")
runner.run(suite)
fp.close()
rc=ReadConfig()
from_addr=rc.get_section_itme('email','from_addr')
password = rc.get_section_itme('email', 'password')
to_addr = rc.get_section_itme('email', 'to_addr')
smtp_server = rc.get_section_itme('email', 'smtp_server')
smtp_port = int(rc.get_section_itme('email', 'smtp_port'))
mail = mailsender(smtp_server,smtp_port)
mail.login(from_addr,password)
mail.send('测试结果已出','请查看测试报告',to_addr)
mail.close()
技术解释:from Common.readConfig import ReadConfig作用是导入Common文件夹readConfig.py文件中ReadConfig类。rc=ReadConfig()创建ReadConfig()类实例,调用rc.get_section_itme方法获得邮箱登陆用户名,密码,以及收件人。