标签归档:testing

用python练习BDD [关闭]

问题:用python练习BDD [关闭]

python有哪些最先进的框架和工具可用于实践行为驱动开发?尤其是找到与rspec和mocha类似的工具来进行红宝石搜索将是很棒的。

Which are the most advanced frameworks and tools there are available for python for practicing Behavior Driven Development? Especially finding similar tools as rspec and mocha for ruby would be great.


回答 0

Ian Bicking建议将doctest用于行为驱动的设计:

我个人倾向于以行为驱动的设计风格使用鼻子空隙模拟。具体来说,鼻子的规范插件非常适合BDD。

Ian Bicking recommends using doctest for behavior driven design:

I personally tend to use nose and voidspace mock in a behavior driven design style. Specifically, the spec plugin for nose is excellent for BDD.


回答 1

生菜意味着要成为类似python的黄瓜类工具:http//lettuce.it/

您可以在github.com/gabrielfalcao/lettuce上获取源代码

Lettuce means to be a cucumber-like tool for python: http://lettuce.it/

You can grab the source at github.com/gabrielfalcao/lettuce


回答 2

我真的建议您表现良好

在寻找适用于Python的黄瓜克隆时,我开始使用生菜,但发现它是一个笨拙设计的副本。非常不合常规。

然后我发现了行为,并对此感到非常满意。

I really recommend behave.

Looking for a Cucumber clone for Python, I started using lettuce, but found it a pretty clumsily designed replica. Very Unpythonic.

Then I discovered behave, and have been really happy with it.


回答 3

我建议您使用开发的一组工具来帮助程序员进行BDD和TDD的实践。该工具集由pycukesspecloudludibrioshould-dsl组成

Should-DSL将给您类似RSpec的期望。您可以使用RSpec期望API进行的所有操作,should-dsl也可以。您可以从Github获取最新版本

SpecLoud帮助您进行类似BDD的单元测试。您可以通过执行安装

pip install specloud

Ludibrio是一个用于测试双打(假人,存根和假人)的库。通过安装

pip install ludibrio

PyCukes是BDD的主要工具。它将运行场景,等等。再次,

pip install pycukes

有关更多信息,请阅读PyPi上的工具文档。

I recommend you to use a set of tools developed to help programmers in the practice of BDD and TDD. This tool set is composed by: pycukes, specloud, ludibrio and should-dsl.

Should-DSL will give you RSpec-like expectations. Everything you can do with RSpec expectation API, should-dsl does too. You can grab the latestversion from Github.

SpecLoud helps you on running BDD-like unittests. You can install it by doing

pip install specloud

Ludibrio is a library for test doubles (Mocks, Stubs and Dummies). Install it via

pip install ludibrio

And PyCukes is the main tool for BDD. It will run the Scenarios, etc. Again,

pip install pycukes

For more info please read the tools documentation at PyPi.


回答 4

很棒的帖子和答案。只是想更新包括梳洗在此列表中,因为我读pycukes被中断。有关使用BDD和Django的与梳洗好后是在这里

Great post and answers. Just wanted to update to include Freshen in this list as I read pycukes is discontinued. A good post about using BDD and Django with Freshen is here.


回答 5

您可以将“ sure”用于表达性断言(就像在RSpec中一样)

You can use “sure” for expressive assertions (just like in RSpec)


回答 6

Pyccuracy项目致力于为Python中的BDD提供特定领域的语言。

与在API级别上工作的doctest不同,它对更高级别的操作进行编码,例如加载网页和提交表单。我没有使用过它,但是如果您正在寻找它,它看起来很有希望。

The Pyccuracy project is an effort to provide a domain-specific language for BDD in Python.

Unlike doctest, which works at the API level, it encodes higher-level operations such as loading a web page and submitting a form. I haven’t used it but it looks somewhat promising if that is what you’re looking for.


回答 7

我非常喜欢Pyccuracy。这些天,我正在一个中型项目中实现它。

I like Pyccuracy a lot. I’m implementing it on a mid sized project these days.


回答 8

试用pyspecs。使测试易于阅读并在开发过程中持续运行是我创建此项目的两个主要目标。

测试代码:

from pyspecs import given, when, then, and_, the, this

with given.two_operands:
    a = 2
    b = 3

    with when.supplied_to_the_add_function:
        total = a + b

        with then.the_total_should_be_mathmatically_correct:
            the(total).should.equal(5)

        with and_.the_total_should_be_greater_than_either_operand:
            the(total).should.be_greater_than(a)
            the(total).should.be_greater_than(b)

    with when.supplied_to_the_subtract_function:
        difference = b - a

        with then.the_difference_should_be_mathmatically_correct:
            the(difference).should.equal(1)

控制台输出:

# run_pyspecs.py

  |  given two operands 
  |    when supplied to the add function 
  |      then the total should be mathmatically correct 
  |      and the total should be greater than either operand 
  |    when supplied to the subtract function 
  |      then the difference should be mathmatically correct 

(ok) 6 passed (6 steps, 1 scenarios in 0.0002 seconds)

Try out pyspecs. Making tests easy to read and constantly running during development were two of my main goals in creating this project.

Test Code:

from pyspecs import given, when, then, and_, the, this

with given.two_operands:
    a = 2
    b = 3

    with when.supplied_to_the_add_function:
        total = a + b

        with then.the_total_should_be_mathmatically_correct:
            the(total).should.equal(5)

        with and_.the_total_should_be_greater_than_either_operand:
            the(total).should.be_greater_than(a)
            the(total).should.be_greater_than(b)

    with when.supplied_to_the_subtract_function:
        difference = b - a

        with then.the_difference_should_be_mathmatically_correct:
            the(difference).should.equal(1)

Console Output:

# run_pyspecs.py

  | • given two operands 
  |   • when supplied to the add function 
  |     • then the total should be mathmatically correct 
  |     • and the total should be greater than either operand 
  |   • when supplied to the subtract function 
  |     • then the difference should be mathmatically correct 

(ok) 6 passed (6 steps, 1 scenarios in 0.0002 seconds)

回答 9

我可能完全忘记了这一点,但是我保留的原始BDD论文是BDD只是重新包装了TDD,以强调一些最佳实践。

如果我的解释是正确的,则只需在任何xUnit实现中重命名方法即可获得BDD框架。因此,只需继续使用标准库的unittest即可

编辑:一个快速的谷歌在奶酪店里出现了一个行为模块。进一步搜索 BDD那里没有找到其他任何东西。

I am probably completely missing the point, but what I retained of the original BDD paper was that BDD was just TDD repackaged to emphasize some best practices.

If my interpretation is correct, you can get a BDD framework just by renaming methods around in any xUnit implementation. So just go ahead and use the standard library’s unittest.

EDIT: A quick google turned up a Behaviour module in the Cheese Shop. Further searching for BDD there did not find anything else.


Python:如何使用TO,CC和BCC发送邮件?

问题:Python:如何使用TO,CC和BCC发送邮件?

为了进行测试,我需要在数百个电子邮件框中填充各种消息,并打算为此使用smtplib。但是除其他外,我不仅需要能够向特定邮箱发送消息,还需要向它们发送CC和BCC消息。看起来smtplib在发送电子邮件时不支持CC-ing和BCC-ing。

寻找有关如何执行CC或BCC从python脚本发送消息的建议。

(而且-不,我不是在创建脚本向测试环境之外的任何人发送垃圾邮件。)

I need for testing purposes to populate few hundred email boxes with various messages, and was going to use smtplib for that. But among other things I need to be able to send messages not only TO specific mailboxes, but CC and BCC them as well. It does not look like smtplib supports CC-ing and BCC-ing while sending emails.

Looking for suggestions how to do CC or BCC sending messages from the python script.

(And — no, I’m not creating a script to spam anyone outside of my testing environment.)


回答 0

电子邮件标头与smtp服务器无关。发送电子邮件时,只需将抄送和密件抄送收件人添加到toaddrs。对于CC,将它们添加到CC标头中。

toaddr = 'buffy@sunnydale.k12.ca.us'
cc = ['alexander@sunydale.k12.ca.us','willow@sunnydale.k12.ca.us']
bcc = ['chairman@slayerscouncil.uk']
fromaddr = 'giles@sunnydale.k12.ca.us'
message_subject = "disturbance in sector 7"
message_text = "Three are dead in an attack in the sewers below sector 7."
message = "From: %s\r\n" % fromaddr
        + "To: %s\r\n" % toaddr
        + "CC: %s\r\n" % ",".join(cc)
        + "Subject: %s\r\n" % message_subject
        + "\r\n" 
        + message_text
toaddrs = [toaddr] + cc + bcc
server = smtplib.SMTP('smtp.sunnydale.k12.ca.us')
server.set_debuglevel(1)
server.sendmail(fromaddr, toaddrs, message)
server.quit()

Email headers don’t matter to the smtp server. Just add the CC and BCC recipients to the toaddrs when you send your email. For CC, add them to the CC header.

toaddr = 'buffy@sunnydale.k12.ca.us'
cc = ['alexander@sunydale.k12.ca.us','willow@sunnydale.k12.ca.us']
bcc = ['chairman@slayerscouncil.uk']
fromaddr = 'giles@sunnydale.k12.ca.us'
message_subject = "disturbance in sector 7"
message_text = "Three are dead in an attack in the sewers below sector 7."
message = "From: %s\r\n" % fromaddr
        + "To: %s\r\n" % toaddr
        + "CC: %s\r\n" % ",".join(cc)
        + "Subject: %s\r\n" % message_subject
        + "\r\n" 
        + message_text
toaddrs = [toaddr] + cc + bcc
server = smtplib.SMTP('smtp.sunnydale.k12.ca.us')
server.set_debuglevel(1)
server.sendmail(fromaddr, toaddrs, message)
server.quit()

回答 1

关键是将收件人添加为sendmail呼叫中的电子邮件ID列表

import smtplib
from email.mime.multipart import MIMEMultipart

me = "user63503@gmail.com"
to = "someone@gmail.com"
cc = "anotherperson@gmail.com,someone@yahoo.com"
bcc = "bccperson1@gmail.com,bccperson2@yahoo.com"

rcpt = cc.split(",") + bcc.split(",") + [to]
msg = MIMEMultipart('alternative')
msg['Subject'] = "my subject"
msg['To'] = to
msg['Cc'] = cc
msg.attach(my_msg_body)
server = smtplib.SMTP("localhost") # or your smtp server
server.sendmail(me, rcpt, msg.as_string())
server.quit()

Key thing is to add the recipients as a list of email ids in your sendmail call.

import smtplib
from email.mime.multipart import MIMEMultipart

me = "user63503@gmail.com"
to = "someone@gmail.com"
cc = "anotherperson@gmail.com,someone@yahoo.com"
bcc = "bccperson1@gmail.com,bccperson2@yahoo.com"

rcpt = cc.split(",") + bcc.split(",") + [to]
msg = MIMEMultipart('alternative')
msg['Subject'] = "my subject"
msg['To'] = to
msg['Cc'] = cc
msg.attach(my_msg_body)
server = smtplib.SMTP("localhost") # or your smtp server
server.sendmail(me, rcpt, msg.as_string())
server.quit()

回答 2

不要添加密件抄送头。

看到这个:http : //mail.python.org/pipermail/email-sig/2004-September/000151.html

并且此:“”“注意,sendmail()的第二个参数(收件人)作为一个列表传递。您可以在列表中包括任意数量的地址,以将消息依次传递给它们。信息与消息头是分开的,您甚至可以通过在方法参数中包括消息而不是消息头来将其密送给BCC。http://pymotw.com/2/smtplib中的 ““

toaddr = 'buffy@sunnydale.k12.ca.us'
cc = ['alexander@sunydale.k12.ca.us','willow@sunnydale.k12.ca.us']
bcc = ['chairman@slayerscouncil.uk']
fromaddr = 'giles@sunnydale.k12.ca.us'
message_subject = "disturbance in sector 7"
message_text = "Three are dead in an attack in the sewers below sector 7."
message = "From: %s\r\n" % fromaddr
    + "To: %s\r\n" % toaddr
    + "CC: %s\r\n" % ",".join(cc)
    # don't add this, otherwise "to and cc" receivers will know who are the bcc receivers
    # + "BCC: %s\r\n" % ",".join(bcc)
    + "Subject: %s\r\n" % message_subject
    + "\r\n" 
    + message_text
toaddrs = [toaddr] + cc + bcc
server = smtplib.SMTP('smtp.sunnydale.k12.ca.us')
server.set_debuglevel(1)
server.sendmail(fromaddr, toaddrs, message)
server.quit()

Don’t add the bcc header.

See this: http://mail.python.org/pipermail/email-sig/2004-September/000151.html

And this: “””Notice that the second argument to sendmail(), the recipients, is passed as a list. You can include any number of addresses in the list to have the message delivered to each of them in turn. Since the envelope information is separate from the message headers, you can even BCC someone by including them in the method argument but not in the message header.””” from http://pymotw.com/2/smtplib

toaddr = 'buffy@sunnydale.k12.ca.us'
cc = ['alexander@sunydale.k12.ca.us','willow@sunnydale.k12.ca.us']
bcc = ['chairman@slayerscouncil.uk']
fromaddr = 'giles@sunnydale.k12.ca.us'
message_subject = "disturbance in sector 7"
message_text = "Three are dead in an attack in the sewers below sector 7."
message = "From: %s\r\n" % fromaddr
    + "To: %s\r\n" % toaddr
    + "CC: %s\r\n" % ",".join(cc)
    # don't add this, otherwise "to and cc" receivers will know who are the bcc receivers
    # + "BCC: %s\r\n" % ",".join(bcc)
    + "Subject: %s\r\n" % message_subject
    + "\r\n" 
    + message_text
toaddrs = [toaddr] + cc + bcc
server = smtplib.SMTP('smtp.sunnydale.k12.ca.us')
server.set_debuglevel(1)
server.sendmail(fromaddr, toaddrs, message)
server.quit()

回答 3

从2011年11月发布的Python 3.2开始,smtplib具有一个新功能,send_message而不是just sendmail,这使得处理To / CC / BCC更加容易。从Python官方电子邮件示例提取一些修改后,我们得到:

# Import smtplib for the actual sending function
import smtplib

# Import the email modules we'll need
from email.message import EmailMessage

# Open the plain text file whose name is in textfile for reading.
with open(textfile) as fp:
    # Create a text/plain message
    msg = EmailMessage()
    msg.set_content(fp.read())

# me == the sender's email address
# you == the recipient's email address
# them == the cc's email address
# they == the bcc's email address
msg['Subject'] = 'The contents of %s' % textfile
msg['From'] = me
msg['To'] = you
msg['Cc'] = them
msg['Bcc'] = they


# Send the message via our own SMTP server.
s = smtplib.SMTP('localhost')
s.send_message(msg)
s.quit()

使用标题可以很好地工作,因为send_message遵循文档中概述的BCC

send_message不传输可能在味精中显示的任何密件抄送或Resent-Bcc标头


随着sendmail这是共同的CC头添加到消息,做一些如:

msg['Bcc'] = blind.email@adrress.com

要么

msg = "From: from.email@address.com" +
      "To: to.email@adress.com" +
      "BCC: hidden.email@address.com" +
      "Subject: You've got mail!" +
      "This is the message body"

问题是,sendmail函数将所有这些标头视为相同,这意味着它们将被(明显地)发送给所有To:和BCC:用户,这违背了BCC的目的。如此处其他许多答案所示,解决方案是在标题中不包含密件抄送,而仅在传递给的电子邮件列表中包含BCC sendmail

需要注意的是,它send_message需要一个Message对象,这意味着您需要从中导入一个类,email.message而不仅仅是将字符串传递到中sendmail

As of Python 3.2, released Nov 2011, the smtplib has a new function send_message instead of just sendmail, which makes dealing with To/CC/BCC easier. Pulling from the Python official email examples, with some slight modifications, we get:

# Import smtplib for the actual sending function
import smtplib

# Import the email modules we'll need
from email.message import EmailMessage

# Open the plain text file whose name is in textfile for reading.
with open(textfile) as fp:
    # Create a text/plain message
    msg = EmailMessage()
    msg.set_content(fp.read())

# me == the sender's email address
# you == the recipient's email address
# them == the cc's email address
# they == the bcc's email address
msg['Subject'] = 'The contents of %s' % textfile
msg['From'] = me
msg['To'] = you
msg['Cc'] = them
msg['Bcc'] = they


# Send the message via our own SMTP server.
s = smtplib.SMTP('localhost')
s.send_message(msg)
s.quit()

Using the headers work fine, because send_message respects BCC as outlined in the documentation:

send_message does not transmit any Bcc or Resent-Bcc headers that may appear in msg


With sendmail it was common to add the CC headers to the message, doing something such as:

msg['Bcc'] = blind.email@adrress.com

Or

msg = "From: from.email@address.com" +
      "To: to.email@adress.com" +
      "BCC: hidden.email@address.com" +
      "Subject: You've got mail!" +
      "This is the message body"

The problem is, the sendmail function treats all those headers the same, meaning they’ll get sent (visibly) to all To: and BCC: users, defeating the purposes of BCC. The solution, as shown in many of the other answers here, was to not include BCC in the headers, and instead only in the list of emails passed to sendmail.

The caveat is that send_message requires a Message object, meaning you’ll need to import a class from email.message instead of merely passing strings into sendmail.


回答 4

TO,CC和BCC之间的区别仅出现在文本标题中。在SMTP级别,每个人都是收件人。

收件人-收件人地址为“收件人:”标题

抄送-有此收件人地址的抄送:标头

密件抄送-根本没有在标题中提到此收件人,但仍然是收件人。

如果你有

TO: abc@company.com
CC: xyz@company.com
BCC: boss@company.com

您有三个收件人。电子邮件正文中的标题将仅包含TO:和CC:

The distinction between TO, CC and BCC occurs only in the text headers. At the SMTP level, everybody is a recipient.

TO – There is a TO: header with this recipient’s address

CC – There is a CC: header with this recipient’s address

BCC – This recipient isn’t mentioned in the headers at all, but is still a recipient.

If you have

TO: abc@company.com
CC: xyz@company.com
BCC: boss@company.com

You have three recipients. The headers in the email body will include only the TO: and CC:


回答 5

您可以尝试MIMEText

msg = MIMEText('text')
msg['to'] = 
msg['cc'] = 

然后发送msg.as_string()

https://docs.python.org/3.6/library/email.examples.html

You can try MIMEText

msg = MIMEText('text')
msg['to'] = 
msg['cc'] = 

then send msg.as_string()

https://docs.python.org/3.6/library/email.examples.html


回答 6

在我创建之前,它对我不起作用:

#created cc string
cc = ""someone@domain.com;
#added cc to header
msg['Cc'] = cc

并且在收件人[列表]中添加了抄送,例如:

s.sendmail(me, [you,cc], msg.as_string())

It did not worked for me until i created:

#created cc string
cc = ""someone@domain.com;
#added cc to header
msg['Cc'] = cc

and than added cc in recipient [list] like:

s.sendmail(me, [you,cc], msg.as_string())

回答 7

由于我在“收件人”和“抄送”中都有多个收件人,因此上述所有内容都不适合我。所以我尝试如下:

recipients = ['abc@gmail.com', 'xyz@gmail.com']
cc_recipients = ['lmn@gmail.com', 'pqr@gmail.com']
MESSAGE['To'] = ", ".join(recipients)
MESSAGE['Cc'] = ", ".join(cc_recipients)

并使用“ cc_recipients”扩展“收件人”并以平凡的方式发送邮件

recipients.extend(cc_recipients)
server.sendmail(FROM,recipients,MESSAGE.as_string())

None of the above things worked for me as I had multiple recipients both in ‘to’ and ‘cc’. So I tried like below:

recipients = ['abc@gmail.com', 'xyz@gmail.com']
cc_recipients = ['lmn@gmail.com', 'pqr@gmail.com']
MESSAGE['To'] = ", ".join(recipients)
MESSAGE['Cc'] = ", ".join(cc_recipients)

and extend the ‘recipients’ with ‘cc_recipients’ and send mail in trivial way

recipients.extend(cc_recipients)
server.sendmail(FROM,recipients,MESSAGE.as_string())

Httpbin-HTTP请求和响应服务,用Python+Flask编写

如何使用Python的timeit计时代码段以测试性能?

问题:如何使用Python的timeit计时代码段以测试性能?

我有一个Python脚本,该脚本可以正常工作,但是我需要编写执行时间。我已经用谷歌搜索了,timeit但是我似乎无法使它正常工作。

我的Python脚本如下所示:

import sys
import getopt
import timeit
import random
import os
import re
import ibm_db
import time
from string import maketrans
myfile = open("results_update.txt", "a")

for r in range(100):
    rannumber = random.randint(0, 100)

    update = "update TABLE set val = %i where MyCount >= '2010' and MyCount < '2012' and number = '250'" % rannumber
    #print rannumber

    conn = ibm_db.pconnect("dsn=myDB","usrname","secretPWD")

for r in range(5):
    print "Run %s\n" % r        
    ibm_db.execute(query_stmt)
 query_stmt = ibm_db.prepare(conn, update)

myfile.close()
ibm_db.close(conn)

我需要的是执行查询并将其写入文件所需的时间results_update.txt。目的是测试具有不同索引和调整机制的数据库更新语句。

I’ve a python script which works just as it should, but I need to write the execution time. I’ve googled that I should use timeit but I can’t seem to get it to work.

My Python script looks like this:

import sys
import getopt
import timeit
import random
import os
import re
import ibm_db
import time
from string import maketrans
myfile = open("results_update.txt", "a")

for r in range(100):
    rannumber = random.randint(0, 100)

    update = "update TABLE set val = %i where MyCount >= '2010' and MyCount < '2012' and number = '250'" % rannumber
    #print rannumber

    conn = ibm_db.pconnect("dsn=myDB","usrname","secretPWD")

for r in range(5):
    print "Run %s\n" % r        
    ibm_db.execute(query_stmt)
 query_stmt = ibm_db.prepare(conn, update)

myfile.close()
ibm_db.close(conn)

What I need is the time it takes to execute the query and write it to the file results_update.txt. The purpose is to test an update statement for my database with different indexes and tuning mechanisms.


回答 0

您可以在要计时的块之前或之后使用time.time()time.clock()

import time

t0 = time.time()
code_block
t1 = time.time()

total = t1-t0

此方法不完全准确timeit(它不会平均运行几次),但很简单。

time.time()(在Windows和Linux中)和time.clock()(在Linux中)不够精确,无法实现快速功能(total = 0)。在这种情况下,或者如果要平均几次运行所花费的时间,则必须多次手动调用该函数(就像我在示例代码中已经做过的那样,并且在设置其number参数时timeit会自动执行)

import time

def myfast():
   code

n = 10000
t0 = time.time()
for i in range(n): myfast()
t1 = time.time()

total_n = t1-t0

如注释中所述,在Windows中,Corey time.clock()具有更高的精度(微秒而不是秒),并且优于time.time()

You can use time.time() or time.clock() before and after the block you want to time.

import time

t0 = time.time()
code_block
t1 = time.time()

total = t1-t0

This method is not as exact as timeit (it does not average several runs) but it is straightforward.

time.time() (in Windows and Linux) and time.clock() (in Linux) are not precise enough for fast functions (you get total = 0). In this case or if you want to average the time elapsed by several runs, you have to manually call the function multiple times (As I think you already do in you example code and timeit does automatically when you set its number argument)

import time

def myfast():
   code

n = 10000
t0 = time.time()
for i in range(n): myfast()
t1 = time.time()

total_n = t1-t0

In Windows, as Corey stated in the comment, time.clock() has much higher precision (microsecond instead of second) and is preferred over time.time().


回答 1

如果您要分析代码并可以使用IPython,则它具有magic函数%timeit

%%timeit 对细胞进行操作。

In [2]: %timeit cos(3.14)
10000000 loops, best of 3: 160 ns per loop

In [3]: %%timeit
   ...: cos(3.14)
   ...: x = 2 + 3
   ...: 
10000000 loops, best of 3: 196 ns per loop

If you are profiling your code and can use IPython, it has the magic function %timeit.

%%timeit operates on cells.

In [2]: %timeit cos(3.14)
10000000 loops, best of 3: 160 ns per loop

In [3]: %%timeit
   ...: cos(3.14)
   ...: x = 2 + 3
   ...: 
10000000 loops, best of 3: 196 ns per loop

回答 2

除了时间之外,您显示的这段代码是完全错误的:您执行100个连接(完全忽略最后一个连接,而所有连接除外),然后在您执行第一个执行调用时,将其传递给本地变量query_stmt,该变量仅执行初始化呼叫。

首先,使您的代码正确,而不必担心时间安排:即建立或接收连接并对该连接执行100或500或任意数量的更新的函数,然后关闭该连接。一旦您的代码正常工作,便是考虑在其上使用的正确点timeit

具体来说,如果要计时的函数是一个无参数的函数,则foobar可以使用timeit.timeit(2.6或更高版本-在2.5及更高版本中更为复杂):

timeit.timeit('foobar()', number=1000)

您最好指定运行次数,因为对于您的用例而言,默认值(百万)可能会很高(导致在此代码中花费大量时间;-)。

Quite apart from the timing, this code you show is simply incorrect: you execute 100 connections (completely ignoring all but the last one), and then when you do the first execute call you pass it a local variable query_stmt which you only initialize after the execute call.

First, make your code correct, without worrying about timing yet: i.e. a function that makes or receives a connection and performs 100 or 500 or whatever number of updates on that connection, then closes the connection. Once you have your code working correctly is the correct point at which to think about using timeit on it!

Specifically, if the function you want to time is a parameter-less one called foobar you can use timeit.timeit (2.6 or later — it’s more complicated in 2.5 and before):

timeit.timeit('foobar()', number=1000)

You’d better specify the number of runs because the default, a million, may be high for your use case (leading to spending a lot of time in this code;-).


回答 3

专注于一件事。磁盘I / O速度很慢,因此如果您要调整的只是数据库查询,那么我将不进行测试。

而且,如果需要安排数据库执行时间,请改用数据库工具,例如询问查询计划,并注意性能不仅随确切的查询和拥有的索引而变化,还随数据负载(多少数据)而变化。您已存储)。

就是说,您只需将代码放入函数中,然后使用即可运行该函数timeit.timeit()

def function_to_repeat():
    # ...

duration = timeit.timeit(function_to_repeat, number=1000)

这将禁用垃圾收集,重复调用该function_to_repeat()函数,并使用以下命令计时这些调用的总持续时间timeit.default_timer(),这是您特定平台上最准确的可用时钟。

您应该将设置代码移出重复功能;例如,您应该首先连接到数据库,然后仅对查询计时。使用setup参数导入或创建这些依赖项,并将其传递给函数:

def function_to_repeat(var1, var2):
    # ...

duration = timeit.timeit(
    'function_to_repeat(var1, var2)',
    'from __main__ import function_to_repeat, var1, var2', 
    number=1000)

会抓取globals function_to_repeatvar1var2从您的脚本中将其每次重复传递给函数。

Focus on one specific thing. Disk I/O is slow, so I’d take that out of the test if all you are going to tweak is the database query.

And if you need to time your database execution, look for database tools instead, like asking for the query plan, and note that performance varies not only with the exact query and what indexes you have, but also with the data load (how much data you have stored).

That said, you can simply put your code in a function and run that function with timeit.timeit():

def function_to_repeat():
    # ...

duration = timeit.timeit(function_to_repeat, number=1000)

This would disable the garbage collection, repeatedly call the function_to_repeat() function, and time the total duration of those calls using timeit.default_timer(), which is the most accurate available clock for your specific platform.

You should move setup code out of the repeated function; for example, you should connect to the database first, then time only the queries. Use the setup argument to either import or create those dependencies, and pass them into your function:

def function_to_repeat(var1, var2):
    # ...

duration = timeit.timeit(
    'function_to_repeat(var1, var2)',
    'from __main__ import function_to_repeat, var1, var2', 
    number=1000)

would grab the globals function_to_repeat, var1 and var2 from your script and pass those to the function each repetition.


回答 4

我看到问题已经得到解答,但是我仍然想加2美分。

我也遇到过类似的情况,在这种情况下,我必须测试几种方法的执行时间,因此编写了一个小脚本,该脚本对其中编写的所有函数都调用timeit。

该脚本也可以在github gist上找到

希望对您和其他人有帮助。

from random import random
import types

def list_without_comprehension():
    l = []
    for i in xrange(1000):
        l.append(int(random()*100 % 100))
    return l

def list_with_comprehension():
    # 1K random numbers between 0 to 100
    l = [int(random()*100 % 100) for _ in xrange(1000)]
    return l


# operations on list_without_comprehension
def sort_list_without_comprehension():
    list_without_comprehension().sort()

def reverse_sort_list_without_comprehension():
    list_without_comprehension().sort(reverse=True)

def sorted_list_without_comprehension():
    sorted(list_without_comprehension())


# operations on list_with_comprehension
def sort_list_with_comprehension():
    list_with_comprehension().sort()

def reverse_sort_list_with_comprehension():
    list_with_comprehension().sort(reverse=True)

def sorted_list_with_comprehension():
    sorted(list_with_comprehension())


def main():
    objs = globals()
    funcs = []
    f = open("timeit_demo.sh", "w+")

    for objname in objs:
        if objname != 'main' and type(objs[objname]) == types.FunctionType:
            funcs.append(objname)
    funcs.sort()
    for func in funcs:
        f.write('''echo "Timing: %(funcname)s"
python -m timeit "import timeit_demo; timeit_demo.%(funcname)s();"\n\n
echo "------------------------------------------------------------"
''' % dict(
                funcname = func,
                )
            )

    f.close()

if __name__ == "__main__":
    main()

    from os import system

    #Works only for *nix platforms
    system("/bin/bash timeit_demo.sh")

    #un-comment below for windows
    #system("cmd timeit_demo.sh")

I see the question has already been answered, but still want to add my 2 cents for the same.

I have also faced similar scenario in which I have to test the execution times for several approaches and hence written a small script, which calls timeit on all functions written in it.

The script is also available as github gist here.

Hope it will help you and others.

from random import random
import types

def list_without_comprehension():
    l = []
    for i in xrange(1000):
        l.append(int(random()*100 % 100))
    return l

def list_with_comprehension():
    # 1K random numbers between 0 to 100
    l = [int(random()*100 % 100) for _ in xrange(1000)]
    return l


# operations on list_without_comprehension
def sort_list_without_comprehension():
    list_without_comprehension().sort()

def reverse_sort_list_without_comprehension():
    list_without_comprehension().sort(reverse=True)

def sorted_list_without_comprehension():
    sorted(list_without_comprehension())


# operations on list_with_comprehension
def sort_list_with_comprehension():
    list_with_comprehension().sort()

def reverse_sort_list_with_comprehension():
    list_with_comprehension().sort(reverse=True)

def sorted_list_with_comprehension():
    sorted(list_with_comprehension())


def main():
    objs = globals()
    funcs = []
    f = open("timeit_demo.sh", "w+")

    for objname in objs:
        if objname != 'main' and type(objs[objname]) == types.FunctionType:
            funcs.append(objname)
    funcs.sort()
    for func in funcs:
        f.write('''echo "Timing: %(funcname)s"
python -m timeit "import timeit_demo; timeit_demo.%(funcname)s();"\n\n
echo "------------------------------------------------------------"
''' % dict(
                funcname = func,
                )
            )

    f.close()

if __name__ == "__main__":
    main()

    from os import system

    #Works only for *nix platforms
    system("/bin/bash timeit_demo.sh")

    #un-comment below for windows
    #system("cmd timeit_demo.sh")

回答 5

这是史蒂文的答案的简单包装。该函数不会重复运行/求平均值,只是使您不必在任何地方重复计时代码即可:)

'''function which prints the wall time it takes to execute the given command'''
def time_func(func, *args): #*args can take 0 or more 
  import time
  start_time = time.time()
  func(*args)
  end_time = time.time()
  print("it took this long to run: {}".format(end_time-start_time))

Here’s a simple wrapper for steven’s answer. This function doesn’t do repeated runs/averaging, just saves you from having to repeat the timing code everywhere :)

'''function which prints the wall time it takes to execute the given command'''
def time_func(func, *args): #*args can take 0 or more 
  import time
  start_time = time.time()
  func(*args)
  end_time = time.time()
  print("it took this long to run: {}".format(end_time-start_time))

回答 6

测试套件没有尝试使用导入的程序,timeit因此很难说出意图是什么。但是,这是一个规范的答案,因此timeit似乎有一个完整的例子,详细说明了Martijn的答案

提供的文档timeit提供了许多示例和标志,值得一试。命令行的基本用法是:

$ python -mtimeit "all(True for _ in range(1000))"
2000 loops, best of 5: 161 usec per loop
$ python -mtimeit "all([True for _ in range(1000)])"
2000 loops, best of 5: 116 usec per loop

运行-h以查看所有选项。Python MOTW的精彩部分timeit展示了如何通过命令行中的导入和多行代码字符串运行模块。

在脚本形式中,我通常这样使用它:

import argparse
import copy
import dis
import inspect
import random
import sys
import timeit

def test_slice(L):
    L[:]

def test_copy(L):
    L.copy()

def test_deepcopy(L):
    copy.deepcopy(L)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--n", type=int, default=10 ** 5)
    parser.add_argument("--trials", type=int, default=100)
    parser.add_argument("--dis", action="store_true")
    args = parser.parse_args()
    n = args.n
    trials = args.trials
    namespace = dict(L = random.sample(range(n), k=n))
    funcs_to_test = [x for x in locals().values() 
                     if callable(x) and x.__module__ == __name__]
    print(f"{'-' * 30}\nn = {n}, {trials} trials\n{'-' * 30}\n")

    for func in funcs_to_test:
        fname = func.__name__
        fargs = ", ".join(inspect.signature(func).parameters)
        stmt = f"{fname}({fargs})"
        setup = f"from __main__ import {fname}"
        time = timeit.timeit(stmt, setup, number=trials, globals=namespace)
        print(inspect.getsource(globals().get(fname)))

        if args.dis:
            dis.dis(globals().get(fname))

        print(f"time (s) => {time}\n{'-' * 30}\n")

您可以轻松添加所需的函数和参数。使用不纯函数时要小心,并要注意状态。

样本输出:

$ python benchmark.py --n 10000
------------------------------
n = 10000, 100 trials
------------------------------

def test_slice(L):
    L[:]

time (s) => 0.015502399999999972
------------------------------

def test_copy(L):
    L.copy()

time (s) => 0.01651419999999998
------------------------------

def test_deepcopy(L):
    copy.deepcopy(L)

time (s) => 2.136012
------------------------------

The testing suite doesn’t make an attempt at using the imported timeit so it’s hard to tell what the intent was. Nonetheless, this is a canonical answer so a complete example of timeit seems in order, elaborating on Martijn’s answer.

The docs for timeit offer many examples and flags worth checking out. The basic usage on the command line is:

$ python -mtimeit "all(True for _ in range(1000))"
2000 loops, best of 5: 161 usec per loop
$ python -mtimeit "all([True for _ in range(1000)])"
2000 loops, best of 5: 116 usec per loop

Run with -h to see all options. Python MOTW has a great section on timeit that shows how to run modules via import and multiline code strings from the command line.

In script form, I typically use it like this:

import argparse
import copy
import dis
import inspect
import random
import sys
import timeit

def test_slice(L):
    L[:]

def test_copy(L):
    L.copy()

def test_deepcopy(L):
    copy.deepcopy(L)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--n", type=int, default=10 ** 5)
    parser.add_argument("--trials", type=int, default=100)
    parser.add_argument("--dis", action="store_true")
    args = parser.parse_args()
    n = args.n
    trials = args.trials
    namespace = dict(L = random.sample(range(n), k=n))
    funcs_to_test = [x for x in locals().values() 
                     if callable(x) and x.__module__ == __name__]
    print(f"{'-' * 30}\nn = {n}, {trials} trials\n{'-' * 30}\n")

    for func in funcs_to_test:
        fname = func.__name__
        fargs = ", ".join(inspect.signature(func).parameters)
        stmt = f"{fname}({fargs})"
        setup = f"from __main__ import {fname}"
        time = timeit.timeit(stmt, setup, number=trials, globals=namespace)
        print(inspect.getsource(globals().get(fname)))

        if args.dis:
            dis.dis(globals().get(fname))

        print(f"time (s) => {time}\n{'-' * 30}\n")

You can pretty easily drop in the functions and arguments you need. Use caution when using impure functions and take care of state.

Sample output:

$ python benchmark.py --n 10000
------------------------------
n = 10000, 100 trials
------------------------------

def test_slice(L):
    L[:]

time (s) => 0.015502399999999972
------------------------------

def test_copy(L):
    L.copy()

time (s) => 0.01651419999999998
------------------------------

def test_deepcopy(L):
    copy.deepcopy(L)

time (s) => 2.136012
------------------------------

尝试模拟datetime.date.today(),但无法正常工作

问题:尝试模拟datetime.date.today(),但无法正常工作

谁能告诉我为什么这不起作用?

>>> import mock
>>> @mock.patch('datetime.date.today')
... def today(cls):
...  return date(2010, 1, 1)
...
>>> from datetime import date
>>> date.today()
datetime.date(2010, 12, 19)

也许有人可以提出更好的方法?

Can anyone tell me why this isn’t working?

>>> import mock
>>> @mock.patch('datetime.date.today')
... def today(cls):
...  return date(2010, 1, 1)
...
>>> from datetime import date
>>> date.today()
datetime.date(2010, 12, 19)

Perhaps someone could suggest a better way?


回答 0

有一些问题。

首先,您使用的方式mock.patch不太正确。当用作装饰器时,它仅在装饰函数内datetime.date.todayMock对象替换给定的函数/类(在这种情况下为)。因此,只有在您将是一个不同的功能,不会出现你想要的。today()datetime.date.today

您真正想要的似乎是这样的:

@mock.patch('datetime.date.today')
def test():
    datetime.date.today.return_value = date(2010, 1, 1)
    print datetime.date.today()

不幸的是,这行不通:

>>> test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "build/bdist.macosx-10.6-universal/egg/mock.py", line 557, in patched
  File "build/bdist.macosx-10.6-universal/egg/mock.py", line 620, in __enter__
TypeError: can't set attributes of built-in/extension type 'datetime.date'

失败是因为Python内置类型是不可变的- 有关更多详细信息,请参见此答案

在这种情况下,我将自己子化datetime.date并创建合适的函数:

import datetime
class NewDate(datetime.date):
    @classmethod
    def today(cls):
        return cls(2010, 1, 1)
datetime.date = NewDate

现在您可以:

>>> datetime.date.today()
NewDate(2010, 1, 1)

There are a few problems.

First of all, the way you’re using mock.patch isn’t quite right. When used as a decorator, it replaces the given function/class (in this case, datetime.date.today) with a Mock object only within the decorated function. So, only within your today() will datetime.date.today be a different function, which doesn’t appear to be what you want.

What you really want seems to be more like this:

@mock.patch('datetime.date.today')
def test():
    datetime.date.today.return_value = date(2010, 1, 1)
    print datetime.date.today()

Unfortunately, this won’t work:

>>> test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "build/bdist.macosx-10.6-universal/egg/mock.py", line 557, in patched
  File "build/bdist.macosx-10.6-universal/egg/mock.py", line 620, in __enter__
TypeError: can't set attributes of built-in/extension type 'datetime.date'

This fails because Python built-in types are immutable – see this answer for more details.

In this case, I would subclass datetime.date myself and create the right function:

import datetime
class NewDate(datetime.date):
    @classmethod
    def today(cls):
        return cls(2010, 1, 1)
datetime.date = NewDate

And now you could do:

>>> datetime.date.today()
NewDate(2010, 1, 1)

回答 1

另一种选择是使用 https://github.com/spulec/freezegun/

安装它:

pip install freezegun

并使用它:

from freezegun import freeze_time

@freeze_time("2012-01-01")
def test_something():

    from datetime import datetime
    print(datetime.now()) #  2012-01-01 00:00:00

    from datetime import date
    print(date.today()) #  2012-01-01

它还会影响其他模块的方法调用中的其他日期时间调用:

other_module.py:

from datetime import datetime

def other_method():
    print(datetime.now())    

main.py:

from freezegun import freeze_time

@freeze_time("2012-01-01")
def test_something():

    import other_module
    other_module.other_method()

最后:

$ python main.py
# 2012-01-01

Another option is to use https://github.com/spulec/freezegun/

Install it:

pip install freezegun

And use it:

from freezegun import freeze_time

@freeze_time("2012-01-01")
def test_something():

    from datetime import datetime
    print(datetime.now()) #  2012-01-01 00:00:00

    from datetime import date
    print(date.today()) #  2012-01-01

It also affects other datetime calls in method calls from other modules:

other_module.py:

from datetime import datetime

def other_method():
    print(datetime.now())    

main.py:

from freezegun import freeze_time

@freeze_time("2012-01-01")
def test_something():

    import other_module
    other_module.other_method()

And finally:

$ python main.py
# 2012-01-01

回答 2

对于它的价值,Mock文档专门讨论datetime.date.today,并且无需创建虚拟类就可以做到这一点:

https://docs.python.org/3/library/unittest.mock-examples.html#partial-mocking

>>> from datetime import date
>>> with patch('mymodule.date') as mock_date:
...     mock_date.today.return_value = date(2010, 10, 8)
...     mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
...
...     assert mymodule.date.today() == date(2010, 10, 8)
...     assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)
...

For what it’s worth, the Mock docs talk about datetime.date.today specifically, and it’s possible to do this without having to create a dummy class:

https://docs.python.org/3/library/unittest.mock-examples.html#partial-mocking

>>> from datetime import date
>>> with patch('mymodule.date') as mock_date:
...     mock_date.today.return_value = date(2010, 10, 8)
...     mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
...
...     assert mymodule.date.today() == date(2010, 10, 8)
...     assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)
...

回答 3

我想我来晚了一点,但是我认为这里的主要问题是您今天正在直接修补datetime.date.today,根据文档,这是错误的。

例如,您应该修补导入到已测试功能所在文件中的引用。

假设您有一个functions.py文件,其中包含以下内容:

import datetime

def get_today():
    return datetime.date.today()

然后,在测试中,您应该有这样的内容

import datetime
import unittest

from functions import get_today
from mock import patch, Mock

class GetTodayTest(unittest.TestCase):

    @patch('functions.datetime')
    def test_get_today(self, datetime_mock):
        datetime_mock.date.today = Mock(return_value=datetime.strptime('Jun 1 2005', '%b %d %Y'))
        value = get_today()
        # then assert your thing...

希望这会有所帮助。

I guess I came a little late for this but I think the main problem here is that you’re patching datetime.date.today directly and, according to the documentation, this is wrong.

You should patch the reference imported in the file where the tested function is, for example.

Let’s say you have a functions.py file where you have the following:

import datetime

def get_today():
    return datetime.date.today()

then, in your test, you should have something like this

import datetime
import unittest

from functions import get_today
from mock import patch, Mock

class GetTodayTest(unittest.TestCase):

    @patch('functions.datetime')
    def test_get_today(self, datetime_mock):
        datetime_mock.date.today = Mock(return_value=datetime.strptime('Jun 1 2005', '%b %d %Y'))
        value = get_today()
        # then assert your thing...

Hope this helps a little bit.


回答 4

要添加到Daniel G的解决方案中

from datetime import date

class FakeDate(date):
    "A manipulable date replacement"
    def __new__(cls, *args, **kwargs):
        return date.__new__(date, *args, **kwargs)

这将创建一个类,该类在实例化时将返回正常的datetime.date对象,但也可以对其进行更改。

@mock.patch('datetime.date', FakeDate)
def test():
    from datetime import date
    FakeDate.today = classmethod(lambda cls: date(2010, 1, 1))
    return date.today()

test() # datetime.date(2010, 1, 1)

To add to Daniel G’s solution:

from datetime import date

class FakeDate(date):
    "A manipulable date replacement"
    def __new__(cls, *args, **kwargs):
        return date.__new__(date, *args, **kwargs)

This creates a class which, when instantiated, will return a normal datetime.date object, but which is also able to be changed.

@mock.patch('datetime.date', FakeDate)
def test():
    from datetime import date
    FakeDate.today = classmethod(lambda cls: date(2010, 1, 1))
    return date.today()

test() # datetime.date(2010, 1, 1)

回答 5

几天前我遇到了同样的情况,我的解决方案是在模块中定义一个函数进行测试并对其进行模拟:

def get_date_now():
    return datetime.datetime.now()

今天我发现有关FreezeGun的信息,似乎可以很好地涵盖此案例

from freezegun import freeze_time
import datetime
import unittest


@freeze_time("2012-01-14")
def test():
    assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)

I faced the same situation a couple of days ago, and my solution was to define a function in the module to test and just mock that:

def get_date_now():
    return datetime.datetime.now()

Today I found out about FreezeGun, and it seems to cover this case beautifully

from freezegun import freeze_time
import datetime
import unittest


@freeze_time("2012-01-14")
def test():
    assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)

回答 6

对我来说,最简单的方法是:

import datetime
from unittest.mock import Mock, patch

def test():
    datetime_mock = Mock(wraps=datetime.datetime)
    datetime_mock.now.return_value = datetime.datetime(1999, 1, 1)
    with patch('datetime.datetime', new=datetime_mock):
        assert datetime.datetime.now() == datetime.datetime(1999, 1, 1)

注意此解决方案:从所有的功能datetime moduletarget_module停止工作。

The easiest way for me is doing this:

import datetime
from unittest.mock import Mock, patch

def test():
    datetime_mock = Mock(wraps=datetime.datetime)
    datetime_mock.now.return_value = datetime.datetime(1999, 1, 1)
    with patch('datetime.datetime', new=datetime_mock):
        assert datetime.datetime.now() == datetime.datetime(1999, 1, 1)

CAUTION for this solution: all functionality from datetime module from the target_module will stop working.


回答 7

您可以基于Daniel G解决方案使用以下方法。这具有不破坏类型检查的优点isinstance(d, datetime.date)

import mock

def fixed_today(today):
    from datetime import date

    class FakeDateType(type):
        def __instancecheck__(self, instance):
            return isinstance(instance, date)

    class FakeDate(date):
        __metaclass__ = FakeDateType

        def __new__(cls, *args, **kwargs):
            return date.__new__(date, *args, **kwargs)

        @staticmethod
        def today():
            return today

    return mock.patch("datetime.date", FakeDate)

基本上,我们用datetime.date自己的python子类替换基于C的类,该子类产生原始datetime.date实例并isinstance()完全像native一样响应查询datetime.date

在测试中将其用作上下文管理器:

with fixed_today(datetime.date(2013, 11, 22)):
    # run the code under test
    # note, that these type checks will not break when patch is active:
    assert isinstance(datetime.date.today(), datetime.date)

可以使用类似的方法来模拟datetime.datetime.now()功能。

You can use the following approach, based on Daniel G solution. This one has advantage of not breaking type checking with isinstance(d, datetime.date).

import mock

def fixed_today(today):
    from datetime import date

    class FakeDateType(type):
        def __instancecheck__(self, instance):
            return isinstance(instance, date)

    class FakeDate(date):
        __metaclass__ = FakeDateType

        def __new__(cls, *args, **kwargs):
            return date.__new__(date, *args, **kwargs)

        @staticmethod
        def today():
            return today

    return mock.patch("datetime.date", FakeDate)

Basically, we replace C-based datetime.date class with our own python subclass, that produces original datetime.date instances and responds to isinstance() queries exactly as native datetime.date.

Use it as context manager in your tests:

with fixed_today(datetime.date(2013, 11, 22)):
    # run the code under test
    # note, that these type checks will not break when patch is active:
    assert isinstance(datetime.date.today(), datetime.date)

Similar approach can be used to mock datetime.datetime.now() function.


回答 8

一般来说,您可能已经datetime或可能将其datetime.date导入到某个模块中。模拟该方法的一种更有效的方法是将其修补在要导入的模块上。例:

py

from datetime import date

def my_method():
    return date.today()

然后,对于您的测试,模拟对象本身将作为参数传递给测试方法。您将使用所需的结果值设置模拟,然后调用被测方法。然后,您可以断言您的方法已完成您想要的。

>>> import mock
>>> import a
>>> @mock.patch('a.date')
... def test_my_method(date_mock):
...     date_mock.today.return_value = mock.sentinel.today
...     result = a.my_method()
...     print result
...     date_mock.today.assert_called_once_with()
...     assert mock.sentinel.today == result
...
>>> test_my_method()
sentinel.today

一句话警告。毫无疑问,过度嘲弄是有可能的。当您这样做时,它将使您的测试时间更长,更难以理解且无法维护。在模拟一个简单的方法之前datetime.date.today,请问问自己是否真的需要模拟它。如果您的测试很简短,并且在不模拟功能的情况下可以正常工作,则您可能只是查看要测试的代码的内部细节,而不是需要模拟的对象。

Generally speaking, you would have datetime or perhaps datetime.date imported into a module somewhere. A more effective way of mocking the method would be to patch it on the module that is importing it. Example:

a.py

from datetime import date

def my_method():
    return date.today()

Then for your test, the mock object itself would be passed as an argument to the test method. You would set up the mock with the result value you want, and then call your method under test. Then you would assert that your method did what you want.

>>> import mock
>>> import a
>>> @mock.patch('a.date')
... def test_my_method(date_mock):
...     date_mock.today.return_value = mock.sentinel.today
...     result = a.my_method()
...     print result
...     date_mock.today.assert_called_once_with()
...     assert mock.sentinel.today == result
...
>>> test_my_method()
sentinel.today

A word of warning. It is most certainly possible to go overboard with mocking. When you do, it makes your tests longer, harder to understand, and impossible to maintain. Before you mock a method as simple as datetime.date.today, ask yourself if you really need to mock it. If your test is short and to the point and works fine without mocking the function, you may just be looking at an internal detail of the code you’re testing rather than an object you need to mock.


回答 9

这是另一种模拟方法datetime.date.today(),具有额外的好处,即其余datetime功能可以继续工作,因为模拟对象被配置为包装原始datetime模块:

from unittest import mock, TestCase

import foo_module

class FooTest(TestCase):

    @mock.patch(f'{foo_module.__name__}.datetime', wraps=datetime)
    def test_something(self, mock_datetime):
        # mock only datetime.date.today()
        mock_datetime.date.today.return_value = datetime.date(2019, 3, 15)
        # other calls to datetime functions will be forwarded to original datetime

注意wraps=datetime参数mock.patch()–当foo_module使用其他datetime功能时,date.today()它们将被转发到原始包装的datetime模块。

Here’s another way to mock datetime.date.today() with an added bonus that the rest of datetime functions continue to work, as the mock object is configured to wrap the original datetime module:

from unittest import mock, TestCase

import foo_module

class FooTest(TestCase):

    @mock.patch(f'{foo_module.__name__}.datetime', wraps=datetime)
    def test_something(self, mock_datetime):
        # mock only datetime.date.today()
        mock_datetime.date.today.return_value = datetime.date(2019, 3, 15)
        # other calls to datetime functions will be forwarded to original datetime

Note the wraps=datetime argument to mock.patch() – when the foo_module uses other datetime functions besides date.today() they will be forwarded to the original wrapped datetime module.


回答 10

http://blog.xelnor.net/python-mocking-datetime/中讨论了几种解决方案。综上所述:

模拟对象 -简单高效,但中断了isinstance()检查:

target = datetime.datetime(2009, 1, 1)
with mock.patch.object(datetime, 'datetime', mock.Mock(wraps=datetime.datetime)) as patched:
    patched.now.return_value = target
    print(datetime.datetime.now())

模拟课

import datetime
import mock

real_datetime_class = datetime.datetime

def mock_datetime_now(target, dt):
    class DatetimeSubclassMeta(type):
        @classmethod
        def __instancecheck__(mcs, obj):
            return isinstance(obj, real_datetime_class)

    class BaseMockedDatetime(real_datetime_class):
        @classmethod
        def now(cls, tz=None):
            return target.replace(tzinfo=tz)

        @classmethod
        def utcnow(cls):
            return target

    # Python2 & Python3 compatible metaclass
    MockedDatetime = DatetimeSubclassMeta('datetime', (BaseMockedDatetime,), {})

    return mock.patch.object(dt, 'datetime', MockedDatetime)

用于:

with mock_datetime_now(target, datetime):
   ....

Several solutions are discussed in http://blog.xelnor.net/python-mocking-datetime/. In summary:

Mock object – Simple and efficient but breaks isinstance() checks:

target = datetime.datetime(2009, 1, 1)
with mock.patch.object(datetime, 'datetime', mock.Mock(wraps=datetime.datetime)) as patched:
    patched.now.return_value = target
    print(datetime.datetime.now())

Mock class

import datetime
import mock

real_datetime_class = datetime.datetime

def mock_datetime_now(target, dt):
    class DatetimeSubclassMeta(type):
        @classmethod
        def __instancecheck__(mcs, obj):
            return isinstance(obj, real_datetime_class)

    class BaseMockedDatetime(real_datetime_class):
        @classmethod
        def now(cls, tz=None):
            return target.replace(tzinfo=tz)

        @classmethod
        def utcnow(cls):
            return target

    # Python2 & Python3 compatible metaclass
    MockedDatetime = DatetimeSubclassMeta('datetime', (BaseMockedDatetime,), {})

    return mock.patch.object(dt, 'datetime', MockedDatetime)

Use as:

with mock_datetime_now(target, datetime):
   ....

回答 11

也许您可以使用自己的“ today()”方法,将在需要的地方进行修补。可以在此处找到模拟utcnow()的示例:https ://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default

Maybe you could use your own “today()” method that you will patch where needed. Example with mocking utcnow() can be found here: https://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default


回答 12

我使用自定义装饰器实现了@ user3016183方法:

def changeNow(func, newNow = datetime(2015, 11, 23, 12, 00, 00)):
    """decorator used to change datetime.datetime.now() in the tested function."""
    def retfunc(self):                             
        with mock.patch('mymodule.datetime') as mock_date:                         
            mock_date.now.return_value = newNow
            mock_date.side_effect = lambda *args, **kw: datetime(*args, **kw)
            func(self)
    return retfunc

我以为有一天可以帮助某人…

I implemented @user3016183 method using a custom decorator:

def changeNow(func, newNow = datetime(2015, 11, 23, 12, 00, 00)):
    """decorator used to change datetime.datetime.now() in the tested function."""
    def retfunc(self):                             
        with mock.patch('mymodule.datetime') as mock_date:                         
            mock_date.now.return_value = newNow
            mock_date.side_effect = lambda *args, **kw: datetime(*args, **kw)
            func(self)
    return retfunc

I thought that might help someone one day…


回答 13

可以从datetime模块中模拟功能而无需添加side_effects

import mock
from datetime import datetime
from where_datetime_used import do

initial_date = datetime.strptime('2018-09-27', "%Y-%m-%d")
with mock.patch('where_datetime_used.datetime') as mocked_dt:
    mocked_dt.now.return_value = initial_date
    do()

It’s possible to mock functions from datetime module without adding side_effects

import mock
from datetime import datetime
from where_datetime_used import do

initial_date = datetime.strptime('2018-09-27', "%Y-%m-%d")
with mock.patch('where_datetime_used.datetime') as mocked_dt:
    mocked_dt.now.return_value = initial_date
    do()

回答 14

对于那些使用pytest与嘲笑者的人,这里是我如何嘲笑的datetime.datetime.now(),这与原始问题非常相似。

test_get_now(mocker):
    datetime_mock = mocker.patch("blackline_accounts_import.datetime",)
    datetime_mock.datetime.now.return_value=datetime.datetime(2019,3,11,6,2,0,0)

    now == function_being_tested()  # run function

    assert now == datetime.datetime(2019,3,11,6,2,0,0)

本质上,模拟必须设置为返回指定的日期。您无法直接修补datetime的对象。

For those of you using pytest with mocker here is how I mocked datetime.datetime.now() which is very similar to the original question.

test_get_now(mocker):
    datetime_mock = mocker.patch("blackline_accounts_import.datetime",)
    datetime_mock.datetime.now.return_value=datetime.datetime(2019,3,11,6,2,0,0)

    now == function_being_tested()  # run function

    assert now == datetime.datetime(2019,3,11,6,2,0,0)

Essentially the mock has to be set to return the specified date. You aren’t able to patch over datetime’s object directly.


回答 15

我通过导入datetimeas realdatetime并将模拟中所需的方法替换为实际方法来完成这项工作:

import datetime as realdatetime

@mock.patch('datetime')
def test_method(self, mock_datetime):
    mock_datetime.today = realdatetime.today
    mock_datetime.now.return_value = realdatetime.datetime(2019, 8, 23, 14, 34, 8, 0)

I made this work by importing datetime as realdatetime and replacing the methods I needed in the mock with the real methods:

import datetime as realdatetime

@mock.patch('datetime')
def test_method(self, mock_datetime):
    mock_datetime.today = realdatetime.today
    mock_datetime.now.return_value = realdatetime.datetime(2019, 8, 23, 14, 34, 8, 0)

回答 16

您可以datetime使用以下方法进行模拟:

在模块中sources.py

import datetime


class ShowTime:
    def current_date():
        return datetime.date.today().strftime('%Y-%m-%d')

在您的tests.py

from unittest import TestCase, mock
import datetime


class TestShowTime(TestCase):
    def setUp(self) -> None:
        self.st = sources.ShowTime()
        super().setUp()

    @mock.patch('sources.datetime.date')
    def test_current_date(self, date_mock):
        date_mock.today.return_value = datetime.datetime(year=2019, month=10, day=1)
        current_date = self.st.current_date()
        self.assertEqual(current_date, '2019-10-01')

You can mock datetime using this:

In the module sources.py:

import datetime


class ShowTime:
    def current_date():
        return datetime.date.today().strftime('%Y-%m-%d')

In your tests.py:

from unittest import TestCase, mock
import datetime


class TestShowTime(TestCase):
    def setUp(self) -> None:
        self.st = sources.ShowTime()
        super().setUp()

    @mock.patch('sources.datetime.date')
    def test_current_date(self, date_mock):
        date_mock.today.return_value = datetime.datetime(year=2019, month=10, day=1)
        current_date = self.st.current_date()
        self.assertEqual(current_date, '2019-10-01')

回答 17

CPython实际上使用纯Python Lib / datetime.py和C优化的模块/_datetimemodule.c来实现datetime模块。C最佳化版本无法修补,而纯Python版本可以修补。

Lib / datetime.py中的纯Python实现的底部是以下代码:

try:
    from _datetime import *  # <-- Import from C-optimized module.
except ImportError:
    pass

此代码导入所有C优化的定义,并有效替换所有纯Python定义。我们可以通过以下操作强制CPython使用datetime模块的纯Python实现:

import datetime
import importlib
import sys

sys.modules["_datetime"] = None
importlib.reload(datetime)

通过设置sys.modules["_datetime"] = None,我们告诉Python忽略C优化模块。然后,我们重新加载导致导入的模块_datetime失败。现在,纯Python定义仍然存在并且可以正常修补。

如果您使用的是Pytest,则将上面的代码段包含在conftest.py中,即可datetime正常修补对象。

CPython actually implements the datetime module using both a pure-Python Lib/datetime.py and a C-optimized Modules/_datetimemodule.c. The C-optimized version cannot be patched but the pure-Python version can.

At the bottom of the pure-Python implementation in Lib/datetime.py is this code:

try:
    from _datetime import *  # <-- Import from C-optimized module.
except ImportError:
    pass

This code imports all the C-optimized definitions and effectively replaces all the pure-Python definitions. We can force CPython to use the pure-Python implementation of the datetime module by doing:

import datetime
import importlib
import sys

sys.modules["_datetime"] = None
importlib.reload(datetime)

By setting sys.modules["_datetime"] = None, we tell Python to ignore the C-optimized module. Then we reload the module which causes the import from _datetime to fail. Now the pure-Python definitions remain and can be patched normally.

If you’re using Pytest then include the snippet above in conftest.py and you can patch datetime objects normally.


具有基类和子类的Python单元测试

问题:具有基类和子类的Python单元测试

我目前有一些单元测试,它们共享一组通用的测试。这是一个例子:

import unittest

class BaseTest(unittest.TestCase):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

上面的输出是:

Calling BaseTest:testCommon
.Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK

有没有办法重写上面的内容,这样testCommon就不会调用第一个?

编辑: 而不是运行上面的5个测试,我希望它只运行4个测试,其中2个来自SubTest1,另外2个来自SubTest2。似乎Python unittest自己在运行原始的BaseTest,我需要一种机制来防止这种情况的发生。

I currently have a few unit tests which share a common set of tests. Here’s an example:

import unittest

class BaseTest(unittest.TestCase):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

The output of the above is:

Calling BaseTest:testCommon
.Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK

Is there a way to rewrite the above so that the very first testCommon is not called?

EDIT: Instead of running 5 tests above, I want it to run only 4 tests, 2 from the SubTest1 and another 2 from SubTest2. It seems that Python unittest is running the original BaseTest on its own and I need a mechanism to prevent that from happening.


回答 0

使用多重继承,因此具有通用测试的类本身不会继承自TestCase。

import unittest

class CommonTests(object):
    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(unittest.TestCase, CommonTests):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(unittest.TestCase, CommonTests):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

Use multiple inheritance, so your class with common tests doesn’t itself inherit from TestCase.

import unittest

class CommonTests(object):
    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(unittest.TestCase, CommonTests):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(unittest.TestCase, CommonTests):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

回答 1

不要使用多重继承,它会咬你以后

相反,您可以将您的基类移至单独的模块中,或将其与空白类包装在一起:

class BaseTestCases:

    class BaseTest(unittest.TestCase):

        def testCommon(self):
            print('Calling BaseTest:testCommon')
            value = 5
            self.assertEqual(value, 5)


class SubTest1(BaseTestCases.BaseTest):

    def testSub1(self):
        print('Calling SubTest1:testSub1')
        sub = 3
        self.assertEqual(sub, 3)


class SubTest2(BaseTestCases.BaseTest):

    def testSub2(self):
        print('Calling SubTest2:testSub2')
        sub = 4
        self.assertEqual(sub, 4)

if __name__ == '__main__':
    unittest.main()

输出:

Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK

Do not use multiple inheritance, it will bite you later.

Instead you can just move your base class into the separate module or wrap it with the blank class:

class BaseTestCases:

    class BaseTest(unittest.TestCase):

        def testCommon(self):
            print('Calling BaseTest:testCommon')
            value = 5
            self.assertEqual(value, 5)


class SubTest1(BaseTestCases.BaseTest):

    def testSub1(self):
        print('Calling SubTest1:testSub1')
        sub = 3
        self.assertEqual(sub, 3)


class SubTest2(BaseTestCases.BaseTest):

    def testSub2(self):
        print('Calling SubTest2:testSub2')
        sub = 4
        self.assertEqual(sub, 4)

if __name__ == '__main__':
    unittest.main()

The output:

Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK

回答 2

您可以使用单个命令解决此问题:

del(BaseTest)

因此,代码如下所示:

import unittest

class BaseTest(unittest.TestCase):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

del(BaseTest)

if __name__ == '__main__':
    unittest.main()

You can solve this problem with a single command:

del(BaseTest)

So the code would look like this:

import unittest

class BaseTest(unittest.TestCase):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

del(BaseTest)

if __name__ == '__main__':
    unittest.main()

回答 3

Matthew Marshall的答案很好,但是它要求您在每个测试用例中都从两个类继承,这很容易出错。相反,我使用了这个(python> = 2.7):

class BaseTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        if cls is BaseTest:
            raise unittest.SkipTest("Skip BaseTest tests, it's a base class")
        super(BaseTest, cls).setUpClass()

Matthew Marshall’s answer is great, but it requires that you inherit from two classes in each of your test cases, which is error-prone. Instead, I use this (python>=2.7):

class BaseTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        if cls is BaseTest:
            raise unittest.SkipTest("Skip BaseTest tests, it's a base class")
        super(BaseTest, cls).setUpClass()

回答 4

您想达到什么目的?如果您有通用的测试代码(断言,模板测试等),则将它们放在没有前缀的方法中,test这样unittest就不会加载它们。

import unittest

class CommonTests(unittest.TestCase):
      def common_assertion(self, foo, bar, baz):
          # whatever common code
          self.assertEqual(foo(bar), baz)

class BaseTest(CommonTests):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(CommonTests):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)

class SubTest2(CommonTests):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

What are you trying to achieve? If you have common test code (assertions, template tests, etc), then place them in methods which aren’t prefixed with test so unittest won’t load them.

import unittest

class CommonTests(unittest.TestCase):
      def common_assertion(self, foo, bar, baz):
          # whatever common code
          self.assertEqual(foo(bar), baz)

class BaseTest(CommonTests):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(CommonTests):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)

class SubTest2(CommonTests):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

回答 5

Matthew的答案是我需要使用的答案,因为我仍然是2.5。但是从2.7开始,您可以在要跳过的任何测试方法上使用@ unittest.skip()装饰器。

http://docs.python.org/library/unittest.html#skipping-tests-and-expected-failures

您需要实现自己的跳过装饰器以检查基本类型。以前没有使用过此功能,但是在我的头顶上,您可以使用BaseTest作为标记类型来调节跳过:

def skipBaseTest(obj):
    if type(obj) is BaseTest:
        return unittest.skip("BaseTest tests skipped")
    return lambda func: func

Matthew’s answer is the one I needed to use since I’m on 2.5 still. But as of 2.7 you can use the @unittest.skip() decorator on any test methods you want to skip.

http://docs.python.org/library/unittest.html#skipping-tests-and-expected-failures

You’ll need to implement your own skipping decorator to check for the base type. Haven’t used this feature before, but off the top of my head you could use BaseTest as a marker type to condition the skip:

def skipBaseTest(obj):
    if type(obj) is BaseTest:
        return unittest.skip("BaseTest tests skipped")
    return lambda func: func

回答 6

我想解决此问题的一种方法是通过隐藏测试方法(如果使用了基类)。这样就不会跳过测试,因此在许多测试报告工具中,测试结果可以是绿色而不是黄色。

与mixin方法相比,ide之类的PyCharm不会抱怨基类中缺少单元测试方法。

如果基类从该类继承,则它将需要重写setUpClasstearDownClass方法。

class BaseTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls._test_methods = []
        if cls is BaseTest:
            for name in dir(cls):
                if name.startswith('test') and callable(getattr(cls, name)):
                    cls._test_methods.append((name, getattr(cls, name)))
                    setattr(cls, name, lambda self: None)

    @classmethod
    def tearDownClass(cls):
        if cls is BaseTest:
            for name, method in cls._test_methods:
                setattr(cls, name, method)
            cls._test_methods = []

A way I’ve thought of solving this is by hiding the test methods if the base class is used. This way the tests aren’t skipped, so the test results can be green instead of yellow in many test reporting tools.

Compared to the mixin method, ide’s like PyCharm won’t complain that unit test methods are missing from the base class.

If a base class inherits from this class, it will need to override the setUpClass and tearDownClass methods.

class BaseTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls._test_methods = []
        if cls is BaseTest:
            for name in dir(cls):
                if name.startswith('test') and callable(getattr(cls, name)):
                    cls._test_methods.append((name, getattr(cls, name)))
                    setattr(cls, name, lambda self: None)

    @classmethod
    def tearDownClass(cls):
        if cls is BaseTest:
            for name, method in cls._test_methods:
                setattr(cls, name, method)
            cls._test_methods = []

回答 7

您可以添加__test_ = FalseBaseTest类,但是如果添加它,请注意必须添加__test__ = True派生类才能运行测试。

import unittest

class BaseTest(unittest.TestCase):
    __test__ = False

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):
    __test__ = True

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):
    __test__ = True

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

You can add __test__ = False in BaseTest class, but if you add it, be aware that you must add __test__ = True in derived classes to be able to run tests.

import unittest

class BaseTest(unittest.TestCase):
    __test__ = False

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):
    __test__ = True

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):
    __test__ = True

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

回答 8

另一种选择是不执行

unittest.main()

除此之外,您可以使用

suite = unittest.TestLoader().loadTestsFromTestCase(TestClass)
unittest.TextTestRunner(verbosity=2).run(suite)

所以你只执行类中的测试 TestClass

Another option is not to execute

unittest.main()

Instead of that you can use

suite = unittest.TestLoader().loadTestsFromTestCase(TestClass)
unittest.TextTestRunner(verbosity=2).run(suite)

So you only execute the tests in the class TestClass


回答 9

我所做的与@Vladim P.(https://stackoverflow.com/a/25695512/2451329)大致相同,但略有修改:

import unittest2


from some_module import func1, func2


def make_base_class(func):

    class Base(unittest2.TestCase):

        def test_common1(self):
            print("in test_common1")
            self.assertTrue(func())

        def test_common2(self):
            print("in test_common1")
            self.assertFalse(func(42))

    return Base



class A(make_base_class(func1)):
    pass


class B(make_base_class(func2)):

    def test_func2_with_no_arg_return_bar(self):
        self.assertEqual("bar", func2())

然后我们去。

I made about the same than @Vladim P. (https://stackoverflow.com/a/25695512/2451329) but slightly modified:

import unittest2


from some_module import func1, func2


def make_base_class(func):

    class Base(unittest2.TestCase):

        def test_common1(self):
            print("in test_common1")
            self.assertTrue(func())

        def test_common2(self):
            print("in test_common1")
            self.assertFalse(func(42))

    return Base



class A(make_base_class(func1)):
    pass


class B(make_base_class(func2)):

    def test_func2_with_no_arg_return_bar(self):
        self.assertEqual("bar", func2())

and there we go.


回答 10

从Python 3.2开始,您可以将test_loader函数添加到模块中,以控制由测试发现机制找到哪些测试(如果有)。

例如,下面将只加载原来的海报的SubTest1SubTest2测试用例,忽略Base

def load_tests(loader, standard_tests, pattern):
    suite = TestSuite()
    suite.addTests([SubTest1, SubTest2])
    return suite

它应该可以遍历standard_tests(一个TestSuite包含默认加载器发现测试)并复制,但Basesuite代替,但嵌套性质TestSuite.__iter__品牌是一个复杂得多。

As of Python 3.2, you can add a test_loader function to a module to control which tests (if any) are found by the test discovery mechanism.

For example, the following will only load the original poster’s SubTest1 and SubTest2 Test Cases, ignoring Base:

def load_tests(loader, standard_tests, pattern):
    suite = TestSuite()
    suite.addTests([SubTest1, SubTest2])
    return suite

It ought to be possible to iterate over standard_tests (a TestSuite containing the tests the default loader found) and copy all but Base to suite instead, but the nested nature of TestSuite.__iter__ makes that a lot more complicated.


回答 11

只需将testCommon方法重命名为其他名称即可。单元测试(通常)会跳过其中没有“测试”的所有内容。

快速简单

  import unittest

  class BaseTest(unittest.TestCase):

   def methodCommon(self):
       print 'Calling BaseTest:testCommon'
       value = 5
       self.assertEquals(value, 5)

  class SubTest1(BaseTest):

      def testSub1(self):
          print 'Calling SubTest1:testSub1'
          sub = 3
          self.assertEquals(sub, 3)


  class SubTest2(BaseTest):

      def testSub2(self):
          print 'Calling SubTest2:testSub2'
          sub = 4
          self.assertEquals(sub, 4)

  if __name__ == '__main__':
      unittest.main()`

Just rename the testCommon method to something else. Unittest (usually) skips anything that doesn’t have ‘test’ in it.

Quick and simple

  import unittest

  class BaseTest(unittest.TestCase):

   def methodCommon(self):
       print 'Calling BaseTest:testCommon'
       value = 5
       self.assertEquals(value, 5)

  class SubTest1(BaseTest):

      def testSub1(self):
          print 'Calling SubTest1:testSub1'
          sub = 3
          self.assertEquals(sub, 3)


  class SubTest2(BaseTest):

      def testSub2(self):
          print 'Calling SubTest2:testSub2'
          sub = 4
          self.assertEquals(sub, 4)

  if __name__ == '__main__':
      unittest.main()`

回答 12

因此,这是一个旧线程,但是我今天遇到了这个问题,并为此想到了自己的技巧。它使用一个装饰器,当通过基类访问时,该装饰器使函数的值变为None。无需担心setup和setupclass,因为如果基类没有测试,它们将不会运行。

import types
import unittest


class FunctionValueOverride(object):
    def __init__(self, cls, default, override=None):
        self.cls = cls
        self.default = default
        self.override = override

    def __get__(self, obj, klass):
        if klass == self.cls:
            return self.override
        else:
            if obj:
                return types.MethodType(self.default, obj)
            else:
                return self.default


def fixture(cls):
    for t in vars(cls):
        if not callable(getattr(cls, t)) or t[:4] != "test":
            continue
        setattr(cls, t, FunctionValueOverride(cls, getattr(cls, t)))
    return cls


@fixture
class BaseTest(unittest.TestCase):
    def testCommon(self):
        print('Calling BaseTest:testCommon')
        value = 5
        self.assertEqual(value, 5)


class SubTest1(BaseTest):
    def testSub1(self):
        print('Calling SubTest1:testSub1')
        sub = 3
        self.assertEqual(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print('Calling SubTest2:testSub2')
        sub = 4
        self.assertEqual(sub, 4)

if __name__ == '__main__':
    unittest.main()

So this is kind of an old thread but I came across this problem today and thought of my own hack for it. It uses a decorator that makes the values of the functions None when acessed through the base class. Don’t need to worry about setup and setupclass because if the baseclass has no tests they won’t run.

import types
import unittest


class FunctionValueOverride(object):
    def __init__(self, cls, default, override=None):
        self.cls = cls
        self.default = default
        self.override = override

    def __get__(self, obj, klass):
        if klass == self.cls:
            return self.override
        else:
            if obj:
                return types.MethodType(self.default, obj)
            else:
                return self.default


def fixture(cls):
    for t in vars(cls):
        if not callable(getattr(cls, t)) or t[:4] != "test":
            continue
        setattr(cls, t, FunctionValueOverride(cls, getattr(cls, t)))
    return cls


@fixture
class BaseTest(unittest.TestCase):
    def testCommon(self):
        print('Calling BaseTest:testCommon')
        value = 5
        self.assertEqual(value, 5)


class SubTest1(BaseTest):
    def testSub1(self):
        print('Calling SubTest1:testSub1')
        sub = 3
        self.assertEqual(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print('Calling SubTest2:testSub2')
        sub = 4
        self.assertEqual(sub, 4)

if __name__ == '__main__':
    unittest.main()

回答 13

将BaseTest方法名称更改为setUp:

class BaseTest(unittest.TestCase):
    def setUp(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)


class SubTest1(BaseTest):
    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):
    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

输出:

在0.000秒内进行了2次测试

调用BaseTest:testCommon调用
SubTest1:testSub1调用
BaseTest:testCommon调用
SubTest2:testSub2

文档中

TestCase.setUp()
用于准备测试治具的方法。在调用测试方法之前立即调用该方法。此方法引发的任何异常都将被视为错误而不是测试失败。默认实现不执行任何操作。

Change the BaseTest method name to setUp:

class BaseTest(unittest.TestCase):
    def setUp(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)


class SubTest1(BaseTest):
    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):
    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

Output:

Ran 2 tests in 0.000s

Calling BaseTest:testCommon Calling
SubTest1:testSub1 Calling
BaseTest:testCommon Calling
SubTest2:testSub2

From the documentation:

TestCase.setUp()
Method called to prepare the test fixture. This is called immediately before calling the test method; any exception raised by this method will be considered an error rather than a test failure. The default implementation does nothing.


在pytest中,conftest.py文件有什么用?

问题:在pytest中,conftest.py文件有什么用?

我最近发现了pytest。好像很棒 但是,我认为文档可能会更好。

我正在尝试了解conftest.py文件的用途。

在我的(目前很小的)测试套件conftest.py中,项目根目录下有一个文件。我用它来定义要注入测试的灯具。

我有两个问题:

  1. 这是正确的用法conftest.py吗?它还有其他用途吗?
  2. 我可以有多个conftest.py文件吗?我什么时候要这么做?示例将被理解。

更一般而言,您如何定义conftest.pypy.test测试套件中的目的和正确使用文件?

I recently discovered pytest. It seems great. However, I feel the documentation could be better.

I’m trying to understand what conftest.py files are meant to be used for.

In my (currently small) test suite I have one conftest.py file at the project root. I use it to define the fixtures that I inject into my tests.

I have two questions:

  1. Is this the correct use of conftest.py? Does it have other uses?
  2. Can I have more than one conftest.py file? When would I want to do that? Examples will be appreciated.

More generally, how would you define the purpose and correct use of conftest.py file(s) in a py.test test suite?


回答 0

这是conftest.py的正确用法吗?

是的。治具是潜在的和普遍的使用conftest.py。您将定义的固定装置将在测试套件中的所有测试之间共享。但是,在根目录中定义固定装置conftest.py可能没有用,如果所有测试未使用此类固定装置,则会减慢测试速度。

它还有其他用途吗?

是的,它确实。

  • 夹具:为测试使用的静态数据定义夹具。除非另有说明,否则套件中的所有测试都可以访问此数据。这可能是数据以及将传递给所有测试的模块帮助程序。

  • 外部插件加载conftest.py用于导入外部插件或模块。通过定义以下全局变量,pytest将加载模块并使它可用于其测试。插件通常是在项目或测试中可能需要的其他模块中定义的文件。您还可以按照此处的说明加载一组预定义的插件。

    pytest_plugins = "someapp.someplugin"

  • 挂钩:您可以指定挂钩(例如设置和拆卸方法)以及更多内容来改善测试。有关一组可用的挂钩,请阅读此处。例:

    def pytest_runtest_setup(item):
         """ called before ``pytest_runtest_call(item). """
         #do some stuff`
  • 测试根路径:这是一个隐藏功能。通过conftest.py在根路径中进行定义,pytest无需指定即可识别应用程序模块PYTHONPATH。py.test在后台sys.path通过包含从根路径找到的所有子模块来修改您的文件。

我可以有多个conftest.py文件吗?

是的,您可以,如果测试结构有些复杂,强烈建议您这样做。conftest.py文件具有目录范围。因此,创建有针对性的装置和助手是一个好习惯。

我什么时候要这么做?示例将不胜感激。

几种情况可能适合:

为一组特定的测试创建一组工具或挂钩

root / mod / conftest.py

def pytest_runtest_setup(item):
    print("I am mod")
    #do some stuff


test root/mod2/test.py will NOT produce "I am mod"

加载一组夹具用于某些测试,但不用于其他测试。

root / mod / conftest.py

@pytest.fixture()
def fixture():
    return "some stuff"

root / mod2 / conftest.py

@pytest.fixture()
def fixture():
    return "some other stuff"

root / mod2 / test.py

def test(fixture):
    print(fixture)

将打印“其他一些东西”。

覆盖从根继承的钩子conftest.py

root / mod / conftest.py

def pytest_runtest_setup(item):
    print("I am mod")
    #do some stuff

root / conftest.py

def pytest_runtest_setup(item):
    print("I am root")
    #do some stuff

通过在内部运行任何测试root/mod,仅打印“ I am mod”。

您可以conftest.py 在此处了解更多信息。

编辑:

如果我需要从不同模块中的多个测试中调用普通的辅助函数,该怎么办-如果将它们放在conftest.py中,它们将对我可用吗?还是我应该将它们放在helpers.py模块中,然后在测试模块中导入并使用它?

您可以conftest.py用来定义您的助手。但是,您应该遵循常规做法。辅助工具至少可以在中用作固定装置pytest。例如,在我的测试中,我有一个模拟的redis帮助器,可以通过这种方式将其注入到我的测试中。

根目录/helper/redis/redis.py

@pytest.fixture
def mock_redis():
    return MockRedis()

根/测试/材料/ conftest.py

pytest_plugin="helper.redis.redis"

根/测试/材料/ test.py

def test(mock_redis):
    print(mock_redis.get('stuff'))

这将是一个测试模块,您可以自由地将其导入测试中。注意,您可能会命名redis.pyconftest.py好像您的模块redis包含更多测试一样。但是,由于模棱两可,不鼓励这种做法。

如果要使用conftest.py,只需将该帮助程序放在根目录中conftest.py,并在需要时将其注入。

root / tests / conftest.py

@pytest.fixture
def mock_redis():
    return MockRedis()

根/测试/材料/ test.py

def test(mock_redis):
    print(mock_redis.get(stuff))

您可以做的另一件事是编写一个可安装的插件。在那种情况下,您的助手可以写在任何地方,但是需要定义一个要安装在您的和其他潜在测试框架中的入口点。看到这个

如果您不想使用固定装置,则当然可以定义一个简单的帮助器,并在需要的地方使用普通的旧导入。

root /测试/helper/redis.py

class MockRedis():
    # stuff

根/测试/材料/ test.py

from helper.redis import MockRedis

def test():
    print(MockRedis().get(stuff))

但是,由于模块不在测试的子文件夹中,因此此处可能存在路径问题。您应该能够克服这(未测试)通过添加__init__.py到您的帮助

root / tests / helper / __ init__.py

from .redis import MockRedis

或者只是将helper模块添加到您的中PYTHONPATH

Is this the correct use of conftest.py?

Yes it is. Fixtures are a potential and common use of conftest.py. The fixtures that you will define will be shared among all tests in your test suite. However, defining fixtures in the root conftest.py might be useless and it would slow down testing if such fixtures are not used by all tests.

Does it have other uses?

Yes it does.

  • Fixtures: Define fixtures for static data used by tests. This data can be accessed by all tests in the suite unless specified otherwise. This could be data as well as helpers of modules which will be passed to all tests.

  • External plugin loading: conftest.py is used to import external plugins or modules. By defining the following global variable, pytest will load the module and make it available for its test. Plugins are generally files defined in your project or other modules which might be needed in your tests. You can also load a set of predefined plugins as explained here.

    pytest_plugins = "someapp.someplugin"

  • Hooks: You can specify hooks such as setup and teardown methods and much more to improve your tests. For a set of available hooks, read here. Example:

    def pytest_runtest_setup(item):
         """ called before ``pytest_runtest_call(item). """
         #do some stuff`
    
  • Test root path: This is a bit of a hidden feature. By defining conftest.py in your root path, you will have pytest recognizing your application modules without specifying PYTHONPATH. In the background, py.test modifies your sys.path by including all submodules which are found from the root path.

Can I have more than one conftest.py file?

Yes you can and it is strongly recommended if your test structure is somewhat complex. conftest.py files have directory scope. Therefore, creating targeted fixtures and helpers is good practice.

When would I want to do that? Examples will be appreciated.

Several cases could fit:

Creating a set of tools or hooks for a particular group of tests.

root/mod/conftest.py

def pytest_runtest_setup(item):
    print("I am mod")
    #do some stuff


test root/mod2/test.py will NOT produce "I am mod"

Loading a set of fixtures for some tests but not for others.

root/mod/conftest.py

@pytest.fixture()
def fixture():
    return "some stuff"

root/mod2/conftest.py

@pytest.fixture()
def fixture():
    return "some other stuff"

root/mod2/test.py

def test(fixture):
    print(fixture)

Will print “some other stuff”.

Overriding hooks inherited from the root conftest.py.

root/mod/conftest.py

def pytest_runtest_setup(item):
    print("I am mod")
    #do some stuff

root/conftest.py

def pytest_runtest_setup(item):
    print("I am root")
    #do some stuff

By running any test inside root/mod, only “I am mod” is printed.

You can read more about conftest.py here.

EDIT:

What if I need plain-old helper functions to be called from a number of tests in different modules – will they be available to me if I put them in a conftest.py? Or should I simply put them in a helpers.py module and import and use it in my test modules?

You can use conftest.py to define your helpers. However, you should follow common practice. Helpers can be used as fixtures at least in pytest. For example in my tests I have a mock redis helper which I inject into my tests this way.

root/helper/redis/redis.py

@pytest.fixture
def mock_redis():
    return MockRedis()

root/tests/stuff/conftest.py

pytest_plugin="helper.redis.redis"

root/tests/stuff/test.py

def test(mock_redis):
    print(mock_redis.get('stuff'))

This will be a test module that you can freely import in your tests. NOTE that you could potentially name redis.py as conftest.py if your module redis contains more tests. However, that practice is discouraged because of ambiguity.

If you want to use conftest.py, you can simply put that helper in your root conftest.py and inject it when needed.

root/tests/conftest.py

@pytest.fixture
def mock_redis():
    return MockRedis()

root/tests/stuff/test.py

def test(mock_redis):
    print(mock_redis.get(stuff))

Another thing you can do is to write an installable plugin. In that case your helper can be written anywhere but it needs to define an entry point to be installed in your and other potential test frameworks. See this.

If you don’t want to use fixtures, you could of course define a simple helper and just use the plain old import wherever it is needed.

root/tests/helper/redis.py

class MockRedis():
    # stuff

root/tests/stuff/test.py

from helper.redis import MockRedis

def test():
    print(MockRedis().get(stuff))

However, here you might have problems with the path since the module is not in a child folder of the test. You should be able to overcome this (not tested) by adding an __init__.py to your helper

root/tests/helper/__init__.py

from .redis import MockRedis

Or simply adding the helper module to your PYTHONPATH.


回答 1

广义上,conftest.py是一个本地的按目录的插件。在这里,您可以定义目录特定的挂钩和固定装置。在我的情况下,有一个根目录,其中包含项目特定的测试目录。某些常见的魔术位于“ root” conftest.py中。特定于项目-在自己的项目中。除非将夹具广泛使用(在这种情况下,我宁愿直接在测试文件中定义它们),否则在conftest.py中存储夹具时不会发现任何不良情况。

In a wide meaning conftest.py is a local per-directory plugin. Here you define directory-specific hooks and fixtures. In my case a have a root directory containing project specific tests directories. Some common magic is stationed in ‘root’ conftest.py. Project specific – in their own ones. Can’t see anything bad in storing fixtures in conftest.py unless they are not used widely (In that case I prefer to define them in test files directly)


回答 2

我使用该conftest.py文件来定义要注入测试中的灯具,这是否正确使用conftest.py

是的,通常使用固定装置来准备好用于多个测试的数据。

它还有其他用途吗?

是的,灯具是pytest在实际测试功能之前(有时是之后)运行的功能。灯具中的代码可以执行您想要的任何操作。例如,夹具可以用于获取要进行测试的数据集,或者夹具也可以用于在运行测试之前使系统进入已知状态。

我可以有多个conftest.py文件吗?我什么时候要这么做?

首先,可以将灯具放入单独的测试文件中。但是,要在多个测试文件之间共享灯具,您需要在conftest.py所有测试的中央位置使用文件。灯具可以通过任何测试共享。如果您希望夹具仅由该文件中的测试使用,则可以将它们放在单独的测试文件中。

其次,可以,您可以conftest.py在top tests目录的子目录中拥有其他文件。如果这样做,这些较低级别conftest.py文件中定义的固定装置将可用于该目录和子目录中的测试。

最后,将固定装置放在conftest.py文件中的测试根目录下将使它们在所有测试文件中均可用。

I use the conftest.py file to define the fixtures that I inject into my tests, is this the correct use of conftest.py?

Yes, a fixture is usually used to get data ready for multiple tests.

Does it have other uses?

Yes, a fixture is a function that is run by pytest before, and sometimes after, the actual test functions. The code in the fixture can do whatever you want it to. For instance, a fixture can be used to get a data set for the tests to work on, or a fixture can also be used to get a system into a known state before running a test.

Can I have more than one conftest.py file? When would I want to do that?

First, it is possible to put fixtures into individual test files. However, to share fixtures among multiple test files, you need to use a conftest.py file somewhere centrally located for all of the tests. Fixtures can be shared by any test. They can be put in individual test files if you want the fixture to only be used by tests in that file.

Second, yes, you can have other conftest.py files in subdirectories of the top tests directory. If you do, fixtures defined in these lower-level conftest.py files will be available to tests in that directory and subdirectories.

Finally, putting fixtures in the conftest.py file at the test root will make them available in all test files.


如何在目录中运行所有Python单元测试?

问题:如何在目录中运行所有Python单元测试?

我有一个目录,其中包含我的Python单元测试。每个单元测试模块的形式为test _ *。py。我正在尝试制作一个名为all_test.py的文件,您猜对了,它将以上述测试形式运行所有文件并返回结果。到目前为止,我已经尝试了两种方法。都失败了。我将展示这两种方法,并希望那里的人知道如何正确地正确执行此操作。

对于我的第一次英勇尝试,我想:“如果我只是将所有测试模块导入文件中,然后调用此unittest.main()doodad,它将起作用,对吗?” 好吧,原来我错了。

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]

if __name__ == "__main__":
     unittest.main()

这没有用,我得到的结果是:

$ python all_test.py 

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

对于第二次尝试,我还是可以,也许我会尝试以“手动”方式进行整个测试。所以我尝试在下面这样做:

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
[__import__(str) for str in module_strings]
suites = [unittest.TestLoader().loadTestsFromName(str) for str in module_strings]
[testSuite.addTest(suite) for suite in suites]
print testSuite 

result = unittest.TestResult()
testSuite.run(result)
print result

#Ok, at this point I have a result
#How do I display it as the normal unit test command line output?
if __name__ == "__main__":
    unittest.main()

这也没有用,但是似乎太接近了!

$ python all_test.py 
<unittest.TestSuite tests=[<unittest.TestSuite tests=[<unittest.TestSuite tests=[<test_main.TestMain testMethod=test_respondes_to_get>]>]>]>
<unittest.TestResult run=1 errors=0 failures=0>

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

我似乎有一套类似的套件,可以执行结果。我有点担心它说我只有一个事实run=1,似乎应该是这样run=2,但这是进步。但是如何传递结果并将其显示给main?还是我基本上如何使它工作,以便我可以运行该文件,然后运行此目录中的所有单元测试?

I have a directory that contains my Python unit tests. Each unit test module is of the form test_*.py. I am attempting to make a file called all_test.py that will, you guessed it, run all files in the aforementioned test form and return the result. I have tried two methods so far; both have failed. I will show the two methods, and I hope someone out there knows how to actually do this correctly.

For my first valiant attempt, I thought “If I just import all my testing modules in the file, and then call this unittest.main() doodad, it will work, right?” Well, turns out I was wrong.

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]

if __name__ == "__main__":
     unittest.main()

This did not work, the result I got was:

$ python all_test.py 

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

For my second try, I though, ok, maybe I will try to do this whole testing thing in a more “manual” fashion. So I attempted to do that below:

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
[__import__(str) for str in module_strings]
suites = [unittest.TestLoader().loadTestsFromName(str) for str in module_strings]
[testSuite.addTest(suite) for suite in suites]
print testSuite 

result = unittest.TestResult()
testSuite.run(result)
print result

#Ok, at this point I have a result
#How do I display it as the normal unit test command line output?
if __name__ == "__main__":
    unittest.main()

This also did not work, but it seems so close!

$ python all_test.py 
<unittest.TestSuite tests=[<unittest.TestSuite tests=[<unittest.TestSuite tests=[<test_main.TestMain testMethod=test_respondes_to_get>]>]>]>
<unittest.TestResult run=1 errors=0 failures=0>

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

I seem to have a suite of some sort, and I can execute the result. I am a little concerned about the fact that it says I have only run=1, seems like that should be run=2, but it is progress. But how do I pass and display the result to main? Or how do I basically get it working so I can just run this file, and in doing so, run all the unit tests in this directory?


回答 0

使用Python 2.7及更高版本,您无需编写新代码或使用第三方工具即可完成此操作。内置了通过命令行执行递归测试的功能。__init__.py在您的测试目录中放置:

python -m unittest discover <test_directory>
# or
python -m unittest discover -s <directory> -p '*_test.py'

您可以在python 2.7python 3.x unittest文档中阅读更多内容。

With Python 2.7 and higher you don’t have to write new code or use third-party tools to do this; recursive test execution via the command line is built-in. Put an __init__.py in your test directory and:

python -m unittest discover <test_directory>
# or
python -m unittest discover -s <directory> -p '*_test.py'

You can read more in the python 2.7 or python 3.x unittest documentation.


回答 1

您可以使用可以为您完成此任务的测试运行程序。 例如鼻子很好。运行时,它将在当前树中找到测试并运行它们。

更新:

这是我前鼻时期的一些代码。您可能不希望使用模块名称的明确列表,但是其余的名称可能对您有用。

testmodules = [
    'cogapp.test_makefiles',
    'cogapp.test_whiteutils',
    'cogapp.test_cogapp',
    ]

suite = unittest.TestSuite()

for t in testmodules:
    try:
        # If the module defines a suite() function, call it to get the suite.
        mod = __import__(t, globals(), locals(), ['suite'])
        suitefn = getattr(mod, 'suite')
        suite.addTest(suitefn())
    except (ImportError, AttributeError):
        # else, just load all the test cases from the module.
        suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))

unittest.TextTestRunner().run(suite)

You could use a test runner that would do this for you. nose is very good for example. When run, it will find tests in the current tree and run them.

Updated:

Here’s some code from my pre-nose days. You probably don’t want the explicit list of module names, but maybe the rest will be useful to you.

testmodules = [
    'cogapp.test_makefiles',
    'cogapp.test_whiteutils',
    'cogapp.test_cogapp',
    ]

suite = unittest.TestSuite()

for t in testmodules:
    try:
        # If the module defines a suite() function, call it to get the suite.
        mod = __import__(t, globals(), locals(), ['suite'])
        suitefn = getattr(mod, 'suite')
        suite.addTest(suitefn())
    except (ImportError, AttributeError):
        # else, just load all the test cases from the module.
        suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))

unittest.TextTestRunner().run(suite)

回答 2

在python 3中,如果您使用的是unittest.TestCase

  • 您的目录中必须有一个空(或其他)__init__.py文件test必须命名为test/
  • 您的测试文件test/与模式匹配test_*.py。它们可以位于下方的子目录中test/,并且这些子目录可以命名为任何东西。

然后,您可以使用以下命令运行所有测试:

python -m unittest

做完了!解决方案少于100行。希望其他python初学者可以通过查找此方法节省时间。

In python 3, if you’re using unittest.TestCase:

  • You must have an empty (or otherwise) __init__.py file in your test directory (must be named test/)
  • Your test files inside test/ match the pattern test_*.py. They can be inside a subdirectory under test/, and those subdirs can be named as anything.

Then, you can run all the tests with:

python -m unittest

Done! A solution less than 100 lines. Hopefully another python beginner saves time by finding this.


回答 3

现在,可以直接从unittest:unittest.TestLoader.discover进行此操作

import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)

runner = unittest.TextTestRunner()
runner.run(suite)

This is now possible directly from unittest: unittest.TestLoader.discover.

import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)

runner = unittest.TextTestRunner()
runner.run(suite)

回答 4

通过研究上面的代码(特别是使用TextTestRunnerdefaultTestLoader),我可以很接近了。最终,我通过仅将所有测试套件传递给单个套件的构造函数,而不是“手动”添加它们来修复了我的代码,从而解决了其他问题。所以这是我的解决方案。

import glob
import unittest

test_files = glob.glob('test_*.py')
module_strings = [test_file[0:len(test_file)-3] for test_file in test_files]
suites = [unittest.defaultTestLoader.loadTestsFromName(test_file) for test_file in module_strings]
test_suite = unittest.TestSuite(suites)
test_runner = unittest.TextTestRunner().run(test_suite)

是的,使用鼻子可能比这样做更容易,但这不重要。

Well by studying the code above a bit (specifically using TextTestRunner and defaultTestLoader), I was able to get pretty close. Eventually I fixed my code by also just passing all test suites to a single suites constructor, rather than adding them “manually”, which fixed my other problems. So here is my solution.

import glob
import unittest

test_files = glob.glob('test_*.py')
module_strings = [test_file[0:len(test_file)-3] for test_file in test_files]
suites = [unittest.defaultTestLoader.loadTestsFromName(test_file) for test_file in module_strings]
test_suite = unittest.TestSuite(suites)
test_runner = unittest.TextTestRunner().run(test_suite)

Yeah, it is probably easier to just use nose than to do this, but that is besides the point.


回答 5

如果要运行各种测试用例类中的所有测试,并且很乐意明确指定它们,则可以这样做:

from unittest import TestLoader, TextTestRunner, TestSuite
from uclid.test.test_symbols import TestSymbols
from uclid.test.test_patterns import TestPatterns

if __name__ == "__main__":

    loader = TestLoader()
    tests = [
        loader.loadTestsFromTestCase(test)
        for test in (TestSymbols, TestPatterns)
    ]
    suite = TestSuite(tests)

    runner = TextTestRunner(verbosity=2)
    runner.run(suite)

uclid我的项目在哪里,TestSymbols并且TestPatterns是的子类TestCase

If you want to run all the tests from various test case classes and you’re happy to specify them explicitly then you can do it like this:

from unittest import TestLoader, TextTestRunner, TestSuite
from uclid.test.test_symbols import TestSymbols
from uclid.test.test_patterns import TestPatterns

if __name__ == "__main__":

    loader = TestLoader()
    tests = [
        loader.loadTestsFromTestCase(test)
        for test in (TestSymbols, TestPatterns)
    ]
    suite = TestSuite(tests)

    runner = TextTestRunner(verbosity=2)
    runner.run(suite)

where uclid is my project and TestSymbols and TestPatterns are subclasses of TestCase.


回答 6

我使用了该discover方法和的重载load_tests来在(最少,我认为)数字行中实现此结果:

def load_tests(loader, tests, pattern):
''' Discover and load all unit tests in all files named ``*_test.py`` in ``./src/``
'''
    suite = TestSuite()
    for all_test_suite in unittest.defaultTestLoader.discover('src', pattern='*_tests.py'):
        for test_suite in all_test_suite:
            suite.addTests(test_suite)
    return suite

if __name__ == '__main__':
    unittest.main()

执行击掌,像

Ran 27 tests in 0.187s
OK

I have used the discover method and an overloading of load_tests to achieve this result in a (minimal, I think) number lines of code:

def load_tests(loader, tests, pattern):
''' Discover and load all unit tests in all files named ``*_test.py`` in ``./src/``
'''
    suite = TestSuite()
    for all_test_suite in unittest.defaultTestLoader.discover('src', pattern='*_tests.py'):
        for test_suite in all_test_suite:
            suite.addTests(test_suite)
    return suite

if __name__ == '__main__':
    unittest.main()

Execution on fives something like

Ran 27 tests in 0.187s
OK

回答 7

我尝试了各种方法,但似乎都存在缺陷,或者我必须编写一些代码,这很烦人。但是在Linux下,有一种简便的方法,就是简单地通过某种模式找到每个测试,然后逐个调用它们。

find . -name 'Test*py' -exec python '{}' \;

最重要的是,它肯定有效。

I tried various approaches but all seem flawed or I have to makeup some code, that’s annoying. But there’s a convinient way under linux, that is simply to find every test through certain pattern and then invoke them one by one.

find . -name 'Test*py' -exec python '{}' \;

and most importantly, it definitely works.


回答 8

对于打包的库或应用程序,您不想这样做。setuptools 会为你做

要使用此命令,您的项目的测试必须unittest通过函数,TestCase类或方法或包含TestCase类的模块或包来包装在测试套件中。如果命名套件是一个模块,并且该模块具有一个additional_tests()功能,则将其调用,并将结果(必须为unittest.TestSuite)添加到要运行的测试中。如果命名套件是一个包,则将所有子模块和子包递归地添加到整个测试套件中

只需告诉它您的根测试包在哪里,例如:

setup(
    # ...
    test_suite = 'somepkg.test'
)

然后跑 python setup.py test

除非您避免在测试套件中进行相对导入,否则在Python 3中基于文件的发现可能会出现问题,因为会discover使用文件导入。即使它支持optional top_level_dir,但是我还是有一些无限递归错误。因此,针对非打包代码的简单解决方案是将以下内容放入__init__.py测试包中(请参阅load_tests协议)。

import unittest

from . import foo, bar


def load_tests(loader, tests, pattern):
    suite = unittest.TestSuite()
    suite.addTests(loader.loadTestsFromModule(foo))
    suite.addTests(loader.loadTestsFromModule(bar))

    return suite

In case of a packaged library or application, you don’t want to do it. setuptools will do it for you.

To use this command, your project’s tests must be wrapped in a unittest test suite by either a function, a TestCase class or method, or a module or package containing TestCase classes. If the named suite is a module, and the module has an additional_tests() function, it is called and the result (which must be a unittest.TestSuite) is added to the tests to be run. If the named suite is a package, any submodules and subpackages are recursively added to the overall test suite.

Just tell it where your root test package is, like:

setup(
    # ...
    test_suite = 'somepkg.test'
)

And run python setup.py test.

File-based discovery may be problematic in Python 3, unless you avoid relative imports in your test suite, because discover uses file import. Even though it supports optional top_level_dir, but I had some infinite recursion errors. So a simple solution for a non-packaged code is to put the following in __init__.py of your test package (see load_tests Protocol).

import unittest

from . import foo, bar


def load_tests(loader, tests, pattern):
    suite = unittest.TestSuite()
    suite.addTests(loader.loadTestsFromModule(foo))
    suite.addTests(loader.loadTestsFromModule(bar))

    return suite

回答 9

我使用PyDev / LiClipse,但还没有真正弄清楚如何从GUI一次运行所有测试。(编辑:右键单击根测试文件夹,然后选择Run as -> Python unit-test

这是我当前的解决方法:

import unittest

def load_tests(loader, tests, pattern):
    return loader.discover('.')

if __name__ == '__main__':
    unittest.main()

我把这段代码放在一个名为 all测试目录中。如果我以LiClipse的单元测试形式运行此模块,则将运行所有测试。如果我只要求重复特定或失败的测试,则仅运行那些测试。它也不会干扰我的命令行测试运行程序(noestests)-被忽略。

您可能需要discover根据项目设置将参数更改为。

I use PyDev/LiClipse and haven’t really figured out how to run all tests at once from the GUI. (edit: you right click the root test folder and choose Run as -> Python unit-test

This is my current workaround:

import unittest

def load_tests(loader, tests, pattern):
    return loader.discover('.')

if __name__ == '__main__':
    unittest.main()

I put this code in a module called all in my test directory. If I run this module as a unittest from LiClipse then all tests are run. If I ask to only repeat specific or failed tests then only those tests are run. It doesn’t interfere with my commandline test runner either (nosetests) — it’s ignored.

You may need to change the arguments to discover based on your project setup.


回答 10

根据Stephen Cagle的回答,我添加了对嵌套测试模块的支持。

import fnmatch
import os
import unittest

def all_test_modules(root_dir, pattern):
    test_file_names = all_files_in(root_dir, pattern)
    return [path_to_module(str) for str in test_file_names]

def all_files_in(root_dir, pattern):
    matches = []

    for root, dirnames, filenames in os.walk(root_dir):
        for filename in fnmatch.filter(filenames, pattern):
            matches.append(os.path.join(root, filename))

    return matches

def path_to_module(py_file):
    return strip_leading_dots( \
        replace_slash_by_dot(  \
            strip_extension(py_file)))

def strip_extension(py_file):
    return py_file[0:len(py_file) - len('.py')]

def replace_slash_by_dot(str):
    return str.replace('\\', '.').replace('/', '.')

def strip_leading_dots(str):
    while str.startswith('.'):
       str = str[1:len(str)]
    return str

module_names = all_test_modules('.', '*Tests.py')
suites = [unittest.defaultTestLoader.loadTestsFromName(mname) for mname 
    in module_names]

testSuite = unittest.TestSuite(suites)
runner = unittest.TextTestRunner(verbosity=1)
runner.run(testSuite)

该代码搜索的所有子目录以.查找*Tests.py文件,然后将其加载。它期望每个*Tests.py都包含一个类*Tests(unittest.TestCase),该类依次加载并一个接一个地执行。

这适用于目录/模块的任意深度嵌套,但是之间的每个目录__init__.py至少需要包含一个空文件。这允许测试通过用点替换斜杠(或反斜杠)来加载嵌套模块(请参阅参考资料replace_slash_by_dot)。

Based on the answer of Stephen Cagle I added support for nested test modules.

import fnmatch
import os
import unittest

def all_test_modules(root_dir, pattern):
    test_file_names = all_files_in(root_dir, pattern)
    return [path_to_module(str) for str in test_file_names]

def all_files_in(root_dir, pattern):
    matches = []

    for root, dirnames, filenames in os.walk(root_dir):
        for filename in fnmatch.filter(filenames, pattern):
            matches.append(os.path.join(root, filename))

    return matches

def path_to_module(py_file):
    return strip_leading_dots( \
        replace_slash_by_dot(  \
            strip_extension(py_file)))

def strip_extension(py_file):
    return py_file[0:len(py_file) - len('.py')]

def replace_slash_by_dot(str):
    return str.replace('\\', '.').replace('/', '.')

def strip_leading_dots(str):
    while str.startswith('.'):
       str = str[1:len(str)]
    return str

module_names = all_test_modules('.', '*Tests.py')
suites = [unittest.defaultTestLoader.loadTestsFromName(mname) for mname 
    in module_names]

testSuite = unittest.TestSuite(suites)
runner = unittest.TextTestRunner(verbosity=1)
runner.run(testSuite)

The code searches all subdirectories of . for *Tests.py files which are then loaded. It expects each *Tests.py to contain a single class *Tests(unittest.TestCase) which is loaded in turn and executed one after another.

This works with arbitrary deep nesting of directories/modules, but each directory in between needs to contain an empty __init__.py file at least. This allows the test to load the nested modules by replacing slashes (or backslashes) by dots (see replace_slash_by_dot).


回答 11

这是一个老问题,但现在(2019年)对我有用的是:

python -m unittest *_test.py

我所有的测试文件都与源文件位于同一文件夹中,并以结尾_test

This is an old question, but what worked for me now (in 2019) is:

python -m unittest *_test.py

All my test files are in the same folder as the source files and they end with _test.


回答 12

由于测试发现似乎是一个完整的主题,因此存在一些专用的框架来测试发现:

在这里更多阅读:https : //wiki.python.org/moin/PythonTestingToolsTaxonomy

Because Test discovery seems to be a complete subject, there is some dedicated framework to test discovery :

More reading here : https://wiki.python.org/moin/PythonTestingToolsTaxonomy


回答 13

无论您位于哪个工作目录中,此BASH脚本都将从文件系统中的任何位置执行python unittest测试目录:其工作目录始终test位于该目录所在的位置。

所有测试,独立$ PWD

unittest Python模块对您的当前目录敏感,除非您告诉它当前位置(使用discover -s选项)。

当位于./src./example工作目录中并且需要快速的整体单元测试时,这很有用:

#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

python -m unittest discover -s "$readlink"/test -v

所选测试,独立$ PWD

我将此实用程序文件命名为:runone.py并按以下方式使用它:

runone.py <test-python-filename-minus-dot-py-fileextension>
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

(cd "$dirname"/test; python -m unittest $1)

test/__init__.py在生产过程中不需要文件来负担您的程序包/内存开销。

This BASH script will execute the python unittest test directory from ANYWHERE in the file system, no matter what working directory you are in: its working directory always be where that test directory is located.

ALL TESTS, independent $PWD

unittest Python module is sensitive to your current directory, unless you tell it where (using discover -s option).

This is useful when staying in the ./src or ./example working directory and you need a quick overall unit test:

#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

python -m unittest discover -s "$readlink"/test -v

SELECTED TESTS, independent $PWD

I name this utility file: runone.py and use it like this:

runone.py <test-python-filename-minus-dot-py-fileextension>
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

(cd "$dirname"/test; python -m unittest $1)

No need for a test/__init__.py file to burden your package/memory-overhead during production.


回答 14

这是我创建包装器以从命令行运行测试的方法:

#!/usr/bin/env python3
import os, sys, unittest, argparse, inspect, logging

if __name__ == '__main__':
    # Parse arguments.
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument("-?", "--help",     action="help",                        help="show this help message and exit" )
    parser.add_argument("-v", "--verbose",  action="store_true", dest="verbose",  help="increase output verbosity" )
    parser.add_argument("-d", "--debug",    action="store_true", dest="debug",    help="show debug messages" )
    parser.add_argument("-h", "--host",     action="store",      dest="host",     help="Destination host" )
    parser.add_argument("-b", "--browser",  action="store",      dest="browser",  help="Browser driver.", choices=["Firefox", "Chrome", "IE", "Opera", "PhantomJS"] )
    parser.add_argument("-r", "--reports-dir", action="store",   dest="dir",      help="Directory to save screenshots.", default="reports")
    parser.add_argument('files', nargs='*')
    args = parser.parse_args()

    # Load files from the arguments.
    for filename in args.files:
        exec(open(filename).read())

    # See: http://codereview.stackexchange.com/q/88655/15346
    def make_suite(tc_class):
        testloader = unittest.TestLoader()
        testnames = testloader.getTestCaseNames(tc_class)
        suite = unittest.TestSuite()
        for name in testnames:
            suite.addTest(tc_class(name, cargs=args))
        return suite

    # Add all tests.
    alltests = unittest.TestSuite()
    for name, obj in inspect.getmembers(sys.modules[__name__]):
        if inspect.isclass(obj) and name.startswith("FooTest"):
            alltests.addTest(make_suite(obj))

    # Set-up logger
    verbose = bool(os.environ.get('VERBOSE', args.verbose))
    debug   = bool(os.environ.get('DEBUG', args.debug))
    if verbose or debug:
        logging.basicConfig( stream=sys.stdout )
        root = logging.getLogger()
        root.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
        root.addHandler(ch)
    else:
        logging.basicConfig(stream=sys.stderr)

    # Run tests.
    result = unittest.TextTestRunner(verbosity=2).run(alltests)
    sys.exit(not result.wasSuccessful())

为了简单起见,请原谅我的非PEP8编码标准。

然后,您可以为所有测试的通用组件创建BaseTest类,因此每个测试都将看起来像:

from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
    def test_foo(self):
        driver = self.driver
        driver.get(self.base_url + "/")

要运行,只需将测试指定为命令行参数的一部分,例如:

./run_tests.py -h http://example.com/ tests/**/*.py

Here is my approach by creating a wrapper to run tests from the command line:

#!/usr/bin/env python3
import os, sys, unittest, argparse, inspect, logging

if __name__ == '__main__':
    # Parse arguments.
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument("-?", "--help",     action="help",                        help="show this help message and exit" )
    parser.add_argument("-v", "--verbose",  action="store_true", dest="verbose",  help="increase output verbosity" )
    parser.add_argument("-d", "--debug",    action="store_true", dest="debug",    help="show debug messages" )
    parser.add_argument("-h", "--host",     action="store",      dest="host",     help="Destination host" )
    parser.add_argument("-b", "--browser",  action="store",      dest="browser",  help="Browser driver.", choices=["Firefox", "Chrome", "IE", "Opera", "PhantomJS"] )
    parser.add_argument("-r", "--reports-dir", action="store",   dest="dir",      help="Directory to save screenshots.", default="reports")
    parser.add_argument('files', nargs='*')
    args = parser.parse_args()

    # Load files from the arguments.
    for filename in args.files:
        exec(open(filename).read())

    # See: http://codereview.stackexchange.com/q/88655/15346
    def make_suite(tc_class):
        testloader = unittest.TestLoader()
        testnames = testloader.getTestCaseNames(tc_class)
        suite = unittest.TestSuite()
        for name in testnames:
            suite.addTest(tc_class(name, cargs=args))
        return suite

    # Add all tests.
    alltests = unittest.TestSuite()
    for name, obj in inspect.getmembers(sys.modules[__name__]):
        if inspect.isclass(obj) and name.startswith("FooTest"):
            alltests.addTest(make_suite(obj))

    # Set-up logger
    verbose = bool(os.environ.get('VERBOSE', args.verbose))
    debug   = bool(os.environ.get('DEBUG', args.debug))
    if verbose or debug:
        logging.basicConfig( stream=sys.stdout )
        root = logging.getLogger()
        root.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
        root.addHandler(ch)
    else:
        logging.basicConfig(stream=sys.stderr)

    # Run tests.
    result = unittest.TextTestRunner(verbosity=2).run(alltests)
    sys.exit(not result.wasSuccessful())

For sake of simplicity, please excuse my non-PEP8 coding standards.

Then you can create BaseTest class for common components for all your tests, so each of your test would simply look like:

from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
    def test_foo(self):
        driver = self.driver
        driver.get(self.base_url + "/")

To run, you simply specifying tests as part of the command line arguments, e.g.:

./run_tests.py -h http://example.com/ tests/**/*.py

如何正确断言pytest中引发了异常?

问题:如何正确断言pytest中引发了异常?

码:

# coding=utf-8
import pytest


def whatever():
    return 9/0

def test_whatever():
    try:
        whatever()
    except ZeroDivisionError as exc:
        pytest.fail(exc, pytrace=True)

输出:

================================ test session starts =================================
platform linux2 -- Python 2.7.3 -- py-1.4.20 -- pytest-2.5.2
plugins: django, cov
collected 1 items 

pytest_test.py F

====================================== FAILURES ======================================
___________________________________ test_whatever ____________________________________

    def test_whatever():
        try:
            whatever()
        except ZeroDivisionError as exc:
>           pytest.fail(exc, pytrace=True)
E           Failed: integer division or modulo by zero

pytest_test.py:12: Failed
============================== 1 failed in 1.16 seconds ==============================

如何使pytest打印回溯,所以我会看到在whatever函数中引发异常的地方?

Code:

# coding=utf-8
import pytest


def whatever():
    return 9/0

def test_whatever():
    try:
        whatever()
    except ZeroDivisionError as exc:
        pytest.fail(exc, pytrace=True)

Output:

================================ test session starts =================================
platform linux2 -- Python 2.7.3 -- py-1.4.20 -- pytest-2.5.2
plugins: django, cov
collected 1 items 

pytest_test.py F

====================================== FAILURES ======================================
___________________________________ test_whatever ____________________________________

    def test_whatever():
        try:
            whatever()
        except ZeroDivisionError as exc:
>           pytest.fail(exc, pytrace=True)
E           Failed: integer division or modulo by zero

pytest_test.py:12: Failed
============================== 1 failed in 1.16 seconds ==============================

How to make pytest print traceback, so I would see where in the whatever function an exception was raised?


回答 0

pytest.raises(Exception) 是您所需要的。

import pytest

def test_passes():
    with pytest.raises(Exception) as e_info:
        x = 1 / 0

def test_passes_without_info():
    with pytest.raises(Exception):
        x = 1 / 0

def test_fails():
    with pytest.raises(Exception) as e_info:
        x = 1 / 1

def test_fails_without_info():
    with pytest.raises(Exception):
        x = 1 / 1

# Don't do this. Assertions are caught as exceptions.
def test_passes_but_should_not():
    try:
        x = 1 / 1
        assert False
    except Exception:
        assert True

# Even if the appropriate exception is caught, it is bad style,
# because the test result is less informative
# than it would be with pytest.raises(e)
# (it just says pass or fail.)

def test_passes_but_bad_style():
    try:
        x = 1 / 0
        assert False
    except ZeroDivisionError:
        assert True

def test_fails_but_bad_style():
    try:
        x = 1 / 1
        assert False
    except ZeroDivisionError:
        assert True

输出量

============================================================================================= test session starts ==============================================================================================
platform linux2 -- Python 2.7.6 -- py-1.4.26 -- pytest-2.6.4
collected 7 items 

test.py ..FF..F

=================================================================================================== FAILURES ===================================================================================================
__________________________________________________________________________________________________ test_fails __________________________________________________________________________________________________

    def test_fails():
        with pytest.raises(Exception) as e_info:
>           x = 1 / 1
E           Failed: DID NOT RAISE

test.py:13: Failed
___________________________________________________________________________________________ test_fails_without_info ____________________________________________________________________________________________

    def test_fails_without_info():
        with pytest.raises(Exception):
>           x = 1 / 1
E           Failed: DID NOT RAISE

test.py:17: Failed
___________________________________________________________________________________________ test_fails_but_bad_style ___________________________________________________________________________________________

    def test_fails_but_bad_style():
        try:
            x = 1 / 1
>           assert False
E           assert False

test.py:43: AssertionError
====================================================================================== 3 failed, 4 passed in 0.02 seconds ======================================================================================

请注意,e_info将保存异常对象,以便您可以从中提取详细信息。例如,如果要检查异常调用堆栈或内部的另一个嵌套异常。

pytest.raises(Exception) is what you need.

Code

import pytest

def test_passes():
    with pytest.raises(Exception) as e_info:
        x = 1 / 0

def test_passes_without_info():
    with pytest.raises(Exception):
        x = 1 / 0

def test_fails():
    with pytest.raises(Exception) as e_info:
        x = 1 / 1

def test_fails_without_info():
    with pytest.raises(Exception):
        x = 1 / 1

# Don't do this. Assertions are caught as exceptions.
def test_passes_but_should_not():
    try:
        x = 1 / 1
        assert False
    except Exception:
        assert True

# Even if the appropriate exception is caught, it is bad style,
# because the test result is less informative
# than it would be with pytest.raises(e)
# (it just says pass or fail.)

def test_passes_but_bad_style():
    try:
        x = 1 / 0
        assert False
    except ZeroDivisionError:
        assert True

def test_fails_but_bad_style():
    try:
        x = 1 / 1
        assert False
    except ZeroDivisionError:
        assert True

Output

============================================================================================= test session starts ==============================================================================================
platform linux2 -- Python 2.7.6 -- py-1.4.26 -- pytest-2.6.4
collected 7 items 

test.py ..FF..F

=================================================================================================== FAILURES ===================================================================================================
__________________________________________________________________________________________________ test_fails __________________________________________________________________________________________________

    def test_fails():
        with pytest.raises(Exception) as e_info:
>           x = 1 / 1
E           Failed: DID NOT RAISE

test.py:13: Failed
___________________________________________________________________________________________ test_fails_without_info ____________________________________________________________________________________________

    def test_fails_without_info():
        with pytest.raises(Exception):
>           x = 1 / 1
E           Failed: DID NOT RAISE

test.py:17: Failed
___________________________________________________________________________________________ test_fails_but_bad_style ___________________________________________________________________________________________

    def test_fails_but_bad_style():
        try:
            x = 1 / 1
>           assert False
E           assert False

test.py:43: AssertionError
====================================================================================== 3 failed, 4 passed in 0.02 seconds ======================================================================================

Note that e_info saves the exception object so you can extract details from it. For example, if you want to check the exception call stack or another nested exception inside.


回答 1

您的意思是这样的吗:

def test_raises():
    with pytest.raises(Exception) as execinfo:   
        raise Exception('some info')
    # these asserts are identical; you can use either one   
    assert execinfo.value.args[0] == 'some info'
    assert str(execinfo.value) == 'some info'

Do you mean something like this:

def test_raises():
    with pytest.raises(Exception) as execinfo:   
        raise Exception('some info')
    # these asserts are identical; you can use either one   
    assert execinfo.value.args[0] == 'some info'
    assert str(execinfo.value) == 'some info'

回答 2

有两种方法可以在pytest中处理此类情况:

  • 使用pytest.raises功能

  • 使用pytest.mark.xfail装饰器

用途pytest.raises

def whatever():
    return 9/0
def test_whatever():
    with pytest.raises(ZeroDivisionError):
        whatever()

用途pytest.mark.xfail

@pytest.mark.xfail(raises=ZeroDivisionError)
def test_whatever():
    whatever()

输出pytest.raises

============================= test session starts ============================
platform linux2 -- Python 2.7.10, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- 
/usr/local/python_2.7_10/bin/python
cachedir: .cache
rootdir: /home/user, inifile:
collected 1 item

test_fun.py::test_whatever PASSED


======================== 1 passed in 0.01 seconds =============================

pytest.xfail标记的输出:

============================= test session starts ============================
platform linux2 -- Python 2.7.10, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- 
/usr/local/python_2.7_10/bin/python
cachedir: .cache
rootdir: /home/user, inifile:
collected 1 item

test_fun.py::test_whatever xfail

======================== 1 xfailed in 0.03 seconds=============================

文档所述

使用pytest.raises很可能是更好的为你在哪里测试异常你自己的代码被刻意提高的情况下,而使用@pytest.mark.xfail具有校验功能可能是更好的东西,像记录未修正的错误(如测试描述什么是“应该”发生)或错误的依赖。

There are two ways to handle these kind of cases in pytest:

  • Using pytest.raises function

  • Using pytest.mark.xfail decorator

As the documentation says:

Using pytest.raises is likely to be better for cases where you are testing exceptions your own code is deliberately raising, whereas using @pytest.mark.xfail with a check function is probably better for something like documenting unfixed bugs (where the test describes what “should” happen) or bugs in dependencies.

Usage of pytest.raises:

def whatever():
    return 9/0
def test_whatever():
    with pytest.raises(ZeroDivisionError):
        whatever()

Usage of pytest.mark.xfail:

@pytest.mark.xfail(raises=ZeroDivisionError)
def test_whatever():
    whatever()

Output of pytest.raises:

============================= test session starts ============================
platform linux2 -- Python 2.7.10, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- 
/usr/local/python_2.7_10/bin/python
cachedir: .cache
rootdir: /home/user, inifile:
collected 1 item

test_fun.py::test_whatever PASSED


======================== 1 passed in 0.01 seconds =============================

Output of pytest.xfail marker:

============================= test session starts ============================
platform linux2 -- Python 2.7.10, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- 
/usr/local/python_2.7_10/bin/python
cachedir: .cache
rootdir: /home/user, inifile:
collected 1 item

test_fun.py::test_whatever xfail

======================== 1 xfailed in 0.03 seconds=============================

回答 3

你可以试试

def test_exception():
    with pytest.raises(Exception) as excinfo:   
        function_that_raises_exception()   
    assert str(excinfo.value) == 'some info' 

you can try

def test_exception():
    with pytest.raises(Exception) as excinfo:   
        function_that_raises_exception()   
    assert str(excinfo.value) == 'some info' 

回答 4

pytest不断发展,并且随着最近的一次不错的变化,现在可以同时测试

  • 异常类型(严格测试)
  • 错误消息(使用正则表达式进行严格检查或宽松检查)

文档中的两个示例:

with pytest.raises(ValueError, match='must be 0 or None'):
    raise ValueError('value must be 0 or None')
with pytest.raises(ValueError, match=r'must be \d+$'):
    raise ValueError('value must be 42')

我已经在许多项目中使用了这种方法,并且非常喜欢它。

pytest constantly evolves and with one of the nice changes in the recent past it is now possible to simultaneously test for

  • the exception type (strict test)
  • the error message (strict or loose check using a regular expression)

Two examples from the documentation:

with pytest.raises(ValueError, match='must be 0 or None'):
    raise ValueError('value must be 0 or None')
with pytest.raises(ValueError, match=r'must be \d+$'):
    raise ValueError('value must be 42')

I have been using that approach in a number of projects and like it very much.


回答 5

正确的方法正在使用,pytest.raises但是我在这里的评论中找到了有趣的替代方法,并希望将其保存给以后这个问题的读者:

try:
    thing_that_rasises_typeerror()
    assert False
except TypeError:
    assert True

Right way is using pytest.raises but I found interesting alternative way in comments here and want to save it for future readers of this question:

try:
    thing_that_rasises_typeerror()
    assert False
except TypeError:
    assert True

回答 6

此解决方案是我们正在使用的解决方案:

def test_date_invalidformat():
    """
    Test if input incorrect data will raises ValueError exception
    """
    date = "06/21/2018 00:00:00"
    with pytest.raises(ValueError):
        app.func(date) #my function to be tested

请参阅pytest,https: //docs.pytest.org/en/latest/reference.html#pytest-raises

This solution is what we are using:

def test_date_invalidformat():
    """
    Test if input incorrect data will raises ValueError exception
    """
    date = "06/21/2018 00:00:00"
    with pytest.raises(ValueError):
        app.func(date) #my function to be tested

Please refer to pytest, https://docs.pytest.org/en/latest/reference.html#pytest-raises


回答 7

更好的做法是使用继承unittest.TestCase并运行self.assertRaises的类。

例如:

import unittest


def whatever():
    return 9/0


class TestWhatEver(unittest.TestCase):

    def test_whatever():
        with self.assertRaises(ZeroDivisionError):
            whatever()

然后,您可以通过运行以下命令来执行它:

pytest -vs test_path

Better practice will be using a class that inherit unittest.TestCase and running self.assertRaises.

For example:

import unittest


def whatever():
    return 9/0


class TestWhatEver(unittest.TestCase):

    def test_whatever():
        with self.assertRaises(ZeroDivisionError):
            whatever()

Then you would execute it by running:

pytest -vs test_path

回答 8

您是否尝试删除“ pytrace = True”?

pytest.fail(exc, pytrace=True) # before
pytest.fail(exc) # after

您是否尝试过使用’–fulltrace’吗?

Have you tried to remove “pytrace=True” ?

pytest.fail(exc, pytrace=True) # before
pytest.fail(exc) # after

Have you tried to run with ‘–fulltrace’ ?


用Python编写单元测试:如何开始?[关闭]

问题:用Python编写单元测试:如何开始?[关闭]

我用Python完成了第一个适当的项目,现在的任务是为它编写测试。

由于这是我第一次做项目,所以这是我第一次为此编写测试。

问题是,我该如何开始?我真的一点儿都不知道。谁能指出我一些可以用来开始编写测试的文档/教程/链接/书(尤其是单元测试)

关于该主题的任何建议都将受到欢迎。

I completed my first proper project in Python and now my task is to write tests for it.

Since this is the first time I did a project, this is the first time I would be writing tests for it.

The question is, how do I start? I have absolutely no idea. Can anyone point me to some documentation/ tutorial/ link/ book that I can use to start with writing tests (and I guess unit testing in particular)

Any advice will be welcomed on this topic.


回答 0

如果您是第一次使用单元测试,那么最简单的学习方法通​​常是最好的。在此基础上,我建议使用py.test而不是默认unittest模块

考虑以下两个示例,它们具有相同的作用:

示例1(单元测试):

import unittest

class LearningCase(unittest.TestCase):
    def test_starting_out(self):
        self.assertEqual(1, 1)

def main():
    unittest.main()

if __name__ == "__main__":
    main()

示例2(pytest):

def test_starting_out():
    assert 1 == 1

假设两个文件都被命名test_unittesting.py,我们如何运行测试?

示例1(单元测试):

cd /path/to/dir/
python test_unittesting.py

示例2(pytest):

cd /path/to/dir/
py.test

If you’re brand new to using unittests, the simplest approach to learn is often the best. On that basis along I recommend using py.test rather than the default unittest module.

Consider these two examples, which do the same thing:

Example 1 (unittest):

import unittest

class LearningCase(unittest.TestCase):
    def test_starting_out(self):
        self.assertEqual(1, 1)

def main():
    unittest.main()

if __name__ == "__main__":
    main()

Example 2 (pytest):

def test_starting_out():
    assert 1 == 1

Assuming that both files are named test_unittesting.py, how do we run the tests?

Example 1 (unittest):

cd /path/to/dir/
python test_unittesting.py

Example 2 (pytest):

cd /path/to/dir/
py.test

回答 1

免费的Python书籍Dive Into Python中关于单元测试的,您可能会觉得很有用。

如果您遵循现代实践,则可能应该在编写项目时编写测试,而不要等到项目接近完成时再进行测试。

现在有点晚,但是现在您知道下一次了。:)

The free Python book Dive Into Python has a chapter on unit testing that you might find useful.

If you follow modern practices you should probably write the tests while you are writing your project, and not wait until your project is nearly finished.

Bit late now, but now you know for next time. :)


回答 2

在我看来,有三个很棒的python测试框架值得一试。
单元测试 -模块配备了所有的Python发行标准
鼻子 -可以运行单元测试的测试,而且具有更少的样板。
pytest-还运行单元测试,样板更少,报告更好,还有很多很棒的额外功能

为了对所有这些进行良好的比较,请阅读http://pythontesting.net/start-here的每个介绍。
也有关于灯具的扩展文章,还有更多。

There are, in my opinion, three great python testing frameworks that are good to check out.
unittest – module comes standard with all python distributions
nose – can run unittest tests, and has less boilerplate.
pytest – also runs unittest tests, has less boilerplate, better reporting, lots of cool extra features

To get a good comparison of all of these, read through the introductions to each at http://pythontesting.net/start-here.
There’s also extended articles on fixtures, and more there.


回答 3

的文档 单元测试将是一个良好的开端。

另外,现在有点晚了,但是将来请考虑在项目本身之前或期间编写单元测试。这样,您可以使用它们进行测试,并且(理论上)可以将它们用作回归测试,以验证您的代码更改没有破坏任何现有代码。这将为您带来编写测试用例的全部好处:)

The docs for unittest would be a good place to start.

Also, it is a bit late now, but in the future please consider writing unit tests before or during the project itself. That way you can use them to test as you go along, and (in theory) you can use them as regression tests, to verify that your code changes have not broken any existing code. This would give you the full benefit of writing test cases :)


回答 4

unittest随标准库一起提供,但是我建议您进行鼻子测试

鼻子扩展了单元测试,使测试更加容易。

我也建议你pylint

分析Python源代码以寻找错误和质量低劣的迹象。

unittest comes with the standard library, but I would recomend you nosetests.

nose extends unittest to make testing easier.

I would also recomend you pylint

analyzes Python source code looking for bugs and signs of poor quality.


回答 5

正如其他人已经回答的那样,编写单元测试已经晚了,但还不算太晚。问题是您的代码是否可测试。确实,要对现有代码进行测试并不容易,甚至有一本关于此的书:有效地使用遗留代码(请参见要点前期PDF)。

现在编写或不编写单元测试就是您的要求。您只需要意识到这可能是一项繁琐的任务。您可以解决此问题,以学习单元测试或考虑首先编写验收(端对端)测试,然后在更改代码或向项目添加新功能时开始编写单元测试。

As others already replied, it’s late to write unit tests, but not too late. The question is whether your code is testable or not. Indeed, it’s not easy to put existing code under test, there is even a book about this: Working Effectively with Legacy Code (see key points or precursor PDF).

Now writing the unit tests or not is your call. You just need to be aware that it could be a tedious task. You might tackle this to learn unit-testing or consider writing acceptance (end-to-end) tests first, and start writing unit tests when you’ll change the code or add new feature to the project.


回答 6

鼻子测试是在python中进行单元测试的出色解决方案。它同时支持基于单元测试的测试用例和doctest,并且只需简单的配置文件就可以开始使用它。

nosetests is brilliant solution for unit-testing in python. It supports both unittest based testcases and doctests, and gets you started with it with just simple config file.