看隔壁關於 ORM 的討論說 Ruby/Rails 的長處都被其他語言學走了,但我覺得至少 RSpec 在 JS 世界還沒有替代。以下是一個用 RSpec 寫測試的例子:
require 'rspec'
require 'matrix'
RSpec.describe 'numbers' do
# 描述加法的性質
shared_examples 'addition' do
# 加法滿足交換律
# a 和 b 會在實際的 context 裏注入
it 'has commutativity' do
expect(a + b).to eq(b + a)
end
end
context 'for number' do
# 令 a 和 b 為兩個隨機數,並調用加法的測試用例
let(:a) { Random.rand }
let(:b) { Random.rand }
it_behaves_like 'addition'
end
context 'for vector' do
# a 和 b 可以是向量,也滿足加法的性質
let(:a) { Vector[Random.rand, Random.rand] }
let(:b) { Vector[Random.rand, Random.rand] }
it_behaves_like 'addition'
end
end
如果要用 js 寫,至少所有的 a 和 b 都要換成類似context.a
context.b
,因為 ruby 有 self ,而 js 只有詞法作用域,這導致 js 的 dsl 表達力很受限。
另外在 ruby 裏也可以很輕易做 mock ,例如這個例子修改了Date.today
來返回我們測試用的日期:
require 'rspec'
require 'date'
RSpec.describe 'Date' do
# 選定一個測試用的日期,一般來說實際會使用隨機的
let(:date) { Date.parse('1970-01-01') }
before do
# Date.today 本身是標準庫定義好的,但我們可以覆寫它來返回我們的日期
allow(Date).to receive(:today).and_return(date)
end
describe '.today' do
it 'is the mocked date' do
# Date.today 現在會是我們 mock 的日期 1970-01-01
expect(Date.today).to eq(date)
end
end
end
![]() |
1
dssxzuxc 13 天前 ![]() ror 最优秀的地方在于极致地实现约定大于配置,以及魔法般的 DSL 。
有什么好处?就是极致地少写代码。 不同人有不同的喜好,就像我追求的是接近完美的类型安全,多写代码无所谓,反正现在大部分活是 AI 干的。而类型安全让我几乎杜绝各种傻逼运行时错误,可以安心地睡觉。你要说人菜那我无话可说,我本来就是一名普通的程序员,一定会犯错误,我只能希望能少犯错误。Ruby 很优秀与 Ruby 不适合团队协作并不冲突。 |
![]() |
2
ericguo 13 天前 ![]() @dssxzuxc 多写代码费 token 啊。。。能少写一点总是好的,只要没有歧义,我觉得 ruby 甚至可以当作以后所有其他语言的 LLM 转换的源语言用。没有明面上的类型干扰,更加容易让 LLM 关注逻辑,从逻辑推断出各个语言应该使用的类型或者需要导入的库。
|
![]() |
4
NineTree 13 天前
看不懂,但是感觉很厉害
|
![]() |
5
qiumaoyuan 13 天前
@dssxzuxc 我觉得不是适合不适合团队合作,而是适合什么样的团队合作。我还是那句话,程序员写代码时的思维应该是主动的、清醒的、活跃的,而不是被动的、怠惰的。当然如果你习惯把一个方法的代码写得超过 10 行,那确实大脑容易超载。
|
6
visper 13 天前
ruby 都已经没人用了吧。
|
7
dddd1919 13 天前 ![]() @dssxzuxc #1 除此之外觉得还有一点就是 all in one 的思路,通过这种 add-in 可以在一个工程内解决各种各样的问题。测试除了 Rspec ,还用过 Capybara 处理模拟 web 的测试,用 factory girl 构造铺底数据,用 waiter 去做自动化灰盒,这一切都是一个工程统统搞定,到现在其他生态很难找到这么完整的工具生态
|
![]() |
8
catamaran 13 天前
@qiumaoyuan 如果你的代码每个方法都不超过 10 行,我想可以称为神人了
|
9
kakki 13 天前
这种手工造 DSL 的语言根本不适合有一定规模的团队合作,对于代码民工来说,架构设计为做填空题就行,无论谁来拉翔都破坏力有限,Ruby 比较适合小规模专家级团队,尤其是独立开发.
|
![]() |
10
qiumaoyuan 13 天前
@catamaran 不要自己吓自己,也不要限制自己。我觉得主要是很多人遇到问题习惯性的选择应付,而不是死磕。看看多数人对“屎山”的态度,几乎没有人选择去清理干净,增强自己能力的,而是能跑就行。在“屎山”的基础上,遇到 bug 的态度是兵来将挡水来土掩,不考虑怎么系统性的解决,除非实在应付不了,才会逼自己努力思考一下。
|
![]() |
11
zpvip 13 天前
@kakki 我一直搞不明白很多团队的代码合并流程, 团队成员发 pull request 时不同时提交测试吗? pull request 合并前没有人审核代码吗?
如果新代码能通过各种测试, 有两人以上 approved, 能有什么破坏力? |
12
Configuration 13 天前
实际上 RSpec 在 ruby 社区争议挺大的,喜欢的人很喜欢,讨厌的人很讨厌
|
![]() |
13
cloudzhou 13 天前
说起这个测试用例,接触 ai 后,平时补充代码不让我惊讶
ai 来写测试代码,那真是又稳又细,修修改改就可以用了 所以 ai 目前还没有到创造性的时候,擅长于已有的数据下,分析进行后续处理 |
![]() |
15
cloudzhou 13 天前
@zpvip #11 按照我经历过的公司,没有哪家真的测试用例 80% 以上的,都是主流程走一走,甚至依靠一些白盒测试,而人日压缩越发严重,压力很大,每次接需求就是填坑而已
之前我从 Java 世界到 Django ,简直蜜月期 后来为什么去掉幻想呢?从我需要大批量修改某个变量开始 比如说有一个广泛大量使用的表,我因为业务需求,需要字段重命名 nameXXX -> nameYYY 如果是静态语言,那么 ide -> refactor/rename 等,一把搞定 同理 看某个全局变量哪里引用,对应修改逻辑 我不知道这么多年,脚本语言是否改进了,在当时的话,是依靠 grep + 人肉 但是,我修改了好多次,发现总是漏了一些地方 起码 Python 来说,是运行时解析,到对应代码,才抛出错误 ror 不知道是否完善一些 |
![]() |
16
msg7086 13 天前
|
17
lithium4010 13 天前
jest-plugin-set
|
![]() |
18
zpvip 13 天前
@cloudzhou #15
Rails 的标准 CRUD 的 DB Migration, Controller, Model, View 还有 RSpec / Minitest 都是命令行生成的, 如果就真是标准的 Restful, 那测试就是 100% 覆盖且通过, 此时你还没有写一行代码, 只运行了一行命令, 例如: bin/rails generate scaffold Post title:string body:text 具体什么过程, 可以去这看视频: https://kamal-deploy.org, 从 9:54 开始. 我一般会把 View 模板选做好, 不像 DHH 演示那样阳春. 当然实际情况不是这么简单, 各种一对多, 多对多关系, Post 嵌套 Comments 之类的, 补充一些测试用例也非常简单, 另外会提前安装测试相关的 Gems, 比如: gem "capybara" gem "selenium-webdriver" gem 'webmock', '~> 3.25' gem 'shoulda-matchers' gem 'rspec-rails' gem "factory_bot_rails", gem "faker", "~> 3.5" 现在有了 AI, 它自己会发现这些 Gems 并用这些 Gems 去写 RSpecs. 所以覆盖率 95% 以上是不难的. 一般 Rails 程序员都会有个启动模板, 也可以用别人现成的. https://github.com/nickjj/docker-rails-example 字段重命名的事, IDE 批量替换就可以了吧, 如果测试覆盖率好, 一下就可以找出漏网之鱼吧. 当然, 最简单的方法就是不改旧代码, 加个别名就可以了, 李白, 字太白, 号诗仙, 几个名字同时可用, 以后有心情了也可以一天改一处, 发现一处改一处, 天天提交 Git commit 还可以混 KPI. class User < ApplicationRecord alias_attribute :name_老字段名, :name_新改的字段名 end u = User.new u.name_老字段名 = "Alice" u.name_老字段名 # => "Alice" u.name_新改的字段名 # => "Alice" u.name_新改的字段名 = "Bob" u.name_老字段名 # => "Bob" 我以前做了十多年 C++, 副业也用 PHP 开发过流量很大的系统, 后来尝试换成 Ruby on Rails, 发现这才是网站开发的正确方式, 再后来发现全职 RoR 岂不是更爽, C++ 又不会给更多钱. 年纪大了为了能更适应就业市场, 也尝试用 Go/C#/Java/PHP/JS (React, NodeJs), 越用越气, 相比 Ruby on Rails 真是太麻烦了, 稍写两下就感叹, 在 Ruby on Rails 不就一句话的事吗, 各种测试写得头痛, 难怪覆盖率上不去. 语法方面, Java 的 Lamda 把我气笑了, 只有一个抽象方法的接口?! 真是老气横秋, 感觉有人按着我的头写代码, 的确是很好的防呆设计. AI 时代, 现在程序员一个顶十个, 全栈工程师应该更吃香, 希望我还能再战几年后再去卖红薯. |
![]() |
19
razertory 13 天前
是不是用 Scala 写出来也优雅
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import org.scalatest.{OneInstancePerTest, Outcome} import org.scalatest.wordspec.AnyWordSpec import scala.util.Random // 共享测试 trait AdditionBehaviors { this: AnyWordSpec with Matchers => // 用 def 而不是 val ,让子类可以延迟绑定 def a: Double def b: Double def additionBehavior(): Unit = { "addition" should { "be commutative" in { (a + b) shouldEqual (b + a) } } } } // 针对 Double 的测试 class DoubleAdditionSpec extends AnyWordSpec with Matchers with AdditionBehaviors { override def a: Double = Random.nextDouble() override def b: Double = Random.nextDouble() "Double" should { behave like additionBehavior() } } // 针对 Vector[Double] 的测试 import breeze.linalg.{DenseVector => Vec} class VectorAdditionSpec extends AnyWordSpec with Matchers with AdditionBehaviors { override def a: Double = Random.nextDouble() override def b: Double = Random.nextDouble() // 这里把 a 、b 换成向量 private val v1 = Vec(a, a) private val v2 = Vec(b, b) "Vector" should { behave like additionBehavior() } // 重新实现加法行为,因为这里是向量 override def additionBehavior(): Unit = { "addition" should { "be commutative" in { (v1 + v2) shouldEqual (v2 + v1) } } } } |
20
charles0 13 天前
QuickCheck 、Hypothesis 是不是都能实现这个?
|
![]() |
21
cloudzhou 13 天前
@zpvip 别名就算了,那肯定不是解决之道,清晰是第一位
我在想,是否有个语言,有 ror 的开发舒适度,同时具备静态编译过程(可选),然后还能编译成为一个独立 bin (类似 Go ) 也就是你在开发阶段,ror 现有开发方式;又是严格类型和语法,ide 完全可解析,比如变量名方法名错误及时提示(加个运行参数) 那就完美了 |
22
xgdgsc 13 天前 via Android
@cloudzhou https://github.com/erikedin/Behavior.jl?tab=readme-ov-file 可以试试 Julia ,就是目前编译独立 bin 在实现进程中,编译出比较大的一坨 dll 目前就可以,1.12rc 在测试编译出小体积的 dll 了。
|
![]() |
23
doraemon0711 12 天前 ![]() 我是力挺 rspec 的一派,因为我认为 rspec 完全发挥了 ruby 的高度定制 DSL 的能力,让写测试就像写文章一样顺畅
但我也充分理解一个对 ruby 不熟悉的人,或者母语非英语的人看到这些代码会一脸懵的情况,因为我刚开始用 rspec 是就是这样的 说回 ruby ,我认为其最显著的特点,是用**最简洁**的方式把面向对象发挥到了极致,当意识到 ruby 中任何定义的 class 都是 Class 的实例、`1+1`实际上是`1.+(1)`时,我才意识到什么叫完全面向对象的编程语言,你或许可以指责 ruby 性能不行,但了解过 ruby 的人一定不应该指责其语法不优雅 |
![]() |
24
flyqie 12 天前 via Android
说起来,有点好奇,ruby 很多设计确实不错,但现在用的似乎并不算多,为什么呢?
|
![]() |
25
mizuhashi OP @flyqie 我覺得這兩者是沒有關係的,像 react 的設計非常落伍但是用的人卻很多。從個人的角度我只知道自己很喜歡 ruby ,14 年的時候我要在 ruby python node 裏面選一個學,然後看了作者 matz 的書,很欣賞這個人,就堅定使用 ruby 了,後來也遇到了很多用 ruby 的有趣的人(當時在 qq 群裏的老師是一位寫 rpgmaker 腳本的)。我覺得技術背後的人/人文是很重要的,如果要為用的人少找個原因,大概就是有類似想法的人不是多數
|
![]() |
26
mizuhashi OP @razertory 應該是的,在 ruby 裏的實現方式是,給定一個代碼塊,如 { a },我們可以自由決定這個 a 的解讀方式。實際上這個 a 代表的 self.a ,然後 ruby 可以在執行每個代碼塊的時候指定 self 是什麼,再找到對應的 a 方法,只要具備類似的特性就能實現差不多的東西
|
28
kneo 12 天前 via Android
有没有可能,别人没有,是因为嫌弃?
|
![]() |
29
DOLLOR 12 天前
@mizuhashi
a 自动绑定到 self.a ,JS 曾经有类似的语法,也就是 with 块。 动态绑定 self ,在 JS 里也有类似的 call/apply/bind 。 还有 eval 、new Function ,可以玩各种很骚的操作。 但这些特性在 JS 里,通常不被视为优秀,而是糟粕,遭到唾弃。 或许这就是从“精致的小众玩意”转变到“流水线的大众工具”的代价吧 |
![]() |
30
qiumaoyuan 12 天前
@flyqie 语言优秀不优秀,跟用的人多不多没有太大关系。我觉得以逻辑严谨著称的群体,应该要看得清楚这点。
很多时候反而设计得很糟糕的东西更流行,每当有人谈到排行榜、流行程度,我就总是联想到 MooTools 和 jQuery 。MooTools 设计得十分优秀,无论是源码(喏,你真要看每个函数/方法 10 行左右的代码,MooTools 相当多时候都是),还是暴露出来的接口,都简洁得不像话,但受众就是少得可怜。 而 jQuery 满世界闻名,流行程度我就不用多说了。但如果你想要复用代码,它就一个插件机制可选,写出来的代码总是又臭又长。反观 MooTools 本身基于对象的设计,OOP 当中那么多的复用手段,它都完全可以利用。 jQuery 流行的原因是啥?我觉得就是看起来上手简单。但工具这东西,上手难度和往深了用之后的趁手程度往往是两回事。 另外,如果看排行榜的话,以前 C 和 C++ 一直是老大,这两年 AI 火了,Python 一下窜到第一,所以 Python 突然变成最优秀的语言了吗?明显不是这个原因,对吧? |
![]() |
31
qiumaoyuan 12 天前
说话 RSpec 这东西,我特别喜欢的是它的 context (以及它的各种别名:describe ,example_group 等),这玩意可以把测试场景划分得很细,可以无限层级地复用上下文,简直就是为测试时遇到的各种场景细微区别时,最大限度复用代码而生的。
当然 TestUnit 和 MiniTest 本身利用好 OOP 也可以做到一样的效果,就是要求使用者熟练掌握 OOP 各项特性。 |
![]() |
33
mizuhashi OP ![]() @qiumaoyuan rspec 寫起來很快樂,跑起來也很快樂,免費的快樂
|