SaltStack's TEST SUITE - Salt测试套件介绍

SALT’S TEST SUITE: AN INTRODUCTION

注:本教程做了几个假设。 第一个假设是你有Salt的基本知识。 为了加快速度,请查看Salt Walkthrough

第二个假设是您的Salt开发环境已经配置,并且您对Salt代码库的贡献有基本的了解。 如果您不熟悉这两个主题,请分别参阅安装Salt for DevelopmentContributing页面。

Salt带有强大的集成和单元测试套件。 测试套件允许从单个界面进行全自动的集成和/或单元测试。

Salt的测试套件位于Salt代码库的根目录下的tests目录下,分为两种主要类型的测试:单元测试和集成测试。 单元和集成子测试套件位于test目录中,这是Salt的大多数测试用例所在的目录。

此外,您也可以参考Github上的这篇文章:SALT’S TEST SUITE: AN INTRODUCTION

GETTING SET UP FOR TESTS

除了Salt的安装要求之外,还有一些要求需要安装才能运行Salt的测试套件。 您可以使用salt/requirements目录中的文件来安装这些附加要求,具体取决于您的相关Python版本:

pip install -r requirements/dev_python27.txt
pip install -r requirements/dev_python34.txt

为了能够运行利用ZeroMQ传输的集成测试,您还需要为其安装其他要求。 确保已安装Python版本所需的C/C++编译器和开发库以及头文件。

这是基于RedHat的操作系统的示例:

yum install gcc gcc-c++ python-devel
pip install -r requirements/zeromq.txt

在Debian上,Ubuntu或其衍生产品则运行以下命令:

apt-get install build-essential python-dev
pip install -r requirements/zeromq.txt

这将安装最新的pycryptopyzmq(带有捆绑的libzmq)运行集成测试套件所需的Python模块。

TEST DIRECTORY STRUCTURE

如本教程的介绍中所述,Salt的测试套件位于Salt代码库的根目录中的tests目录中。 从那里,测试分为两组integrationunit。 在每个目录中,目录结构大致反映了Salt自己的代码库的目录结构。 例如,tests/integration/modules中的文件包含salt/modules中文件的测试。

注:tests/integrationtests/unit是本教程中讨论的唯一目录。 除了下面的“ Running the Test Suite”部分中使用的tests/runtests.py文件之外,测试中的其他目录和文件超出了本教程的范围。

INTEGRATION VS. UNIT

鉴于Salt的测试套件包含两种功能强大但非常不同的测试方法,您何时应该编写集成测试以及何时编写单元测试?

集成测试使用Salt master,minions和syndic直接测试salt功能,并专注于测试这些组件的交互。 Salt的集成测试运行器包括运行Salt执行模块、运行器、状态、shell命令、salt-ssh命令以及salt-api命令等的功能。这提供了使用Salt测试自身的巨大能力,并使编写此类测试变得轻而易举。集成测试是尽可能测试Salt功能的首选方法。

单元测试不会调动任何Salt守护进程,而是在测试单个函数的单个实现时发挥它们的价值。应该使用单元测试来测试函数的逻辑,而不是针对特定的交互进行测试。单元测试应该用于测试函数的退出点,例如任何returnraises语句。

在可能无法编写集成测试的情况下,单元测试也很有用。虽然集成测试套件非常强大,但不幸的是,它并未覆盖Salt生态系统的所有功能区域。例如,在撰写本文时,没有办法为Proxy Minions编写集成测试。由于需要调整测试运行器以考虑Proxy Minion进程,因此通过测试Proxy Minion函数中包含的逻辑,单元测试仍可在此期间提供一些测试支持。

RUNNING THE TEST SUITE

安装完所有需求后,salt/tests目录中的runtests.py文件将用于实例化Salt的测试套件:

python tests/runtests.py [OPTIONS]

上面的命令,如果没有指定任何选项,将运行整套集成和单元测试。 启动某些类型测试需要运行某些标志,例如破坏性测试。 如果不包含这些标志,则测试套件将仅执行不需要特别注意的测试。

在测试运行结束时,您将看到已通过、失败或被跳过的测试的摘要输出。

测试运行器还包括一个--help选项,列出了所有各种命令行选项:

python tests/runtests.py --help

或:

./tests/runtests.py --help

RUNNING INTEGRATION TESTS

Salt的集成测试集使用Salt来测试自己。 测试套件的集成部分包括一些内置的Salt守护进程,它们将在准备测试运行时启动。 这个Salt守护进程列表包括:

  • 2 Salt Masters
  • 2 Salt Minions
  • 1 Salt Syndic

这些不同的守护进程用于在测试套件中执行Salt命令和功能,允许您编写测试以针对预期或意外行为进行断言。

使用典型的master/minion执行模块命令进行测试的一个简单示例是test/integration/modules/test_test.py文件中test_ping函数的测试:

def test_ping(self):
    '''
    test.ping
    '''
    self.assertTrue(self.run_function('test.ping'))

上面的测试是一个非常简单的例子,其中test.ping函数由Salt的测试套件运行程序执行,并断言minion返回True响应。

TEST SELECTION OPTIONS

如果查看测试运行器的--help命令的输出,您将看到一个名为Tests Selection Options的部分。 本节下的选项包含集成测试套件的各个子部分,例如--modules--ssh--states。 通过选择这些选项中的任何一个,测试守护进程将启动并运行命名子部分中的集成测试。

./tests/runtests.py --modules

注:--help输出的测试选择选项中列出的测试子部分仅适用于集成测试。 他们不进行单元测试。

RUNNING UNIT TESTS

虽然./tests/runtests.py执行整个测试套件(除非需要特殊标志的任何测试),但--unit标志可用于仅运行Salt的单元测试。 Salt的单元测试包括位于tests/unit目录中的测试。

单元测试不会启动任何Salt测试守护进程,因为与集成测试相比,单元测试的执行和执行速度非常快。

./tests/runtests.py --unit

RUNNING SPECIFIC TESTS

有时需要执行特定的测试文件,测试类甚至单个测试,例如编写新测试时。 在这些情况下,应使用--name选项。

要运行单个测试文件(例如集成测试目录中的pillar模块测试文件),必须使用.分隔文件路径,而不是/作为分隔符,没有文件扩展名:

./tests/runtests.py --name=integration.modules.test_pillar
./tests/runtests.py -n integration.modules.test_pillar

某些测试文件只包含一个测试类,而其他测试文件包含多个测试类。 要在文件中运行特定的测试类,请将测试类的名称附加到文件路径的末尾:

./tests/runtests.py --name=integration.modules.test_pillar.PillarModuleTest
./tests/runtests.py -n integration.modules.test_pillar.PillarModuleTest

要运行在一个文件中的单个测试,请附加单个测试所属的测试类的名称,以及测试本身的名称:

./tests/runtests.py \
  --name=integration.modules.test_pillar.PillarModuleTest.test_data
./tests/runtests.py \
  -n integration.modules.test_pillar.PillarModuleTest.test_data

--name-n选项可用于单元测试和集成测试。 以下命令是如何执行tests/unit/modules/test_cp.py文件中的单个测试的示例:

./tests/runtests.py \
  -n unit.modules.test_cp.CpTestCase.test_get_template_success

WRITING TESTS FOR SALT

一旦你熟悉了运行测试,现在可以开始编写它们了! 请务必查看本教程的“集成与单元”部分,以确定哪种类型的测试对您正在测试的代码最有意义。

注意:Salt测试文件需要许多装饰器、命名约定和代码规范。 我们不会在本教程中介绍所有这些细节。 请参阅下面“附加测试文档”部分中列出的测试文档链接,以了解有关这些要求的更多信息。

在以下部分中,测试示例假定“新”测试已添加到已存在且经常在测试套件中运行的测试文件中,并且使用符合正确的要求编写。

WRITING INTEGRATION TESTS

由于集成测试针对正在运行的环境进行验证,如本教程的“运行集成测试”部分所述,集成测试非常容易编写,通常是编写Salt测试的首选方法。

以下集成测试是从tests/integration/modules目录中的test.py文件中获取的示例。 此测试使用run_function方法测试传统执行模块命令的功能。

run_function方法使用集成测试守护进程来执行module.function命令,就像使用Salt一样。 minion运行该函数并返回。 该测试还使用Python的Assert Functions来测试minion的返回是否是预期的。

def test_ping(self):
    '''
    test.ping
    '''
    self.assertTrue(self.run_function('test.ping'))

也可以为 run_function 方法传入参数:

def test_echo(self):
    '''
    test.echo
    '''
    self.assertEqual(self.run_function('test.echo', ['text']), 'text')

下一个示例来自tests/integration/modules/test_aliases.py文件,演示了如何将kwargs传递给run_function调用。 另外请注意,在尝试断言aliases.get_target调用应返回的内容之前,此测试使用另一个salt函数来确保存在正确的数据(通过aliases.set_target调用)。

def test_set_target(self):
    '''
    aliases.set_target and aliases.get_target
    '''
    set_ret = self.run_function(
            'aliases.set_target',
            alias='fred',
            target='bob')
    self.assertTrue(set_ret)
    tgt_ret = self.run_function(
            'aliases.get_target',
            alias='fred')
    self.assertEqual(tgt_ret, 'bob')

以这种方式使用多个Salt命令提供了两个有用的好处。第一个是它为aliases.set_target函数提供了一些额外的覆盖。第二个好处是对aliases.get_target的调用不依赖于在此测试之外设置的任何别名的存在。测试不应取决于先前的执行,成功或其他测试的失败。它们应尽可能与其他测试隔离。

虽然在运行之前构建测试依赖于彼此的测试文件可能很诱人,但应该避免这种情况。 SaltStack建议每个测试应测试单个功能,而不是依赖其他测试。因此,在可能的情况下,个别测试也应该分解为单个部分。这些并不是硬性规则,但更多的是作为保持测试套件简单的建议。这有助于在发生故障和暴露问题时调试代码和相关测试。可能存在大型测试使用许多断言来设置用例以防止潜在回归的情况。

注意:上面的示例都使用run_function选项来测试传统master/minion环境中的执行模块函数。要查看如何测试其他常见Salt组件(如runners,salt-api等)的示例,请参阅Integration Test Class Examples文档。

DESTRUCTIVE VS NON-DESTRUCTIVE TESTS

由于Salt用于更改系统的设置和行为,因此运行测试的最佳方法是对基础系统进行实际更改。 这就是破坏性集成测试的概念发挥作用的地方。 可以编写测试来改变它们运行的系统。 此功能填补了正确测试系统管理方面(如软件包安装)所需的空白。

要编写破坏性测试,请导入并使用destructiveTest装饰器作为测试方法:

import integration
from tests.support.helpers import destructiveTest

class PkgTest(integration.ModuleCase):
    @destructiveTest
    def test_pkg_install(self):
        ret = self.run_function('pkg.install', name='finch')
        self.assertSaltTrueReturn(ret)
        ret = self.run_function('pkg.purge', name='finch')
        self.assertSaltTrueReturn(ret)

WRITING UNIT TESTS

如上面的Integration vs. Unit部分所述,应编写单元测试来测试函数的逻辑。这包括专注于测试返回和引发语句。应该大量努力模拟正在测试的代码中使用的外部资源。

应该模拟的外部资源包括但不限于API、函数调用、全局可用的外部数据或通过函数参数、文件数据等传入的外部数据。这种做法有助于隔离单元测试以测试Salt逻辑。考虑编写单元测试的一种方法是“阻止所有出口”。有关如何正确模拟外部资源的更多信息,请参阅Salt的单元测试文档。

Salt的单元测试使用Python的模拟类和MagicMock@patch装饰器在“阻止所有出口”时也被大量使用。

当前在Salt中使用的单元测试的一个简单示例是tests/unit/modules/test_cp.py文件中的test_get_file_not_found测试。此测试使用@patch装饰器和MagicMock来模拟对Salt的cp.hash_file执行模块函数的调用的返回。这可以确保我们直接测试cp.get_file函数,而不是无意中测试对cp.get_file中使用的cp.hash_file的调用。

def test_get_file_not_found(self):
    '''
    Test if get_file can't find the file.
    '''
    with patch('salt.modules.cp.hash_file', MagicMock(return_value=False)):
        path = 'salt://saltines'
        dest = '/srv/salt/cheese'
        ret = ''
        self.assertEqual(cp.get_file(path, dest), ret)

请注意,Salt的cp模块是在文件顶部导入的,与所有其他必要的测试一同导入。 然后在测试函数中调用get_file函数,而不是像上面的集成测试示例那样使用run_function方法。

当找不到hash_file时,对cp.get_file的调用返回空字符串。 因此,上面的示例很好地说明了通过@patch装饰器“阻止退出”的单元测试,以及通过断言if子句中的return语句来测试逻辑。

在以下文档中有更多关于编写不同复杂性的单元测试的示例:

注:应该非常小心,以确保您在测试功能中测试有用的东西。

CHECKING FOR LOG MESSAGES

要测试是否已发出给定的日志消息,可以使用以下模式:

# Import logging handler
from tests.support.helpers import TestsLoggingHandler

# .. inside test
with TestsLoggingHandler() as handler:
    for message in handler.messages:
        if message.startswith('ERROR: This is the error message we seek'):
            break
        else:
            raise AssertionError('Did not find error message')

AUTOMATED TEST RUNS

SaltStack维护着一个Jenkins服务器,可以在 https://jenkins.saltstack.com 上查看。 从这个Jenkins服务器执行的测试为每次测试运行创建新的虚拟机,然后在新的、干净的虚拟机上执行破坏性测试。 这允许跨受支持的平台执行测试。

ADDITIONAL TESTING DOCUMENTATION

除了本教程之外,还有一些其他有用的资源和文档可以更深入地介绍Salt的测试运行器,编写Salt代码测试以及常规Python测试文档。 有关更多信息,请参阅以下参考:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值