Pytest在哪里存储预期的数据

时间:2015-04-14 12:28:03

标签: python pytest

测试功能我需要传递参数并查看输出是否与预期输出匹配。

当函数的响应只是一个小数组或可以在测试函数中定义的单行字符串时很容易,但假设我测试的函数修改了一个可能很大的配置文件。或者,如果我明确定义它,结果数组就会长4行。我在哪里存储,以便我的测试保持干净,易于维护?

现在,如果那是字符串我只是将文件放在.py测试附近并在测试中做open()

def test_if_it_works():
    with open('expected_asnwer_from_some_function.txt') as res_file:
        expected_data = res_file.read()
    input_data = ... # Maybe loaded from a file as well
    assert expected_data == if_it_works(input_data)

我发现这种方法存在许多问题,比如维护此文件的问题是最新的。它看起来也很糟糕。 我可以让事情更好地将其转移到固定装置上:

@pytest.fixture
def expected_data()
    with open('expected_asnwer_from_some_function.txt') as res_file:
        expected_data = res_file.read()
    return expected_data

@pytest.fixture
def input_data()
    return '1,2,3,4'

def test_if_it_works(input_data, expected_data):
    assert expected_data == if_it_works(input_data)

这只是将问题转移到另一个地方,通常我需要测试函数是否在空输入,输入单个项目或多个项目的情况下工作,所以我应该创建一个大的夹具,包括所有三个案例或多个灯具。最后代码变得非常混乱。

如果一个函数需要一个复杂的字典作为输入或者给出相同大小的字典测试代码变得丑陋:

 @pytest.fixture
 def input_data():
     # It's just an example
     return {['one_value': 3, 'one_value': 3, 'one_value': 3,
     'anotherky': 3, 'somedata': 'somestring'], 
      ['login': 3, 'ip_address': 32, 'value': 53, 
      'one_value': 3], ['one_vae': 3, 'password': 13, 'lue': 3]}

很难用这些灯具阅读测试并让它们保持最新状态。

更新

经过一段时间的搜索,我找到了一个解决了部分问题的库,而不是大配置文件,我有大量的HTML响应。这是betamax

为了便于使用,我创建了一个灯具:

from betamax import Betamax

@pytest.fixture
def session(request):
    session = requests.Session()
    recorder = Betamax(session)
    recorder.use_cassette(os.path.join(os.path.dirname(__file__), 'fixtures', request.function.__name__)
    recorder.start()
    request.addfinalizer(recorder.stop)
    return session

所以现在在我的测试中我只使用session fixture,我所做的每个请求都被自动序列化到fixtures/test_name.json文件,所以下次我执行测试而不是做一个真正的HTTP请求库从文件系统加载它:

def test_if_response_is_ok(session):
   r = session.get("http://google.com")

它非常方便,因为为了使这些灯具保持最新,我只需要清理fixtures文件夹并重新运行我的测试。

4 个答案:

答案 0 :(得分:30)

我有一次类似的问题,我必须针对预期的文件测试配置文件。这就是我修复它的方法:

  1. 创建一个与测试模块名称相同且位于同一位置的文件夹。将所有预期的文件放在该文件夹中。

    test_foo/
        expected_config_1.ini
        expected_config_2.ini
    test_foo.py
    
  2. 创建一个夹具,负责将此文件夹的内容移动到临时文件中。我确实使用了tmpdir夹具。

    from __future__ import unicode_literals
    from distutils import dir_util
    from pytest import fixture
    import os
    
    
    @fixture
    def datadir(tmpdir, request):
        '''
        Fixture responsible for searching a folder with the same name of test
        module and, if available, moving all contents to a temporary directory so
        tests can use them freely.
        '''
        filename = request.module.__file__
        test_dir, _ = os.path.splitext(filename)
    
        if os.path.isdir(test_dir):
            dir_util.copy_tree(test_dir, bytes(tmpdir))
    
        return tmpdir
    
  3. 使用新装置。

    def test_foo(datadir):
        expected_config_1 = datadir.join('expected_config_1.ini')
        expected_config_2 = datadir.join('expected_config_2.ini')
    
  4. 请记住:datadirtmpdir fixture相同,并且能够处理放置在具有测试模块名称的文件夹中的预期文件。

答案 1 :(得分:2)

如果您只进行了一些测试,那么为什么不将数据包含在字符串文字中:

expected_data = """
Your data here...
"""

如果你有一把,或者预期的数据真的很长,我认为你使用灯具是有道理的。

但是,如果你有很多,那么也许不同的解决方案会更好。事实上,对于一个项目,我有超过一百个输入和预期输出文件。所以我建立了自己的测试框架(或多或少)。我使用Nose,但PyTest也可以。我创建了一个测试生成器,它走遍了测试文件的目录。对于每个输入文件,产生一个测试,它将实际输出与预期输出进行比较(PyTest称之为parametrizing)。然后我记录了我的框架,以便其他人可以使用它。要查看和/或编辑测试,您只需编辑输入和/或预期的输出文件,而不需要查看python测试文件。为了使不同的输入文件能够定义不同的选项,我还为每个目录创建了一个YAML配置文件(JSON也可以用来保持依赖关系)。 YAML数据由一个字典组成,其中每个键是输入文件的名称,值是关键字的字典,它将与输入文件一起传递给正在测试的函数。如果您有兴趣,请访问source codedocumentation。我最近想到将选项定义为Unittests here(仅需要内置的unittest lib),但我不确定我是否喜欢它。

答案 2 :(得分:2)

我相信pytest-datafiles可以提供很大的帮助。不幸的是,它似乎不再维护了。目前,它运行良好。

这是一个来自文档的简单示例:

import os
import pytest

@pytest.mark.datafiles('/opt/big_files/film1.mp4')
def test_fast_forward(datafiles):
    path = str(datafiles)  # Convert from py.path object to path (str)
    assert len(os.listdir(path)) == 1
    assert os.path.isfile(os.path.join(path, 'film1.mp4'))
    #assert some_operation(os.path.join(path, 'film1.mp4')) == expected_result

    # Using py.path syntax
    assert len(datafiles.listdir()) == 1
    assert (datafiles / 'film1.mp4').check(file=1)

答案 3 :(得分:0)

想想是否真的需要测试配置文件的全部内容。

如果只检查多个值或子字符串,请为该配置准备一个预期的模板。测试的位置将被标记为具有一些特殊语法的“变量”。然后为模板中的变量准备一个单独的预期值列表。此预期列表可以存储为单独的文件,也可以直接存储在源代码中。

模板示例:

ALLOWED_HOSTS = ['{host}']
DEBUG = {debug}
DEFAULT_FROM_EMAIL = '{email}'

这里,模板变量放在花括号内。

预期值可能如下所示:

host = www.example.com
debug = False
email = webmaster@example.com

或者甚至是一个简单的逗号分隔列表:

www.example.com, False, webmaster@example.com

然后,您的测试代码可以通过将变量替换为期望值来从模板生成预期文件。并将预期的文件与实际文件进行比较。

单独维护模板和期望值具有以下优势:您可以使用相同的模板拥有许多测试数据集。

仅测试变量

更好的方法是配置生成方法仅为配置文件生成所需的值。可以通过其他方法将这些值轻松插入到模板中。但优点是测试代码可以直接以明确的方式直接比较所有配置变量。

<强>模板

虽然很容易用模板中的所需值替换变量,但是有一些现成的模板库,它们只允许在一行中完成。以下是一些示例:DjangoJinjaMako