阅读视图

发现新文章,点击刷新页面。
☑️ ⭐

2024年终总结 - 稳中求变

2025年啦,大家过得都可还好?

时间转瞬即逝,2024年就过完了,不得不感叹一年时间还是蛮短暂的!

回顾2024:

2024骑着第一辆公路车参加了2023的跨年骑行,千人规模很是壮观,以后是再也不会有了。

2024组装了第一台真正意义上的NAS,五盘位,8T,结合Apple TV该享受的还是要享受。

2024学习了swift与swiftui,与朋友空闲时间开发了一款临时邮箱应用,突破了独开零收入门槛。

2024读了6本书,远未达到最初设定的10本书的目标,看来执行力还是差了些。

2024为小朋友组装了一个大玩具:水管攀爬架,挺爱玩,大运动好了不少。

2024总共骑行了400来公里,都没别人跑步跑得多,看来还得找个骑行搭子。

2024开始关注保险,为家人安排了重疾险,寿险。

2024房贷利率降低了不少,还贷压力也有部分减轻,但依旧是个重担。

2024不少地方发生战争,如乌克兰俄罗斯,还有各种事故,只能说珍惜当下,天灾还是人祸不知道哪一个先来。

2024特朗普竞选总统成功,币圈高涨,可惜没能抓住,错失成为首富的机会。

2024陪着我的小家伙又成长了一年,每次看到他的笑容,喊着爸爸,心里都是感动。

2024家里第一次开了地暖,室内外温差十几二十度,家里真的是舒服。

2024奶奶九十大寿,真的是儿孙满堂,实实在在的印证了家有一老,如有一宝。

2024工作平稳,但有个盼头,来年努力为公司输出,一同成长。

2025愿世界和平,愿家人安康,愿陌生的你快乐幸福,愿风调雨顺!

本文链接:https://deepzz.com/post/2024-year-end.html参与评论 »

☑️ ⭐

如何玩转智能家居 - 家庭组网方案选择

为什么会有家庭组网这个说法?其根本原因是想要实现全屋网络覆盖。结合时下流行的智能家居,相辅相成。

目前,大多数家庭的网络仅靠那么一个路由器支撑,但路由器的网络覆盖范围往往都会有局限,就比如信号穿堵墙就会有很大的衰减,家里的角落甚至有时搜索不到信号。那么一般我们的解决方案有哪些呢?

  1. 换一个更好更强的路由器,天线更多,窗墙能力更强,4根的,6根的,越多越强
  2. 使用WiFi放大器,将信号进行增强。一个不够用两个,两个不够用多个
  3. 路由器桥接模式,当前路由器信号不好的时候切换成信号好的
  4. 家庭组网方案:电力猫、有线/无线Mesh、AC(POE)+AP

前面三种有各种各样的缺点,比如别墅这种大空间用一个再NB的路由器也是覆盖不全的;WiFi放大器这种东西网速会有损耗,稳定性得不到保证;路由器桥接,由于SSID不同,设备不会自动切换路由器,只能手动切换,不方便。因此,前三种方案均不推荐。

我们这里着重说家庭组网方案。这几种方案都能解决全屋网络覆盖的问题,具体选择哪种需根据情况来决定。

电力猫

电力猫用的传输技术是正交频分复用(OFDM),简单点说就是把互联网信号和电力信号叠加到一起。 power-cat

我们平时用的电都是交流电,频率是50HZ,电力猫就是在交流电的基础上叠加上互联网的信号。因为互联网的信号频率都很高(10MHz甚至更高)所以如果采用合适的手段,是不会对电力传输产生影响的。

不过要注意,电力猫有以下缺点:

  • 必须在同一电表下,不在同一电表下将不会进行数据传输
  • 如果电力线采用三相供电设计,电力猫的作用就会受到严重限制,速度会降低
  • 品质低下的电力猫存在散热和噪音问题
  • 电力猫的稳定性容易受到滤波产品、充电器、大功率电器的干扰

一般的,如果家里现有的网络布线中没有预埋网线且不能够增加网线的情况下建议采用这种方式组网。

无线/有线Mesh

什么Mesh组网?又称网格网络,即家里的网络通过节点组建的方式实现网格化,网络中所有的节点都互相连接,并且每一个节点至少连接其他两个节点,所有的节点之间形成一个整体的网络。 mesh-networking

整个Mesh网络中有一个主节点,主节点用来进行节点信息的同步。

组网后,会生成一种网状网络,不同接入点可以以星状、树状、串联和总线方式,混合组网。在这个网络中,SSID统一,无线设备还可以自由寻找信号最好的节点去连接传输数据,用户手持设备在不同节点间,穿梭时无线网络是无缝切换的,实现较好的漫游效果,漫游过程中,数据丢包,延时,抖动越低,网络质量越好。

mesh-networking

Mesh组网分无线组网和有线组网(有线/无线是指Mesh网络中节点的连接方式):

1、无线组网

优点是可以不受空间的限制,可以在需要增加节点的时候随意增加,实现家庭的网络全覆盖。

缺点也是有的,就比如说所有节点均需要的一个供电插座,摆放位置,对于强迫症患者来说是致命的。

2、有线组网

优点是可以通过主路由POE供电方式(POE供电是通过网线为节点供电)和面板子路由形式进行隐藏式安装,美观,比如华为H6。

缺点是后期不能够随意增加节点,因为网口的位置和个数都是固定,如果前期没有提前规划好就会比较痛苦。

AC(POE)+AP

AC是指无线控制器(Wireless AccessPoint Controller),是一种网络设备,用来集中化控制局域网内可控的无线AP,是一个无线网络的核心,负责管理无线网络中的所有无线AP,对AP管理包括:下发配置、修改相关配置参数、射频智能管理、接入安全控制等。

AP即无线访问接入点(Wireless AccessPoint),传统有线网络中的HUB。AP相当于一个连接有线网和无线网的桥梁,其主要作用是将各个无线网络客户端连接到一起,然后将无线网络接入以太网,从而达到网络无线覆盖的目的。

目前该组网方式最常用于企业等大型对网络要求较高的商业场所,该组网方式稳定,简单。目前也用于对网络要求较高的家庭组网中。我们这里聊一聊AC+AP组网的特点。

由于AC+AP组网方式中,一般为有线组网,我们这里也只讨论该组网形式。

1、对于AC

常接触到关键词是POE(Power Over Ethernet),一般还是建议采用POE为AP供电的方式,这样更加方便。

2、对于AP

分为面板AP和吸顶AP。面板AP是一种隐藏式安装形式,这种必须要使用POE供电方式才行,非常美观,但覆盖范围相较于吸顶AP会稍微逊色,100平建议2-3个。吸顶AP安装在吊顶处,覆盖范围较广,100平安装1-2个就好。

由于目前我家是新装修,所以博主采用:AC(POE)+AP:

啊,之前买的 479GPE 不能放进弱电箱。重新买了个4010GP,它有磁铁可以直接粘在弱电箱上。

FTTR

FTTR(Fiber To The Room)即光纤到室的说法。FTTR组网方案采用了一个主光猫,多个从光猫的方式进行网络组网,光猫与光猫之间通过光纤进行连接,由于光纤比较细小,即使显示布线也不会太影响美观: fiber-to-the-room

有兴趣的朋友可以去了解下。我认为FTTR是未来十年发展的一种趋势,但现其高昂的布设成本劝退了我,待后期方案成熟,将网线替换成光纤也是可行的。

下文将介绍博主具体组网方案和思考。

参考文档

[1] https://wenku.baidu.com/view/b001ec4f33687e21af45a953.html
[2] ​https://www.zhihu.com/question/410166038/answer/1989571138
[3] https://zhuanlan.zhihu.com/p/296788149

本文链接:https://deepzz.com/post/home-networking.html参与评论 »

☑️ ⭐

如何玩转智能家居 - HomeAssistant接触

接上篇文章:如何玩转HomeAssistant - HA介绍

如何安装 HA,上篇文章说到了一些,本篇文章实战安装 HA,并介绍 HA 内部概念。通过两种方式安装:

  • 镜像安装
  • Docker安装

如果大家有更多需求,可以到官方站点查看:Installation

镜像运行

镜像安装,相当于安装一个操作系统,我们需要提前准备好虚拟机或者一个小型的设备(如树莓派或NUC等等)。

我这里准备的是 VirtualBox Linux 环境来进行安装 HassOS,镜像下载地址在 Github 上:home-assistant/operating-system

1. Create a new virtual machine
2. Select “Other Linux (64Bit)
3. Select “Use an existing virtual hard disk file”, select the VDI file from above
4. Edit the “Settings” of the VM and go “System” then Motherboard and Enable EFI
5. Then “Network” “Adapter 1” Bridged and your adapter.

virtualbox-install-ha

Docker运行

容器运行对于了解过的人应该是比较喜欢的一种方式。我们这是使用 docker 做演示。

首先,我们得有一个安装 docker 环境的机器:docker安装,选择一个合适的 HA 镜像版本。运行方式很简单:

docker run --init -d \
  --name homeassistant \
  --restart=unless-stopped \
  -e TZ=Asia/Shanghai \
  -v /PATH_TO_YOUR_CONFIG:/config \
  --network=host \
  homeassistant/home-assistant:stable

替换 PATH_TO_YOUR_CONFIG 为本地配置路径,容器运行起来之后会监听 :8123 端口,启动过程可能会耗点时间。通过访问 http://<host>:8123 即可。

HA 内在

HA 初次接触是一个新鲜事物,它的内部有很多概念,如集成、设备、服务和实体等。提前理解这些概念有助于我们快速上手,玩转 HA。

设备和服务

集成是 HA 中重要的概念,串联着整个系统。那么什么是集成?

集成可以说集成设备,集成服务,或者说设备和服务是集成的一种抽象体现。而实体(Entity)是通过集成(如 lightswitch 等)进行标准化的(如小米灯通过 light集成 集成了 Entity)。标准化实体附加了用于控制的服务。

实体将 HA 的内部工作抽象化。作为集成商,不必担心服务或状态机的工作方式。相反,可以扩展实体类并为要集成的设备类型实现必要的属性和方法。

integrating-devices-services

  1. Device Integration(ie. hue)将使用此配置来建立与设备/服务的连接。它将转发 Config Entry(传统使用发现助手)以在其各自的集成(lightswitch)中设置实体。device Integration 还可以为未标准化的事物注册自己的 Services。这些服务在集成的域下发布 hue.activate_scene

  2. Entity Integration(i.e. light)负责定义抽象实体类和服务来控制实体。

  3. Entity Component 帮助程序负责将配置分发到平台,转发发现并收集用于服务调用的实体以。

  4. Entity Platform 帮助程序管理该平台的所有实体,并在必要时轮询它们以获取更新。添加实体时,entity platform 负责将实体注册到设备和实体注册表中。

  5. Integration Platform(i.e. hue.light)使用配置来查询外部设备/服务,并创建要添加的实体。integration platform 还可以注册实体服务。这些服务将在设备集成的所有实体上进行实体集成(即所有 Hue light 实体)。这些服务在设备集成域下发布。

1、实体与 HA Core 交互

从实体基类集成的集成实体类负责获取数据并处理服务调用。如果禁用了轮询,则它还负责告知 HA 数据何时可用。

entity-core-interaction

实体基类(由实体集成定义)负责格式化数据并将其写入状态机。

实体注册表将为 unavailable 当前未由实体对象支持的任何注册实体写入状态。

2、实体数据层次

entity-data-hierarchy

删除,禁用或重新启用任何对象,下面的所有对象都将进行相应调整。

什么是实体

什么是实体,实体注册表(Entity Registry)?我认为是 HA 中智能设备所能划分的最小单元,也可以理解为控制单元,如空气净化器中的温度传感器上报视作一个实体。

每个实体均有 Unique ID,该ID不能被用户更改,否则造成数据不一致情况。如果一个设备只有一个ID,但提供多个实体,我们可以这样标识 {unique_id}-{sensor_type}

切记 Unique ID 必须全局唯一,且不可变,一般用 MAC 地址。

什么是设备

什么是设备,设备注册表(Device Registry)?HA 中的设备代表具有自己的控制单元的物理设备,它位于一个特定的地理区域,通常由一个或多个实体表示。举个例子,一台空气净化器是一台设备,它所包含的温度、湿度和PM2.5传感器(控制单元)所暴露的我们可以认为是实体。

但是一个实体(如温度传感器)如果拆解出来,也可以是一个独立的设备,这里更多的其实就是一个从属关系的划分(设备可以视作实体,实体可以视作设备),具体的实体或设备的划分自行考虑。配置实体、实体、设备之间的关系如下图:

ha-device-entity

Config Entry 配置了指定的 Entry,该 Entry 可能关联着某个 Device。一个设备通常有如下属性:

属性 描述
id HA 生成的唯一ID
name 设备的名称
connections connetion_type, connection_identifier的集合
identifiers 标识符集合,外界的设备识别号,如序列号
manufacturer 设备制造商
model 设备模型
suggested_area 建议设备区域
config_entries 联接该设备的实体
sw_version 设备防火墙版本
via_device 设备与 HA 之间路由消息的设备标识符
area_id 区域ID
entry_type 实体类型,None 或者 “service”

通过设备注册表来进行管理。

什么是区域

什么是区域,区域注册表(Area Registry)?区域应该是最好理解的,它用来定义区域,如客厅,卧室A,厨房等,代表了一个具体的物理位置,它可以帮助我们归集和标记设备的具体区域。

通过区域注册表来进行管理。

什么是Blueprint

蓝图,是可重复使用的自动化,可以轻松共享。您可以从 Github 和社区论坛导入其他用户的 Blueprint。

什么是自动化

其描述很清晰,为智能家居指定自动化规则。即在什么情况下想要使得智能家居做出什么样的反应。

什么是场景

定格一组设备的状态,日后即可一键恢复。也就是说在实际使用过程中,你可能有一个固定的场景或者模式,比如家庭影院。

什么是脚本

执行一系列动作,相当于指定流水线。可以自行考虑考虑

什么是lovelace

相当于是一个主题,你可以在这个主题上做自己的定制。

HA 初识

当 HA 运行起来之后,你可以通过 <ip>:8123 进行访问,默认会根据浏览器进行对应语言的显示(如中文),通过一些简单的配置就可以开启我们的智能家居之旅了。

首先,需要我们初始化一个账户,也就是管理员账户,《创建账户》。

然后,设置 HA 的名称,选择时区和单位等,至于定位可能不准需要自己选择,《下一步》。

然后,HA 会自动发现你网路中的相关设备和服务,如果现在不是很清楚可以直接跳过,《完成》。

现在,我们进入 HA 首页,你的折腾开始了:

ha-home

  • 概览,也就是仪表盘,我们后续会将我们的智能设备通过卡片的形式添加到这里。可以在这里直接控制设备和查看设备状态。
  • 地图,能够显示相关位置信息,如人员、家的定位等
  • 日志,记录 HA 中发生相关事件
  • 历史,查看历史的事件信息
  • 媒体浏览器,查看一些硬件设备上的媒体
  • 开发者工具,可以帮助我们做一些调试动作
  • 配置,我们使用最多的,如集成、自动化等,和配置文件关系密切
  • 通知,一些通知,告警等
  • 人员,个人设置,如密码修改,主题等

配置文件

HA 的运行离不开配置,现在有两种配置方式,一是通过 HA 网页 UI 进行配置(需要相应的集成适配),二是通过 HA 配置文件进行配置。这里我们直接介绍配置文件:

├── automations.yaml                       # 自动化
├── blueprints                             # 蓝图
│   └── automation
│       └── homeassistant
│           ├── motion_light.yaml
│           └── notify_leaving_zone.yaml
├── configuration.yaml                     # 主配置文件
├── deps                                   # 相关依赖
├── groups.yaml                            # 分组
├── home-assistant_v2.db                   # sqlite3数据库
├── scenes.yaml                            # 场景
├── scripts.yaml                           # 脚本
├── secrets.yaml                           # 密钥
└── tts                                    # 文字转语音记录

默认情况下 HA 会自动创建上面的配置文件。具体的使用方式在后续的实践过程我们一一熟悉。

但想要玩转 HA 我们还会接触到更多的配置,如用来保存第三方开发组件的 custom_components 文件夹等(HA 会默认加载)。当然为了方便管理,我们也可以自己组织文件夹的组成。然后在 configuration.yaml 进行启用:

# Configure a default setup of Home Assistant (frontend, api, etc)
default_config:

# Text to speech
tts:
  - platform: google_translate

group: !include groups.yaml
automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml

!include 用来指定该字段内容的外部文件,而 !include_dir_merge_named 则用来指定文件夹。

之后的文章,我们通过一个个实战来熟悉 HA 的配置和使用,玩转 HA。

本文链接:https://deepzz.com/post/homeassistant-concept.html参与评论 »

☑️ ⭐

她和他的第一次出国自由行 - 出行模版

第一次出国自由行,心里难免有些慌张,哈。希望在这里记录下本次出行的经验,做一个模版供下次出行参考。

三要素

和写小说一样,地点人物时间是我认为最重要的三个要素。

地点,首先确定地点,有些地方真的值得前去。

  • 安全,安全永远放在第一位。
  • 目的,美景、美食、美人,总之这个地方一定要有吸引你的地方。
  • 交通,包车,租车,还是公共交通。当地交通的便利性决定了我们旅行的欢乐值。
  • 物价,当地物价,基于当地物价及预算需要确定是否前去。
  • (国际)语言,英语,日语…语言是我们交流的工具,也是我们重要的考虑因素。

人物,其次是人物,有同样目的地的人有时更容易达成目标。

  • 人数,总共多少人,关系着定机票、门票、酒店、包车大小。
  • 关系,人物关系,房间是安排亲子房还是其它,以及本次旅行的尴尬度。
  • 年龄,年龄大小,决定了本次游玩的项目危险程度,疲劳程度,或者安排分支行程。

时间,最后确定时间,时间是开始也是结尾。

  • 旅行天数,旅行时间一定要安排好,不能匆忙,一般5天以上为最佳。
  • 出发日期,确定出发日期便于定机票,酒店及其它日程安排。根据当地天气及人物假期恰当安排。

三者都确定好之后,我们需要根据这些情况做一个大概的预算。然后着手我们的旅行了。

提前准备

哈,旅行之前我们是需要准备好一些东西的。等一切准备就绪,带着人和行李就可以了。

机票,提前定好机票。国际旅行一定确定是否转机,行李是否直挂。

酒店,为了不每天的奔波及找酒店。有一个好的行程安排是轻松旅行的关键。酒店最好定在景点的周边,这样节省时间体力。

APP安装,旅行(飞猪,携程,马蜂窝),地图(谷歌,高德),民宿(airbnb,途家),出行(滴滴,Uber,grab),翻译(谷歌,百度),

(国际)资料,护照复印件,旅行线路图,打印酒店订单。

(国际)电话,当地大使馆紧急联络方式(地址,电话,邮件等)。当地紧急电话(报警,救护车,火警等)。

(国际)货币,是到当地兑换还是提前国内兑换(参考汇率)。一般可以先在国内兑换美元,再到当地用美元兑现当地货币。

所有东西都准备好了,就等着人带着你的行李出发啦。

物料准备

当我们的资料准备好了之后,机票、酒店均已定好。那么现在只却人过去了,当然人也得带着自己的随身行李了。

必备证件,切记一定把自己的证件带全,最好放在一个专用的包里。

  • 身份证
  • 学生证
  • 驾驶证
  • 银行卡
  • (国际)护照
  • (国际)信用卡
  • (国际)驾驶证

日常衣物,请根据当地气候天气携带日常衣物。

常用物品:

  • 墨镜
  • 内衣裤(一次性)
  • 拖鞋(一次性)

春夏:

  • T恤
  • 短裤
  • 防嗮衫
  • 牛仔裤
  • 泳装
  • 袜子(船袜,短袜)
  • 帽子(草帽,鸭舌帽)
  • 连衣裙
  • 吊带
  • 鞋(运动鞋,小白鞋等)

秋冬:

  • 羽绒服
  • 毛衣
  • 秋衣秋裤
  • 围巾
  • 手套
  • 雪地靴
  • 帽子
  • 口罩
  • 长袜

洗漱用品,最好携带洗漱用品,很多酒店民宿都不提供。

  • 牙刷牙膏
  • 一次性毛巾
  • 洗发水、护发素、沐浴露
  • 剃须刀

护肤用品,现在皮肤娇贵得很,一定记得携带护肤用品。

  • 水、乳、霜、精华
  • 面膜
  • 防嗮霜(喷雾)
  • 洗面奶、卸妆油
  • 化妆品等

备用药品,出门在外,总有磕磕碰碰,随身携带些药品备用。

  • 创口贴
  • 感冒药
  • 消炎药
  • 晕船药
  • 藿香正气胶囊
  • 驱蚊药

设备器材,携带什么样的设备代表你旅行的目的,嘿嘿。

  • 手机(&防水套)
  • 相机(&三脚架)
  • Kindle
  • iPad
  • 雨伞
  • 充电宝
  • 充电器(手机,相机,电脑等)
  • 电脑
  • 对讲机
  • (国际)电源转换插头(欧标、美标等)
  • (国际)变压器,国内是220v,不过充电器一般都是有适配范围。

(国际)网络通讯,一定有随时能通信的方式。

  • (国际)手机漫游,开通手机漫游业务。
  • (国际)购买当地电话卡,这将是一个非常方便的做法。
  • (国际)WiFi 租赁,考虑方便性,可以租赁个。

行程安排

提前计划好旅行的行程,拒绝盲目。一般建议定好前两天的行程,然后根据状态选择后面的行程。

如:

  • 2-10,双流T1-樟宜T2 23:20-04:10
  • 2-11,樟宜T2-伍拉赖I 06:55-09:35
    • 库塔海滩馨乐庭酒店
  • 2-12,佩尼达深度游
    • 库塔海滩馨乐庭酒店
  • 2-13,……
  • 2-14,……
  • 2-15,伍拉赖l-樟宜 13:00-15:40
    • 樟宜T2-双流T1 17:54-22:20

bali-time

本文链接:https://deepzz.com/post/our-travel-template.html参与评论 »

☑️ ⭐

Go 单元测试,基准测试,http 测试

对我们程序员来说,如何提高代码质量一定是我们的重中之重。不仅需要你能够写得一手的业务代码,还需要做的是如何保证你的代码质量。测试用例便是一个非常好的用来提高我们代码质量的工具。

通过测试,我们能够及时的发现我们程序的设计逻辑错误,并能够给接手项目的其它程序员同学理解函数有帮助。

本篇文章主要介绍 Go 语言中的 testing 包。它要求我们以 *_test.go 新建文件,并在文件中以 TestXxx 命名函数。然后再通过 go test [flags] [packages] 执行函数。

$ ls
db.go
db_test.go

$ cat db_test.go
package db

import "testing"

func TestGetUser(t *testing.T) {
    user, err := GetUser("test@example.com")
    if err != nil {
        t.Fatal(err)
    }
    t.Log(user)
}

它也为我们提供了三种类型的函数:测试函数 T、基准测试函数 B、实例函数 Example。

Test 测试

函数测试,其基本签名是:

func TestName(t *testing.T){
    // ...
}

测试函数的名字必须以 Test 开头,可选的后缀名必须不以小写字母开头,一般跟我们测试的函数名。

类型 testing.T 有以下方法:

// 打印日志。对于测试,会在失败或指定 -test.v 标志时打印。对与基准测试,总是打印,避免因未指定 -test.v 带来的测试不准确
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string, args ...interface{})


// 标记函数失败,继续执行该函数
func (c *T) Fail()
// 标记函数失败,调用 runtime.Goexit 退出该函数。但继续执行其它函数或基准测试。
func (c *T) FailNow()
// 返回函数是否失败
func (c *T) Failed() bool


// 等同于 t.Log + t.Fail
func (c *T) Error(args ...interface{})
// 等同于 t.Logf + t.Fail
func (c *T) Errorf(format string, args ...interface{})


// 等同于 t.Log + t.FailNow
func (c *T) Fatal(args ...interface{})
// 等同于 t.Logf + t.FailNow
func (c *T) Fatalf(format string, args ...interface{})


// 将调用函数标记标记为测试助手函数。
func (c *T) Helper()

// 返回正在运行的测试或基准测试的名称
func (c *T) Name() string

// 用于表示当前测试只会与其他带有 Parallel 方法的测试并行进行测试。
func (t *T) Parallel()

// 执行名字为 name 的子测试 f,并报告 f 在执行过程中是否失败
// Run 会阻塞到 f 的所有并行测试执行完毕。
func (t *T) Run(name string, f func(t *T)) bool


// 相当于 t.Log + t. SkipNow
func (c *T) Skip(args ...interface{})
// 将测试标记为跳过,并调用 runtime.Goexit 退出该测试。继续执行其它测试或基准测试
func (c *T) SkipNow()
// 相当于 t.Logf + t.SkipNow
func (c *T) Skipf(format string, args ...interface{})
// 报告该测试是否是忽略
func (c *T) Skipped() bool

Benchmark 测试

函数测试,其基本签名是:

func BenchmarkName(b *testing.B){
    // ...
}

测试函数的名字必须以 Benchmark 开头,可选的后缀名必须不以小写字母开头,一般跟我们测试的函数名。

B 类型有一个参数 N,它可以用来只是基准测试的迭代运行的次数。基准测试与测试,基准测试总是会输出日志。

type B struct {
    N int
    // contains filtered or unexported fields
}

基准测试较测试多了些函数:

func (c *B) Log(args ...interface{})
func (c *B) Logf(format string, args ...interface{})
func (c *B) Fail()
func (c *B) FailNow()
func (c *B) Failed() bool
func (c *B) Error(args ...interface{})
func (c *B) Errorf(format string, args ...interface{})
func (c *B) Fatal(args ...interface{})
func (c *B) Fatalf(format string, args ...interface{})
func (c *B) Helper()
func (c *B) Name() string
func (b *B) Run(name string, f func(b *B)) bool
func (c *B) Skip(args ...interface{})
func (c *B) SkipNow()
func (c *B) Skipf(format string, args ...interface{})
func (c *B) Skipped() bool


// 打开当前基准测试的内存统计功能,与使用 -test.benchmem 设置类似,
// 但 ReportAllocs 只影响那些调用了该函数的基准测试。
func (b *B) ReportAllocs()

// 对已经逝去的基准测试时间以及内存分配计数器进行清零。对于正在运行中的计时器,这个方法不会产生任何效果。
func (b *B) ResetTimer()
例:
func BenchmarkBigLen(b *testing.B) {
    big := NewBig()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        big.Len()
    }
}

// 以并行的方式执行给定的基准测试。RunParallel 会创建出多个 goroutine,并将 b.N 个迭代分配给这些 goroutine 执行,
// 其中 goroutine 数量的默认值为 GOMAXPROCS。用户如果想要增加非CPU受限(non-CPU-bound)基准测试的并行性,
// 那么可以在 RunParallel 之前调用 SetParallelism。RunParallel 通常会与 -cpu 标志一同使用。
// body 函数将在每个 goroutine 中执行,这个函数需要设置所有 goroutine 本地的状态,
// 并迭代直到 pb.Next 返回 false 值为止。因为 StartTimer、StopTimer 和 ResetTimer 这三个函数都带有全局作用,所以 body函数不应该调用这些函数;
// 除此之外,body 函数也不应该调用 Run 函数。
func (b *B) RunParallel(body func(*PB))
例:
func BenchmarkTemplateParallel(b *testing.B) {
    templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    b.RunParallel(func(pb *testing.PB) {
        var buf bytes.Buffer
        for pb.Next() {
            buf.Reset()
            templ.Execute(&buf, "World")
        }
    })
}


// 记录在单个操作中处理的字节数量。 在调用了这个方法之后, 基准测试将会报告 ns/op 以及 MB/s
func (b *B) SetBytes(n int64)

// 将 RunParallel 使用的 goroutine 数量设置为 p*GOMAXPROCS,如果 p 小于 1,那么调用将不产生任何效果。
// CPU受限(CPU-bound)的基准测试通常不需要调用这个方法。
func (b *B) SetParallelism(p int)

// 开始对测试进行计时。
// 这个函数在基准测试开始时会自动被调用,它也可以在调用 StopTimer 之后恢复进行计时。
func (b *B) StartTimer()

// 停止对测试进行计时。
func (b *B) StopTimer()

Example 测试

示例函数可以帮助我们写一个示例,并与输出相比较:

func ExampleHello() {
    fmt.Println("hello")
    // Output: hello
}

func ExampleSalutations() {
    fmt.Println("hello, and")
    fmt.Println("goodbye")
    // Output:
    // hello, and
    // goodbye
}

// 无序输出 Unordered output
func ExamplePerm() {
    for _, value := range Perm(4) {
        fmt.Println(value)
    }
    // Unordered output: 4
    // 2
    // 1
    // 3
    // 0
}

关于示例函数我们需要知道:

  • 函数的签名需要以 Example 开头
  • 输出的对比有有序(Output)和无序(Unordered output)两种
  • 如果函数没有输出注释,将不会被执行

官方给我们的命名的规则是:

// 一个包的 example
func Example() { ... }
// 一个函数 F 的 example
func ExampleF() { ... }
// 一个类型 T 的 example
func ExampleT() { ... }
// 一个类型 T 的方法 M 的 example
func ExampleT_M() { ... }

// 如果以上四种类型需要提供多个示例,可以通过添加后缀的方式
// 后缀必须小写
func Example_suffix() { ... }
func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }

子测试

上面我们也说到了 Test 和 Benchmark 的 Run 方法,它用来执行子测试。

func TestFoo(t *testing.T) {
    // <setup code>
    t.Run("A=1", func(t *testing.T) { ... })
    t.Run("A=2", func(t *testing.T) { ... })
    t.Run("B=1", func(t *testing.T) { ... })
    // <tear-down code>
}

每个子测试可以用一个唯一的名字表示:顶级测试的名称和传递给 Run 的名称序的组合,用 / 分隔。

go test -run ''      # 运行所有测试
go test -run Foo     # 匹配 Foo 相关的顶级测试,如 TestFooBar
go test -run Foo/A=  # 匹配 Foo 相关的顶级测试, 并匹配子测试 A=
go test -run /A=1    # 匹配所有顶级测试, 并匹配它们的子测试 A=1

子测试也可以用来控制并行性。父级测试只有在完成所有子测试后才能完成。在这个例子中,所有的测试都是相互平行的,并且只与对方一起运行,而不管可能定义的其它顶级测试:

func TestGroupedParallel(t *testing.T) {
    for _, tc := range tests {
        tc := tc // capture range variable
        t.Run(tc.Name, func(t *testing.T) {
            t.Parallel()
            ...
        })
    }
}

运行直到并行子测试完成才会返回,这提供了一种在一组并行测试后进行清理的方法:

func TestTeardownParallel(t *testing.T) {
    // This Run will not return until the parallel tests finish.
    t.Run("group", func(t *testing.T) {
        t.Run("Test1", parallelTest1)
        t.Run("Test2", parallelTest2)
        t.Run("Test3", parallelTest3)
    })
    // <tear-down code>
}

Main 测试

有时候我们也需要从主函数开始进行测试:

func TestMain(m *testing.M)

例:
func TestMain(m *testing.M) {
    // call flag.Parse() here if TestMain uses flags
    os.Exit(m.Run())
}

HTTP 测试

Go 语言目前的 web 开发是比较多的,那么在我们对功能函数有了测试之后,HTTP 的测试又该怎样做呢?

Go 的标准库为我们提供了一个 httptest 的库,通过它就能够轻松的完成 HTTP 的测试。

1、测试 Handle 函数

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "net/http/httptest"
)

var HandleHelloWorld = func(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "<html><body>Hello World!</body></html>")
}

func main() {
    req := httptest.NewRequest("GET", "http://example.com/foo", nil)
    w := httptest.NewRecorder()
    HandleHelloWorld(w, req)

    resp := w.Result()
    body, _ := ioutil.ReadAll(resp.Body)

    fmt.Println(resp.StatusCode)
    fmt.Println(resp.Header.Get("Content-Type"))
    fmt.Println(string(body))
}

2、TLS 服务器?

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/http/httptest"
)

func main() {
    ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello, client")
    }))
    defer ts.Close()

    client := ts.Client()
    res, err := client.Get(ts.URL)
    if err != nil {
        log.Fatal(err)
    }

    greeting, err := ioutil.ReadAll(res.Body)
    res.Body.Close()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s", greeting)
}

3、常用的 HTTP 框架又如何测试?

// Package main provides ...
package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"

    "github.com/gin-gonic/gin"
)

func main() {
    engine := gin.Default()
    engine.GET("/hello", func(c *gin.Context) { c.String(http.StatusOK, "Hello") })
    engine.GET("/world", func(c *gin.Context) { c.String(http.StatusOK, "world") })

    req := httptest.NewRequest(http.MethodGet, "/hello", nil)
    w := httptest.NewRecorder()

    engine.ServeHTTP(w, req)

    fmt.Println(w.Body.String())
}

本文链接:https://deepzz.com/post/study-golang-test.html参与评论 »

❌