SALT’S TEST SUITE: AN INTRODUCTION
文章目录
注:本教程做了几个假设。 第一个假设是你有Salt的基本知识。 为了加快速度,请查看Salt Walkthrough。
第二个假设是您的Salt开发环境已经配置,并且您对Salt代码库的贡献有基本的了解。 如果您不熟悉这两个主题,请分别参阅安装Salt for Development和Contributing页面。
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
这将安装最新的pycrypto
和pyzmq
(带有捆绑的libzmq
)运行集成测试套件所需的Python模块。
TEST DIRECTORY STRUCTURE
如本教程的介绍中所述,Salt的测试套件位于Salt代码库的根目录中的tests
目录中。 从那里,测试分为两组integration
和 unit
。 在每个目录中,目录结构大致反映了Salt自己的代码库的目录结构。 例如,tests/integration/modules
中的文件包含salt/modules
中文件的测试。
注:
tests/integration
和tests/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守护进程,而是在测试单个函数的单个实现时发挥它们的价值。应该使用单元测试来测试函数的逻辑,而不是针对特定的交互进行测试。单元测试应该用于测试函数的退出点,例如任何return
或raises
语句。
在可能无法编写集成测试的情况下,单元测试也很有用。虽然集成测试套件非常强大,但不幸的是,它并未覆盖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测试文档。 有关更多信息,请参阅以下参考: