使用 Vagrant 和 Fabric 用于集成测试 【已翻译100%】

在cloudshare中,我们的服务是由许多部件组成的。当我们更改一个给定组件的代码后我们总需要测试它。我们小心地尝试着平衡单元测试和集成测试(或系统测试)的总量,以便能够实现合理的代码覆盖率和测试运行时间,最重要的是提升对我们代码的信心。

不久前,我们彻底改写了一个叫网关的组件。这个网关运行在Linux机器上,其处理了我们内部的许多路由,防火墙,NAT,负载平衡以及流量日志等很多内容。它基本上是一个路由器/防火墙,通过获取动态配置并根据它了解的配置实施不同的网络规则。这次改写是通过重新设计其(虚拟)硬件和内核模块完成的。它是一个Python应用包使用原始的debian打包部署的。

在重写之前,这个网关是“冰封”的。“冰封”在这里的意思是没有人敢修改它的代码。它没有测试代码,因此每个更改都需要一份完整的手册,单是痛苦的回归测试也需要花一个星期。

我们坐下来定义了我们的目标。我们希望所有的开发人员都能够在本地的机器跑所有的集成测试,并且能够很容易。很容易还意味着在变更代码后不需要部署其他任何东西。需要做的这是在IDE中编辑代码然后重新运行测试。不需要提交代码,不需要重新打包,不需要部署(我们在Windows上开发)/
当进行测试时就不是那么容易了,你知道会发生什么。

改善集成测试:

我们已经知道需要改善我们的单元测试。但是集成测试呢?那是另一回事。你如何测试你的硬件和内核配置以确保这些配置能完成你所想让它实现的网络魔术。

让我们考虑如何手动来做这个事情。简单的方法是用linux提供的一系列网络工具:ping,traceroute,tcpdump,netcat等。事实上,这也正是我们QA工程师做的事情:

部署新代码.

创建一个由几台连接到同一个网关的机器组成的测试平台。

对于任何可能的配置,网关都会测试整个网络的功能应该是流畅/阻塞/跑通NAT/路由等。

这简直是一场噩梦。

我甚至敢说:不要QA工程师,就算他们可能才华横溢,不留死角地涵盖所有情况,或浪费时间(合理数量)。 更何况我们希望的是,他们比只是会弄回归测试多一点创意。 这实际上是机器的工作,而不是人的工作。

我们终于面临了严酷考验,并开始思考为何这是可能的。 Vagrant,那时完全是个新的后备方案,来的如此自然。它允许我们能够创建一个由不同的虚拟局域网连接的虚拟机的环境。 Vagrant 还可以让你直接挂载你在主机文件夹到你管理的虚拟机,并且也满足我们的“容易测试”的要求。 如果代码已经被挂载在VM Vagrant,没必要进行部署。

下面是vagrant 文件, 来定义虚拟环境:

Vagrant::Config.run do |config|
    config.vm.define :gateway do |gateway_config|
        gateway_config.vm.box = "gateway"
        gateway_config.vm.host_name = "gateway"
        gateway_config.vm.box_url = "http://FQDN…./gateway.box"

        gateway_config.vm.network :hostonly, "192.168.58.2", { :adapter => 2, :netmask => '255.255.255.0' }
        gateway_config.vm.network :hostonly, "192.168.56.90", {:adapter => 3, :auto_config => false}
        gateway_config.vm.share_folder "code", "/code", "../../..", :mount_options => ["dmode=755", "fmode=755"]
    end

    config.vm.define :tester1 do |config|
        config.vm.box = "tester"
        config.vm.host_name = "tester1"
        config.vm.box_url = "http://FQDN…../tester.box"

        config.vm.network :hostonly, "192.168.58.91", {:adapter => 2, :netmask => '255.255.255.0' }
        config.vm.network :hostonly, "192.168.56.91", {:adapter => 3, :auto_config => false}

        config.vm.share_folder "code", "/code", "../tests"
    end

    config.vm.define :tester2 do |config|
        config.vm.box = "tester"
        config.vm.host_name = "tester2"
        config.vm.box_url = "http://FQDN…./tester.box"

        config.vm.network :hostonly, "192.168.58.92", {:adapter => 2, :netmask => '255.255.255.0' }
        config.vm.network :hostonly, "192.168.56.91", {:adapter => 3, :auto_config => false}

        config.vm.share_folder "code", "/code", "../tests"
    end
… more testers machines defined here ...

如你所见,本地源码呗挂载/编写在vagrant虚拟机中。在这也有网络定义。一个作为集成测试的物理网络用来配置VLANs(注意:auto_confi => false option)和其他用来测试代码通信。

当开发者运行一段测试时发生了什么?

实际上是在网关虚拟机上运行了测试。使用了本地挂载代码来创建应用对象,调用对象,然后使用 fabric在测试机器上远程运行网络工具来ping/sniff/trace/accept 所有通过和返回给网关的流量的种类。

下面来看个简单的例子,简化了很多的:

class TestVlansBase(unittest.TestCase):
    def setUp(self):
        self._initialize_tester_machines()

    def tearDown(self):
        for tester_name, vlan in self.dct_interfaces_to_remove.iteritems():
            self._remove_interface_from_host(tester_name, vlan)

class TestVlans(TestVlansBase):
    def _test_connection(
            self, server_name, server_vlan, server_ip, server_port, protocol, client_name,
            client_dst_ip=None, client_dst_port=None):
        self.assertTrue(protocol in ('tcp', 'udp'), 'protocol should be tcp or udp')
        client_dst_ip = client_dst_ip or server_ip
        client_dst_port = client_dst_port or server_port
        if protocol == 'tcp':
            server = self._create_server(server_name, server_ip, server_port)
        elif protocol == 'udp':
            filter_exp = '{0} port {1}'.format(protocol, server_port)
            server = self._create_sniffer_on_host(server_name, server_vlan, filter_exp, 1)
        client = self._connect_to_host(client_name, client_dst_ip, client_dst_port, protocol)
        client.runner.join()
        if server.runner.exitcode is None:
            # this means that the process did not exit hence no packets were seen
            server.runner.terminate()
        server.runner.join()
        self.assertEqual(client.runner.exitcode, 0)
        self.assertEqual(server.runner.exitcode, 0)

    def test_reroute_http_traffic(self):
        self._configure_testers()
        self.gateway.configure()
        self._test_connection(
            'tester3', 93, '10.180.0.3', 88, 'tcp', 'tester2', client_dst_ip='10.10.10.10', client_dst_port=80)
        self._test_connection(
            'tester3', 93, '10.180.0.3', 88, 'tcp', 'tester2', client_dst_ip='10.10.10.10', client_dst_port=44444)
        self._test_connection(
            'tester3', 93, '10.180.0.3', 88, 'tcp', 'tester2', client_dst_ip='10.10.10.10', client_dst_port=88)

所有的从网关(测试运行的地方)到测试者机器的远程调用使用的是fabric。

一个可以在测试上运行的简单命令:

class FabricProcessProxy(object):
    metaclass = ABCMeta

    def __init__(self, args, *kwargs):
        self.kwargs = kwargs
        self.args = args
        self.out_q = multiprocessing.Queue()
        # self.runner = multiprocessing.Process(target=lambda: execute(self.run, self.args, *self.kwargs))
        self.runner = multiprocessing.Process(
            target=lambda: self.out_q.put(execute(self.run, self.args, *self.kwargs)))

    def execute(self, hosts):
        self.kwargs['hosts'] = hosts
        self.runner.start()
        return self.runner

    @abstractmethod
    def run(self):
        raise NotImplementedError()

class Ping(FabricProcessProxy):
    def run(self, target, iface, count):
        str_iface = '-I {0} '.format(iface) if iface else ' '
        return run('ping -c {count}{iface}-W 1 {target}'.format(count=count, iface=str_iface, target=target))

def _ping_from_host(self, host, dst_ip, through_iface=None, num_pings=1, b_verify_success=True):
        ping = Ping(dst_ip, through_iface, num_pings)
        ping.execute(['vagrant@{0}'.format(host)]).join()
        if b_verify_success:
            self.assertEqual(ping.runner.exitcode, 0)
        return ping.runner.exitcode

_ping_from_host('tester2', '10.180.0.3')

既然这个基础结构已经建好了,我们就不在回头看它了。我们今天所拥有的网关是一等公民,而且只要通过了测试,我们就不怕重构它,添加新的功能和做出其他改变。

时间: 2024-11-01 03:23:18

使用 Vagrant 和 Fabric 用于集成测试 【已翻译100%】的相关文章

Docker —— 用于统一开发和部署的轻量级 Linux 容器 【已翻译100%】

使用Docker容器--轻量灵活的VM同类,来接管"依赖地狱".学习Docker是如何基于LXC技术,通过把应用包装在容器里来使应用具有移植性和独立性. 想象一下可以轻松地把应用和它的依赖打包,然后在其他的开发.测试和生产环境上平滑的运行.这就是开源Docker项目的目标.尽管它现在还没正式到生产阶段,最新的发布(本篇文章编写时是0.7.x)使得Docker实现这一伟大目标又近了一步. Docker容器试图解决"依赖地狱"问题.现代的应用通常从已存在的组件组合而来,

Vagrant 中高效的 Puppet 模块管理 【已翻译100%】

到现在我还记得首次尝试使用vagrant和puppet这两个工具来准备本地开发环境时候的场景.找出适当的方式来捆绑puppet模块与项目后一切都是很容易做到.基本上它可以通过三步阶段来实现. 1.运行"puppet module install "并将它们添加到 git repo (不是最棒的主意但相对简单). 2.在项目中把puppet模块作为 git 的子模块添加好.这原来是更加麻烦,添加/删除/更新模块成为了真正的痛苦. 3.作为它们的依赖项 使用 puppet-libraria

从 C++ 到 Objective-C 的快速指南 【已翻译100%】

**简介 ** 当我开始为iOS写代码的时候,我意识到,作为一个C++开发者,我必须花费更多的时间来弄清楚Objective-C中怪异的东西.这就是一个帮助C++专家的快速指南,能够使他们快速的掌握Apple的iOS语言. 请注意这绝不是一个完整的指南,但是它让你避免了阅读100页的手册.除此之外,我知道你喜欢我的写作风格. 背景 需要C++的技能,我会比较C++和Objective-C的东西.此外,COM编程也是有用的,因为Objective-C有类似于IUnkown的东西,因此基础的COM编

一个易用的 WPF 自动完成文本框 【已翻译100%】

介绍 这篇文章的目的是在社区中分享一些我上个月完成代码,让一个简单的文本框拥有自定义的自动完成过滤器.这个想法的灵感来自于GMail的搜索功能.在我的项目中,自定义的控件需要如下所有我需要的功能: 它是容易使用的,集成到项目中时,需要的代码要尽量的少. 它需要兼容WCF.我的想法是像GMail一样创建一个分层的应用,过滤功能需要在服务器端执行,然后将结果通过WCF通道传送. 它需要过滤自定义数据(来自于数据库或者自定义的列表)并可搜索多个字段,像GMail一样,建议类似的结果. 所有的过滤需要异

SQLite4 的设计 【已翻译100%】

1.0 内容提要 1.SQLite4 是一个放在库中的紧凑的,自包含的,零维护的的ACID数据库引擎, 像SQLite3一样, 但具有改进的接口和文件格式. 2.运行时环境封装到了一个对象之中. 3.使用了一个很不错的键值对存储引擎: 一个独立的大型键空间 - 不是SQLite3中那种每个表单独的键空间和索引. 2.按字典顺序的键排序. 3.多个存储引擎,可在运行时互换. 4.默认在磁盘上的存储殷勤使用了一个日志结构的合并数据库. 4.表的PRIMARY KEY真正被用作存储引擎的键. 5.可以

构建多语言的 WPF 应用 【已翻译100%】(1/2)

下载源代码 - 84.4 KB 导言 在WPF应用程序中搭建多语言支持(Multilingual Support)是我最近在做的一件事,对于不使用英语的人士而言,此举提高了程序的可用性.实现起来要完成以下目标: 一个版本容纳多种语言. 这就意味着不要创建单独的英语版本.法语版本.日语版本等等. 许多电子产品(例如电视和数码相机)在同一模块中支持多语言.你不需要购买不同模块或给软件打补丁来得到与默认设置不同的语言 允许在运行时切换接口语言. 这就是说不需要关闭应用程序并配置操作系统环境,一切都交给

从 Objective-C 到 Swift —— Swift 糖果 【已翻译100%】

Swift带来很多确实很棒的特性,使得很难再回到Objective-C.主要的特性是安全性,不过这也被看成是一种额外副作用. 带类型接口的强型别 Swift有强型别,这意味着除非你要求,不然Swift不会为你做类型之间的转换.所以,例如你无法把Int型赋给Double型.你不得不首先转换类型: let i: Int = 42 let d: Double = Double(i) 或者你必须给Double类扩展一个方法用来转换Int型: extension Double { func __conve

远程执行 Android 设备上的代码 【已翻译100%】(2/2)

我设置好AP后,从13,119个标明有潜在漏洞的app中随机选了一些,把它们安装到接入了AP的一台Nexus 5(运行4.4.3)和一台三星XE700t(运行AOSP 4.2的x86平板).我们只不过是启动每个App,做些简单的交互操作,就成功地在超过半数的应用中触发了远程代码执行,它们加载了通过中间人代理注入的恶意代码. 为了好玩,我们把注入到一个app中javascript代码反复修改,直到显示Bromium的标志替换了原有广告. 被扰乱而显示了Bromium标志的app的UI截屏. 全是广

通过 Doctype 启用浏览器模式 【已翻译100%】(1/2)

为了即能解析那些满足Web标准的网页,又能解析那些过去20年来遗留下来的传统的网页,现代浏览器一般都实现了多种网页解析的模型.本文将介绍这些解析模型都是什么,以及它们是如何触发的. 内容概述 本文档的主要结论是,你应当在你HTML文档(所有以text/html类型处理的内容)的源代码顶部加上<!DOCTYPE html>.(详见下文) 如果你还想确保使用IE8/IE9/IE10的用户不做任何操作就可以让网页以IE7的形式显示,你可以在你的服务器上为所有text/html的响应添加HTTP头&q