自動發郵件功能也是自動化策劃四項目的重要需求之一。例如,我們想在自動化腳本運作完成之後,郵箱就可以收到最新的測試報告結果。假設生成的測試報告與多人相關,每個人都去測試伺服器檢視就會比較麻煩,如果把這種自動的且不及時的檢視變成被動且及時的查收,就友善多了。
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=<object object at 0x000000000037F0C0>, 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><h1>你好!</h1></html>','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()函數,實作發郵件功能。
整個腳本執行完成後,打開接收郵箱,即可看到最新測試執行的測試報告。