自动发邮件功能也是自动化策划四项目的重要需求之一。例如,我们想在自动化脚本运行完成之后,邮箱就可以收到最新的测试报告结果。假设生成的测试报告与多人相关,每个人都去测试服务器查看就会比较麻烦,如果把这种自动的且不及时的查看变成被动且及时的查收,就方便多了。
SMTP(Simple Mail Transfer Protocol)是简单邮件传输协议,它是一组用于由于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。
Python的smtplib模块提供了一种很方便的途径用来发送电子邮件。它对SMTP协议进行了简单的封装。我们可以使用SMTP对象的sendmail方法发送邮件,通过help()查看SMTP所提供的方法如下。
输出结果:
Help on class SMTP in module smtplib:
class SMTP(builtins.object) | This class manages a connection to an SMTP or ESMTP server. | SMTP Objects: | SMTP objects have the following attributes: | helo_resp | This is the message given by the server in response to the | most recent HELO command. | | ehlo_resp | This is the message given by the server in response to the | most recent EHLO command. This is usually multiline. | | does_esmtp | This is a True value _after you do an EHLO command_, if the | server supports ESMTP. | | esmtp_features | This is a dictionary, which, if the server supports ESMTP, | will _after you do an EHLO command_, contain the names of the | SMTP service extensions this server supports, and their | parameters (if any). | | Note, all extension names are mapped to lower case in the | dictionary. | | See each method's docstrings for details. In general, there is a | method of the same name to perform each SMTP command. There is also a | method called 'sendmail' that will do an entire mail transaction. | | Methods defined here: | | __enter__(self) | | __exit__(self, *args) | | __init__(self, host='', port=0, local_hostname=None, timeout=, source_address=None) | Initialize a new instance. | | If specified, `host' is the name of the remote host to which to | connect. If specified, `port' specifies the port to which to connect. | By default, smtplib.SMTP_PORT is used. If a host is specified the | connect method is called, and if it returns anything other than a | success code an SMTPConnectError is raised. If specified, | `local_hostname` is used as the FQDN of the local host in the HELO/EHLO | command. Otherwise, the local hostname is found using | socket.getfqdn(). The `source_address` parameter takes a 2-tuple (host, | port) for the socket to bind to as its source address before | connecting. If the host is '' and port is 0, the OS default behavior | will be used. | | auth(self, mechanism, authobject, *, initial_response_ok=True) | Authentication command - requires response processing. | | 'mechanism' specifies which authentication mechanism is to | be used - the valid values are those listed in the 'auth' | element of 'esmtp_features'. | | 'authobject' must be a callable object taking a single argument: | | data = authobject(challenge) | | It will be called to process the server's challenge response; the | challenge argument it is passed will be a bytes. It should return | bytes data that will be base64 encoded and sent to the server. | | Keyword arguments: | - initial_response_ok: Allow sending the RFC 4954 initial-response | to the AUTH command, if the authentication methods supports it. | | auth_cram_md5(self, challenge=None) | Authobject to use with CRAM-MD5 authentication. Requires self.user | and self.password to be set. | | auth_login(self, challenge=None) | Authobject to use with LOGIN authentication. Requires self.user and | self.password to be set. | | auth_plain(self, challenge=None) | Authobject to use with PLAIN authentication. Requires self.user and | self.password to be set. | | close(self) | Close the connection to the SMTP server. | | connect(self, host='localhost', port=0, source_address=None) | Connect to a host on a given port. | | If the hostname ends with a colon (`:') followed by a number, and | there is no port specified, that suffix will be stripped off and the | number interpreted as the port number to use. | | Note: This method is automatically invoked by __init__, if a host is | specified during instantiation. | | data(self, msg) | SMTP 'DATA' command -- sends message data to server. | | Automatically quotes lines beginning with a period per rfc821. | Raises SMTPDataError if there is an unexpected reply to the | DATA command; the return value from this method is the final | response code received when the all data is sent. If msg | is a string, lone '\r' and '\n' characters are converted to | '\r\n' characters. If msg is bytes, it is transmitted as is. | | docmd(self, cmd, args='') | Send a command, and return its response code. | | ehlo(self, name='') | SMTP 'ehlo' command. | Hostname to send for this command defaults to the FQDN of the local | host. | | ehlo_or_helo_if_needed(self) | Call self.ehlo() and/or self.helo() if needed. | | If there has been no previous EHLO or HELO command this session, this | method tries ESMTP EHLO first. | | This method may raise the following exceptions: | | SMTPHeloError The server didn't reply properly to | the helo greeting. | | expn(self, address) | SMTP 'expn' command -- expands a mailing list. | | getreply(self) | Get a reply from the server. | | Returns a tuple consisting of: | | - server response code (e.g. '250', or such, if all goes well) | Note: returns -1 if it can't read response code. | | - server response string corresponding to response code (multiline | responses are converted to a single, multiline string). | | Raises SMTPServerDisconnected if end-of-file is reached. | | has_extn(self, opt) | Does the server support a given SMTP service extension? | | helo(self, name='') | SMTP 'helo' command. | Hostname to send for this command defaults to the FQDN of the local | host. | | help(self, args='') | SMTP 'help' command. | Returns help text from server. | | login(self, user, password, *, initial_response_ok=True) | Log in on an SMTP server that requires authentication. | | The arguments are: | - user: The user name to authenticate with. | - password: The password for the authentication. | | Keyword arguments: | - initial_response_ok: Allow sending the RFC 4954 initial-response | to the AUTH command, if the authentication methods supports it. | | If there has been no previous EHLO or HELO command this session, this | method tries ESMTP EHLO first. | | This method will return normally if the authentication was successful. | | This method may raise the following exceptions: | | SMTPHeloError The server didn't reply properly to | the helo greeting. | SMTPAuthenticationError The server didn't accept the username/ | password combination. | SMTPNotSupportedError The AUTH command is not supported by the | server. | SMTPException No suitable authentication method was | found. | | mail(self, sender, options=[]) | SMTP 'mail' command -- begins mail xfer session. | | This method may raise the following exceptions: | | SMTPNotSupportedError The options parameter includes 'SMTPUTF8' | but the SMTPUTF8 extension is not supported by | the server. | | noop(self) | SMTP 'noop' command -- doesn't do anything :> | | putcmd(self, cmd, args='') | Send a command to the server. | | quit(self) | Terminate the SMTP session. | | rcpt(self, recip, options=[]) | SMTP 'rcpt' command -- indicates 1 recipient for this mail. | | rset(self) | SMTP 'rset' command -- resets session. | | send(self, s) | Send `s' to the server. | | send_message(self, msg, from_addr=None, to_addrs=None, mail_options=[], rcpt_options={}) | Converts message to a bytestring and passes it to sendmail. | | The arguments are as for sendmail, except that msg is an | email.message.Message object. If from_addr is None or to_addrs is | None, these arguments are taken from the headers of the Message as | described in RFC 2822 (a ValueError is raised if there is more than | one set of 'Resent-' headers). Regardless of the values of from_addr and | to_addr, any Bcc field (or Resent-Bcc field, when the Message is a | resent) of the Message object won't be transmitted. The Message | object is then serialized using email.generator.BytesGenerator and | sendmail is called to transmit the message. If the sender or any of | the recipient addresses contain non-ASCII and the server advertises the | SMTPUTF8 capability, the policy is cloned with utf8 set to True for the | serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send. | If the server does not support SMTPUTF8, an SMPTNotSupported error is | raised. Otherwise the generator is called without modifying the | policy. | | sendmail(self, from_addr, to_addrs, msg, mail_options=[], rcpt_options=[]) | This command performs an entire mail transaction. | | The arguments are: | - from_addr : The address sending this mail. | - to_addrs : A list of addresses to send this mail to. A bare | string will be treated as a list with 1 address. | - msg : The message to send. | - mail_options : List of ESMTP options (such as 8bitmime) for the | mail command. | - rcpt_options : List of ESMTP options (such as DSN commands) for | all the rcpt commands. | | msg may be a string containing characters in the ASCII range, or a byte | string. A string is encoded to bytes using the ascii codec, and lone | \r and \n characters are converted to \r\n characters. | | If there has been no previous EHLO or HELO command this session, this | method tries ESMTP EHLO first. If the server does ESMTP, message size | and each of the specified options will be passed to it. If EHLO | fails, HELO will be tried and ESMTP options suppressed. | | This method will return normally if the mail is accepted for at least | one recipient. It returns a dictionary, with one entry for each | recipient that was refused. Each entry contains a tuple of the SMTP | error code and the accompanying error message sent by the server. | | This method may raise the following exceptions: | | SMTPHeloError The server didn't reply properly to | the helo greeting. | SMTPRecipientsRefused The server rejected ALL recipients | (no mail was sent). | SMTPSenderRefused The server didn't accept the from_addr. | SMTPDataError The server replied with an unexpected | error code (other than a refusal of | a recipient). | SMTPNotSupportedError The mail_options parameter includes 'SMTPUTF8' | but the SMTPUTF8 extension is not supported by | the server. | | Note: the connection will be open even after an exception is raised. | | Example: | | >>> import smtplib | >>> s=smtplib.SMTP("localhost") | >>> tolist=["[email protected] ","[email protected] ","[email protected] ","[email protected] "] | >>> msg = '''\ | ... From: [email protected] | ... Subject: testin'... | ... | ... This is a test ''' | >>> s.sendmail("[email protected] ",tolist,msg) | { "[email protected] " : ( 550 ,"User unknown" ) } | >>> s.quit() | | In the above example, the message was accepted for delivery to three | of the four addresses, and one was rejected, with the error code | 550. If all addresses are accepted, then the method will return an | empty dictionary. | | set_debuglevel(self, debuglevel) | Set the debug output level. | | A non-false value results in debug messages for connection and for all | messages sent to and received from the server. | | starttls(self, keyfile=None, certfile=None, context=None) | Puts the connection to the SMTP server into TLS mode. | | If there has been no previous EHLO or HELO command this session, this | method tries ESMTP EHLO first. | | If the server supports TLS, this will encrypt the rest of the SMTP | session. If you provide the keyfile and certfile parameters, | the identity of the SMTP server and client can be checked. This, | however, depends on whether the socket module really checks the | certificates. | | This method may raise the following exceptions: | | SMTPHeloError The server didn't reply properly to | the helo greeting. | | verify(self, address) | SMTP 'verify' command -- checks for address validity. | | vrfy = verify(self, address) | | ---------------------------------------------------------------------- | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | debuglevel = 0 | | default_port = 25 | | does_esmtp = 0 | | ehlo_msg = 'ehlo' | | ehlo_resp = None | | file = None | | helo_resp = None
None
Process finished with exit code 0 ==========================================================================================
导入SMTP对象,通过help()查看对象的注释,从中找到sendmail()方法的使用说明。
connect(host,port)方法参数说明如下。
host:指定连接的邮箱服务器。
port:指定连接服务器的端口号。
login(user,password)方法参数说明如下。
user:登录邮箱用户用。
password:登录邮箱密码。
sendmail(from_addr,to_addrs,msg,..)方法参数说明如下。
from_addr:邮件发送者地址。
to_addrs:字符串列表,邮件发送地址。
msg:发送消息。
quit()方法:用于结束SMTP会话。
一般我们发邮件时有两种方式。方式一:自己邮箱的Web页面(如:mail.126.com),输入自己邮箱的用户名和密码登录,打开发邮件页面,填写对方的邮箱地址及邮件标题与正文,完成后单击发送。方式二:下载安装邮箱客户端(如Outlook、Foxmail等),填写邮箱帐号、密码及邮箱服务器(如:smtp.126.com),一般的邮箱客户端会默认记下这些信息,所以,这个过程只需填写一次,后面发邮件的过程与方法一相同。
而我们通过Python的SMTP对象发邮件则更像方式二,因为需要填写邮箱服务器。
当然,在具体发邮件时会涉及诸多需求,例如,邮件的正文的格式、是否带图片、邮件是否需要添加附件(及多附近)、邮件是否需要同时向多人发送等。
一、发送HTML格式的邮件
send_mail.py
import smtplib
from email.mime.text import MIMEText
from email.header import Header
#发送邮箱服务器
smtpserver='smtp.163.com'
#发送邮箱用户/密码
user='****@163.com'
password='*********'
#发送邮箱
sender='*****@163.com'
#接收邮箱
receiver='*****@163.com'
#发送邮件主题
subject='Python email test'
#编写HTML类型的邮件正文
msg=MIMEText('你好! ','html','utf-8')
msg['Subject']=Header(subject,'utf-8')
#连接发送邮件
smtp=smtplib.SMTP()
smtp.connect(smtpserver)
smtp.login(user,password)
smtp.sendmail(sender,receiver,msg.as_string())
smtp.quit()
本例中,除SMTP模块外,我们还用到了email模块,它主要用来定义邮件的标题和正文;Header()方法用来定义邮件标题;MIMEText()用于定义邮件正文,参数为html格式的文本。登录[email protected] 邮箱,查看邮箱内容如图所示。
二、发送带附件的邮件
在发送文件时,有时需要发送附件。下面的实例实现了带附件的邮件发送。
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
#发送邮箱服务器
smtpserver='smtp.163.com'
#发送邮箱
sender='******@163.com'
#接收邮箱
receiver='*********@163.com'
#发送邮箱用户/密码
user='********@163.com'
password='*******'
#发送邮件主题
subject='Python send email test'
#发送的附件
sendfile=open('D:\\下载包\\build.xml','rb').read()
att=MIMEText(sendfile,'base64','utf-8')
att["Content-Type"]='application/octet-stream'
att["Content-Disposition"]='attachment;filename="build.xml"'
msgRoot=MIMEMultipart('related')
msgRoot['Subject']=subject
msgRoot.attach(att)
#连接发送邮件
smtp=smtplib.SMTP()
smtp.connect(smtpserver)
smtp.login(user,password)
smtp.sendmail(sender,receiver,msgRoot.as_string())
smtp.quit()
相比上一个实例,通过MIMEMultipart()模块构造的带附件的邮件如下图。
三、查找最新的测试报告
现在已经知道如何通过Python编写发邮件程序,但要想和自动化测试项目结合还需要解决一个问题,因为测试报告的名称是根据当前时间生成的,所以如何找到最新生成的测试报告是实现发邮件功能的关键。
import os
#定义文件目录
result_dir='C:\\Users\\Administrator\\PycharmProjects\\test2\\HTML测试报告'
lists=os.listdir(result_dir)
#重新按时间对应目录下的文件进行排序
lists.sort(key=lambda fn:os.path.getmtime(result_dir+"\\"+fn))
print(("最新的文件为:"+lists[-1]))
file=os.path.join(result_dir,lists[-1])
print(file)
首先定义测试报告的目录result_dir,os.listdir()可以获取目录下的所有文件及文件夹。利用sort()方法对目录下的文件夹按时间重新排序。list[-1]取到的就是最新生成的文件或文件夹。程序运行结果如下。
四、整合自动发邮件功能
解决了前面的问题后,现在就可以将自动发邮件功能集成到自动化测试项目中了。下面打开runtest.py文件重新进行编辑。
from HTMLTestRunner import HTMLTestRunner
from email.mime.text import MIMEText
from email.header import Header
import smtplib
import unittest
import time
import os
#===================定义发送邮件==================
def send_mail(file_new):
f=open(file_new,'rb')
mail_body=f.read()
f.close()
msg=MIMEText(mail_body,'html','utf-8')
msg['Subject']=Header("自动化测试报告","utf-8")
smtp=smtplib.SMTP()
smtp.connect("smtp.163.com")
smtp.login("******@163.com","*******") #密码为授权码
smtp.sendmail("******@163.com","******@163.com",msg.as_string())
smtp.quit()
print('email has send out!')
#===============查找测试报告目录,找到最新生成的测试报告文件============================
def new_report(testreport):
lists=os.listdir(testreport)
lists.sort(key=lambda fn:os.path.getmtime(testreport+"\\"+fn))
file_new = os.path.join(testreport, lists[-1])
print(file_new)
return file_new
if __name__=='__main__':
test_dir='C:\\Users\\Administrator\\PycharmProjects\\test2\\HTML测试报告\\test_case'
test_report='C:\\Users\\Administrator\\PycharmProjects\\test2\\HTML测试报告'
discover=unittest.defaultTestLoader.discover(test_dir,pattern='test_*.py')
now=time.strftime("%Y-%m-%d %H_%M_%S")
filename=test_report+"\\"+now+'result.html'
fp=open(filename,'wb')
runner = HTMLTestRunner(stream=fp,
title='百度搜索测试报告',
description='用例执行情况:')
runner.run(discover) # 运行测试用例
fp.close() # 关闭报告文件
new_report=new_report(test_report)
send_mail(new_report) #发送测试报告
整个程序的执行过程可以分为三个步骤:
①通过unittest框架discover()找到匹配测试用例,由HTMLTestRunner的run()方法执行测试用例并生成最新的测试报告。
②调用new_report()函数找到测试报告目录(report)下最新生成的测试报告,返回测试报告的路径。
③将得到的最新测试报告的完整路径传给send_mail()函数,实现发邮件功能。
整个脚本执行完成后,打开接收邮箱,即可看到最新测试执行的测试报告。