参考几篇文章后,简单的进行了实践。感觉可以作为以后使用 golang 开发时,单元测试的固定套路。
首先介绍下这几个库
goconvey
可以作为单元测试框架。提供web界面管理测试,不仅可以看到单元测试的成功失败,还可以看到测试的覆盖率。而且启动goconvey以后,可以在文件修改后,自动进行测试。 gostub 在测试过程中,根据需求动态修改全局变量的值(比如,使用测试专用的配置文件),根据需求指定某个函数的返回值。(专业术语叫打桩???) gomock 专门用来测试接口的工具。可以根据接口定义来生成一个实现了接口的mock结构体。不过这个mock结构体上接口的每个函数返回值都需要根据测试来指定,这个mock结构体,可以在测试中使用。 goconvey 安装
go get github.com/smartystreets/goconvey 启动测试框架
cd $GOPATH/src/github.com/asdfsx/codility goconvey 通过浏览器访问 http://127.0.0.1:8080/ 可以看到测试结果。更改上面的地址可以对不同目录下的代码进行测试。
goconvey 可以直接支持 golang 的 testing 模块。但是为了获得更好的输出,可以使用goconvey的api对测试进行包装
import( . "github.com/smartystreets/goconvey/convey" "testing" ) func TestDB2(t *testing.T){ ... Convey("CreateConnection", t, func(){ dbConn, err = CreateConnection() So(err, ShouldEqual, nil) }) ... ) gostub 安装
go get github.com/prashantv/gostub 在测试的过程中,需要根据情况调整配置和一些全局变量,gostub 就是用来做这个的(打桩?)。
import ( "testing" . "github.com/prashantv/gostub" ) var ( MYSQLUSER string MYSQLPASSWORD string MYSQLADDR string MYSQLPORT int DATABASENAME string ) func Test1(t *testing.
随便写写Kafka,并且大俗特俗的从安装操作开始写起。
Kafka 的安装 首先要有 jdk! 其次要有 zookeeper!不过kafka的发行版压缩包中,已经集成了zookeeper。 从官网下载最新版的Kafka,解压后就可以直接使用了。
启动zookeeper:
bin/zookeeper-server-start.sh -daemon config/zookeeper.properties 启动Kafka
bin/kafka-server-start.sh -daemon config/server.properties 这样一个单节点的Kafka就启动了。早期的版本启动的时候还需要使用 nohup 来放到后台。现在只需要带 -daemon 参数了。
Kafka 的操作 只列几个常用的命令,更多的命令可以到官网上去翻文档。
创建topic bin/kafka-topics.sh --zookeeper localhost:2181 --create --topic etl-kafka --partitions 24 --replication-factor 1 列出所有的topic bin/kafka-topics.sh --zookeeper localhost:2181 --list 查看topic信息 bin/kafka-topics.sh --zookeeper localhost:2181 --describe --topic etl-kafka 测试发送消息 bin/kafka-console-producer.sh --broker-list localhost:9092 --topic etl-kafka 测试接受消息 bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic etl-kafka --from-beginning 以上是最常用的命令,此外还有些管理 topic 的命令,如
关于消息队列其实已经有很多文章了,简单摘抄一些。 以下内容来自美团技术点评:消息队列设计精要
消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能,成为异步RPC的主要手段之一。 常见的使用场景: 解耦 最终一致性 广播 错峰与流控 消息队列基本功能: RPC通信协议 高可用 服务端承载消息堆积的能力 存储子系统的选择 消费关系解析 队列高级特性设计: 可靠投递(最终一致性) 消费确认 事务 正式接触过的消息队列有Kafka、RabbitMQ,其余还稍微了解过ZeroMQ、nsq。它们都有其独特之处,都有各自的使用场景。而使用场景决定了消息队列产品的选择。
Kafka 是在搞 ETL 项目时用到的。其设计初衷是高吞吐,所以放弃了传统消息队列的设计思路(维护消息状态,保证消息一次发送…等等特性),取而代之的消息的持久化、分片、偏移量等设计思路。
RabbitMQ 是在研究 OpenStack时接触到的。主要作用是进行 python 的进程间通讯:所有的进程都会连接 RabbitMQ;控制节点会通过RabbitMQ下发命令;计算节点会通过RabbitMQ回传命令执行情况、或者是本机的信息。选择RabbitMQ,应该是看中了其可以保证消息的到达的能力。
而随着Golang社区的发展,也开始有人不断的制造新轮子,前面提到的NSQ就是其中之一,此外还有一个NATS。
另外Rust社区也在不断的发展中,估计也会诞生出一些新的轮子,之后会持续关注这些新轮子。
两个月前给自己挖的坑,就这么算是填上了吧。其实最重要的还是多看代码、多写代码。本文参考 Golang Specification 和 The way to go。
常量 常量声明 const Pi float64 = 3.14159265358979323846 const zero = 0.0 // untyped floating-point constant const ( size int64 = 1024 eof = -1 // untyped integer constant ) const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", untyped integer and string constants const u, v float32 = 0, 3 // u = 0.
源起 docker并不是新技术。作为 golang 社区内的旗舰级的项目,从诞生之初就吸引了很多人的瞩目,甚至可以说是获得了所有 golang 社区的人多瞩目吧。同时对推广 golang 也起了很大的作用。作为从一直关注着 golang 的人,大概2年前就开始尝试使用它了。不过由于其依赖 linux 的多项特性,导致在 mac 上使用必须要借助虚拟机,体验总是要差一点。最近经人提醒发现新的 docker for mac 可以不借助虚拟机就可以在 mac 上提供与 linux 上同样的体验,大喜!遂卸载虚拟机,打算用新软件搭个测试环境玩耍一下。
安装 超级简单,照着说明来就好。顺便装上了 kitematic。这个确实也非常好用。
目标 将一套以前用 python 实现的 kafka producer程序,移植到 golang 上。测试新程序的异常情况处理(特指连接失败的处理)。 然后就被坑掉了。
坑1 网络的问题 最初只是想通过 docker 启动一个单点的 kafka,程序通过本地网络直接访问就可以了。通过之前安装的 kitematic 从 hub.docker.com 上下载了 spotify/kafka 镜像,并直接启动容器。通过kitematic连接到容器内,各项命令执行正常。但是执行程序的时候却发现,总是第一次连接成功后,后边的所有连接都是失败的。通过日志发现程序在第一次成功连接 kafka 之后,连接地址发生了变更,变成了容器内部的地址(直观的现象就是,端口从 kitematic 随机生成的 12345 变成了 9092)。然后又尝试了手动用 –net=host 方式启动,结果彻底连不上了。
在对 github.com/shopify/sarama 简单的分析以后,发现它的连接过程是这样的:
首次连接,根据配置信息连接服务器
连接成功后,从服务器获取broker信息 根据获得到的broker信息,连接其余的broker 问题就出现在最后一步上,服务器返回的broker信息中,网络地址都是容器内部的网络,没有办法从容器网络外部直接连接的。
通过在github上查 issue, google 上查资料,有了以下不负责任的猜想:
原文地址:https://www.odoo.com/documentation/10.0/index.html
整理一下,捡重点的来弄。
Odoo Guidelines 模块结构 目录结构 命名规则 XML files Python Idiomatics Python Programming Programming in Odoo Module Web Controllers ORM API Recordsets Environment Common ORM methods Creating Models Compatibility between new API and old API Model Reference Method decorators Fields Data Files QWeb Views Odoo Guidelines 模块结构 目录结构 主要目录: * data/ : demo and data xml * models/ : models definition * controllers/ : contains controllers (HTTP routes).
复制 http://blog.sunansheng.com/python/odoo/odoo.html 中的请假单例子,创建一个带工作流的例子,工作环境是odoo 10.0。
PS:在这个版本中官方自带的请假模块删掉了工作流~~何等的卧槽!
创建模块模版: python odoo/odoo-bin scaffold qingjia odoo_dev/ 在odoo_dev目录中可以找到新创建的模块,进入目录以后可以看到如下的目录结构
controllers demo __init__.py __manifest__.py models security views __init__.py不需要修改
__manifest__.py需要增加一点东西
"application": True, 创建模型 修改models/model.py文件。添加新的模型
# -*- coding: utf-8 -*- from odoo import models, fields, api class Qingjd(models.Model): _name = 'qingjia.qingjd' name = fields.Many2one('res.users', string="申请人", required=True) days = fields.Float(string="天数", required=True) startdate = fields.Date(string="开始日期", required=True) reason = fields.Text(string="请假事由") def send_qingjd(self): self.sended = True return self.sended def confirm_qingjd(self): self.
一个模块中的Python代码通过导入的过程获得对另一个模块中的代码的访问。import语句是调用导入机制的最常用方法,但它不是唯一的方法。诸如importlib.import_module()和内置__import__()之类的函数也可以用于调用导入机制。
import语句组合两个操作;它搜索指定的模块,然后将该搜索的结果绑定到本地作用域中的名称。import语句的搜索操作被定义为使用适当的参数调用__import__()函数。__import__()的返回值用于执行import语句的名称绑定操作。有关该名称绑定操作的确切详细信息,请参见import语句。
直接调用__import__()只执行模块搜索,如果找到,则执行模块创建操作。虽然可能会发生某些副作用,例如导入父包以及更新各种缓存(包括sys.modules),但只有import语句会执行名称绑定操作。
当调用__import__()作为import语句的一部分时,将调用标准的内置__import__()。用于调用导入系统的其他机制(例如importlib.import_module())可以选择颠覆__import__()并使用其自己的解决方案来实现导入语义。
首次导入模块时,Python会搜索模块,如果找到,它会创建一个模块对象[1],并初始化它。如果找不到指定的模块,则会引发ImportError。当执行导入机制时,Python实现各种策略来搜索命名的模块。这些策略可以通过使用下面部分中描述的各种钩子来修改和扩展。
在版本3.3中更改:导入系统更新成完全实现 PEP 302的第二阶段。不再有任何隐式导入机制 - 完整导入系统通过sys.meta_path暴露。此外,已实现原生命名空间包支持(参见 PEP 420)。
1. importlib importlib模块提供了一个丰富的API,用于与导入系统进行交互。例如importlib.import_module()提供了一个比内置的__import__()更简单的API来调用导入机制。有关其他详细信息,请参阅importlib库文档。
2. 包Packages Python只有一种模块对象,所有的模块都是这种类型,不管这个模块是否是用Python,C,或者其他语言实现。为了帮助组织模块并提供命名层次结构,Python有一个概念:包。
你可以认为包是文件系统中的一个目录并且模块作为文件存放于目录中,但是不要做这种太字面化的类比因为包和模块不需要源于文件系统。从这篇文档的目的是我们用目录和文件这个方便的类比来解释包和模块。和文件系统一样,包有有层次的组织着,并且包本身也会包含子包,规则的模块也一样。
重要的是请注意所有的包都是模块,但不是所有的模块都是包。换句话说,包只是一种特殊形式的模块。具体来说,包含__path__属性的任何模块都被视为包。
所有的模块都有名字。子模块的名字是通过点号从父模块中分离出来的,和Python标准的属性访问语法相似。因此,您可能有一个名为sys的模块和一个名为email的软件包,其中包含一个名为email.mime的子包,名为email.mime.text的子包。
2.1 普通包 Python defines two types of packages, regular packages and namespace packages. Regular packages are traditional packages as they existed in Python 3.2 and earlier. A regular package is typically implemented as a directory containing an init.py file. When a regular package is imported, this init.py file is executed, and the objects it defines are bound to names in the package’s namespace.
在请求的处理中,已经知道在请求处理的最后,会调用Response的render来生成页面。这里来研究下页面是如何形成的。
以入口地址的处理为例:
处理/请求的controller为addon.web.controllers.main.Home,定义在web模块中,处理方式是直接跳转到/web。
处理/web请求的controller同上,使用web.webclient_bootstrap这个模版来生成页面。
class Home(http.Controller): @http.route('/', type='http', auth="none") def index(self, s_action=None, db=None, **kw): return http.local_redirect('/web', query=request.params, keep_hash=True) @http.route('/web', type='http', auth="none") def web_client(self, s_action=None, **kw): ensure_db() if not request.session.uid: return werkzeug.utils.redirect('/web/login', 303) if kw.get('redirect'): return werkzeug.utils.redirect(kw.get('redirect'), 303) request.uid = request.session.uid context = request.env['ir.http'].webclient_rendering_context() return request.render('web.webclient_bootstrap', qcontext=context) 在介绍页面生成之前,先熟悉下可能会用到的模型
模型 odoo.addons.base.ir.ir_ui_view.View 在response.render函数中,需要用到ir.ui.view模型来生成页面。下边就是这个模型的定义。从中我们可以知道在数据库中一定会有一张表ir_ui_view,同时这个模型会在ir_model注册,模型的字段会在ir_model_fields中记录。其中一个特殊的字段是type,从定义猜测View的种类就是列表中的那几种。
View模型还提供了如下功能:
根据模版生成页面 render_template(仅仅算是入口函数) 查询模版ID get_view_id
select ir_model_data.id from ir_model_data where module='web' and name='webclient_bootstrap' 模版读取 read_template、read_template、read_combined
经过漫长的阅读代码,搞清了启动的过程。先简单做个总结。如有遗漏之后再做补充。
系统的启动,模块的加载 结合之前研究过的registry,系统启动时会发生如下动作:
首先加载全局模块web,web_kanban。(在没有确定数据库地址之前,只能显示数据库选择页面。所以这个时候只需要这两个模块就可以了) 在import controller时,由于元类的作用,controller类会自动加载到解释器中 根据配置创建web服务器(线程的、进程的),所有的服务器都使用odoo.service.wsgi_server.application来处理请求。 具体处理请求的是odoo.http.Root 根据需要看要不要再次加载插件 只有当首次接收请求的时候,才会执行加载插件 启动服务器 在启动web服务器之前,首先创建registry。(在进程的实现中,registry会在进程fork之前创建,fork之后registry会被拷贝到各个进程的内存空间中) 当数据库选定之后,registry会根据配置去加载模块 先加载base模块 先创建base模块的依赖关系图graph 使用graph加载base 获取模块中的所有模型 组装配置模型类 根据模型的属性,创建新的模型类,并将模型类注册到registry中 根据模型的属性,为新的模型类添加字段,关联关系等 初始化模型 根据模型类,创建模型类对应的表 装载定义在__manifast__.py中的模块的数据 获取文件列表 调用odoo.tools.convert.convert_file装载文件。 判断文件类型,根据文件类型使用不同的方法解析 根据情况将数据写入ir.model.data 根据情况将数据写入模型自己的数据表中 根据配置标注其它需要加载的模块 根据标注加载模块 使用graph进行模块的记载(下边以web模块为例,看一下模块数据的加载) 获取数据文件:views/webclient_templates.xml 调用odoo.tools.convert.convert_file装载文件 创建xml专用的解析器对象xml_import 解析xml文件 遍历整个xml文档树,根据节点的类型调用不同的函数来进行处理。具体到views/webclient_templates.xml,这个文件由template组成,对应的函数是_tag_template。这个函数在结尾调用_tag_record,这个函数会将数据文件里的内容写入ir_model_data表中。 模块加载完毕后,服务器开始运行。等待处理请求。 请求处理 首次接受到请求 odoo.