使用pytest测试文件读取功能时出错

时间:2018-03-06 11:48:20

标签: python python-3.x pytest

我有一个简单的功能:

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.min.js"></script>

<div ng-app="app" ng-controller="parentCtrl">
       <b>Parent Event:</b> {{message}}<br />
       <br/><br/>
      <div ng-controller="childCtrl">
        <b>Child Event:</b> {{data.name}}<br />
          <input type="button" ng-click="update()" value ="update"/>
        
      </div>
    </div>

当我在不存在的文件上运行此函数时,我得到:

def read_file(fp):
    with open(fp) as fr:
        for line in fr.readlines():
            yield line

在另一个文件中,我尝试使用FileNotFoundError: [Errno 2] No such file or directory: 'idontexist.txt' 测试此函数:

pytest

但是,运行import pytest from utils import read_file def test_file_not_exist(): filepath = 'idontexist.txt' with pytest.raises(FileNotFoundError): read_file(filepath) ,我收到消息:

pytest

为什么这个测试没有通过?

1 个答案:

答案 0 :(得分:3)

您正在创建生成器功能。调用生成器函数会返回一个生成器对象:

>>> def read_file(fp):
...     with open(filepath) as fr:
...         for line in fr.readlines():
...             yield line
... 
>>> read_file('asd')
<generator object read_file at 0x10554ee08>

在遍历生成器之前,不会调用open调用(应该引发FileNotFoundError)。然后你会看到

>>> g = read_file('asd')
>>> for x in g:
...     pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in read_file
NameError: name 'filepath' is not defined

我猜你在发布之前已将fp改为filepath。您可以解决这个问题,但无论哪种方式,您都不会看到来自read_file的任何错误,除非您通过迭代来使用返回的生成器。

编辑:我不建议在像这样的生成器中使用with语句。原因是无法保证文件将被关闭。

要理解这一点,请考虑with的目的是什么。在上下文管理器之前,您将打开一个像

这样的文件
f = open(filename)
f.read(4)
# etc
f.close()

这样做的问题是,如果returnopen之间发生了某些事情(例如异常或close等),该文件可能无法关闭。您可以使用try/finally

来解决此问题
f = open(filename)
try:
    f.read(4)
    # etc
finally:
    f.close()

这很麻烦,所以我们有with语句将其缩短为

with open(filename) as f:
    f.read(4)
    # etc

这很好,因为它可以减少混乱,并且在没有关闭文件的情况下无法离开with语句。但是,当你在read_file以上的生成器中执行此操作时,有人可能会使用

调用生成器
for line in read_file(filename):
    if line.startswith('#'):
        break

现在break生成器暂停在yield后,它无法知道它不会再次迭代,所以它会在那里等待。 yield块中的with允许您离开上下文管理器而不关闭文件。 (使用try/finally时也会出现同样的问题,但在这种情况下可能会更明显。)即使你知道你从循环体中获得了break异常会产生同样的效果。

在这种情况下会发生的事情是,由于CPython中的ref计数GC,文件将可能关闭:当收集生成器时,它将被关闭,抛出异常终止with块,从而关闭文件。这并不比允许GC直接收集文件对象f(也通过file.__del__关闭文件)好得多。

简单的规则是:

  

yield声明

中不要with

这意味着您通常应该在生成器外部使用with语句。所以你做了像

这样的事情
def read_file(f):
    for line in f.readlines():
        yield line

# Control resource at top level
with open(filename) as fin:
    for line in read_file(fin) # pass the resource to generator
        # do something with line

另一点:迭代器的重点在于它们允许我们不必执行诸如将整个文件读入内存之类的操作。因此,不是调用readlines()将while文件读入内存,而是应该直接迭代文件,一次只读取一行。通过这两项更改,您的功能如下:

def read_file(f):
    for line in f:
        yield line

甚至:

def read_file(f):
    yield from f

就迭代器而言,这只是身份函数,因此它是多余的,可以删除。因此,只要您使用read_lines函数,就可以使用

with open(filename) as fin:
    for line in fin:
        # do stuff

(即代码中没有read_lines函数)