技术控

    今日:135| 主题:49488
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] MiniTest is not "Just Ruby", it is "Just Rails"

[复制链接]
入骨的麻木 发表于 2016-10-11 03:35:41
119 2

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
MiniTestrecently have gained a lot of love and respect from Ruby community, as a “less magical” alternative to    RSpec(which is, apparently, “too complicated” for nowadays rubyists). The one quote proudly cited on each MiniTest release email, and in its README, is  
  rspec is a testing DSL. minitest is ruby. (Adam Hawkins,    “Bow Before MiniTest”)  
  (And the next slide explicitly says: “RSpec: less code & more magic; MiniTest: more code & less magic”.)
  Let’s study this statement to understand what is    goodRuby and what is    Ruby way—and how it is different from    Rails way.  
  Ways we solve tasks

  Let’s forget for a moment about testing frameworks and their features and just solve the task from scratch:
  We want to define “tests”. Each of them is set of ruby commands + phrase describing them. After definition, each of them would be performed, and descriptions used to print output.
  For Rubyist accustomed to write idiomatic Ruby code, this requirements naturally lead to some mini-DSL:
  1. define_test "This is the phase describing test" do
  2.   # test's code itself
  3. end
复制代码
This is exactly what RSpec does: solves the task in most unambiguous, idiomatic and readable way possible:
  1. it "performs some set of really complex actions" do
  2.   # test's code
  3. end
复制代码
When you see it, you can immediatly have a good guess of what it does and how it is implemented, and you’ll be pretty close to reality.
  But then, somebody comes and says “DSL’s are bad! It is like language inside language you need to study. It is Unholy Black Magic!” And comes with “pure-Ruby, no DSL” solution:
  1. def test_performs_some_set_of_really_complex_actions
  2.   # test's code
  3. end
复制代码
Or is it? Don’t you see DSL here too? But it    ishere. Let me show you. Slowly.  
  1. def  test_performs_some_set_of_really_complex_actions
  2.       it "performs some set of really complex actions"
  3. #     ^  |         ^
  4. #     1  |         2
复制代码
In both cases, you can’t write anything else instead of (1)—so, it is part of “test framework language”. And (2) is description you provide, it can be anything, it is a parameter.
  So, what is the difference?
  RSpec uses Ruby language structures to designate parts of “test definition language”, which helps to write, read and validate code. Try writing    ti "performs something"and you’ll be stopped early by interpreter, even before tests would run. Want to write test which would be described like    "checks 1:N validation (or similar)"? You just write it.  
  MiniTest pretends to be “just Ruby, nothing new!”, but it is the same DSL, just the fact is hidden from your eyes—with some really bad consequences. Try writing    tset_peforms_something, it is still absolutely “valid” code, just the test would be ignored silently. Try to describe your test with a string that includes any symbol not allowed for method name, and you are screwed.  
  (For everyone yelling “but we have MiniSpec” at this moment—please, refer todiscussion of this fact.)  
  Parting the ways

  This, really simplistic, example, allows to define some important differences between two ways of writing in Ruby: let’s schematically call them “Ruby way” and “Rails way”:
  
       
  • “Ruby way” is relying on      alllanguage features and embracing them; as Matz said, “it is complex language for writing simple code”, you      shouldunderstand Ruby to read and understand RSpec;   
  • “Rails way” is using Ruby features underwater, while allowing developer to believe “he just writes classes and methods, and everything works”;   
  • For making code clean, simple, and readable, “Ruby way” assumes library defines clever combinable atoms you can use in your code;   
  • …while “Rails way” assumes library injects some new      conventionyour code should confirm to (      convention over configuration, you know?.. which, in fact, says, if you want to achieve something, you write kinda “independent” code, it should “just follow the convention”);   
  • “Ruby way” praises method-with-block argument as one of the most powerful syntax constructs, it is one of the most basic code-as-data constructs (yet, I can confirm as a Ruby mentor, hard for newcomers);   
  •       “Rails way” neglects method-as-block argument approach, it is “magic” and “not generic enough”, I suppose? So, even when Railist really needs to pass arbitrary logic to method, he prefers to “go JavaScript”:
         
    1. scope :starts_after, ->(date) { where('start_date > ?', date) }
    2. # this is SO MUCH BETTER RAILS CODE THAN BELOW, RIGHT? RIGHT?
    3. scope(:starts_after){ |date| where('start_date > ?', date) }
    复制代码

  On definition of “magic”

  Ruby and Rails share the same part of bad reputation of “too much magic”, but recently this question (what is “magic”, and what amount of it, whatever it is, is “too much”) became the concern not only outside the community, but also inside. “MiniTest vs RSpec” debate can be seen as a part “right amount of magic” debate too.
  Most of the recent in-community definitions of “too much magic” seem to focus on neglecting and disgusting natural block-based DSLs, while praising “just plain object and methods” approach.
  Yet, what is “magic”, seriously? Magic is    unexpected consequences of simple actions. You say “Avada Kedavra” while doing some gesture with a wooden stick, and Sirius Black dies—that’s magic. But if you pull a trigger, trigger causes chemical reaction, which pushes out the bullet, and    thenSirius Black dies—it is not the magic, it is engineering, despite of the fact how “magical” it seems for unsuspecting barbarian.  
  So, let’s use the same simplistic yet real example:
  1. it "is engineering, seriously" do
  2.   expect(trigger.pull).to eq("fire")
  3. end
复制代码
What and where is here?..    it(description) { ... }is testing construction, which is simple and readable: method, accepting a string and a block; probably it adds test to internal list of tests to do (yep, it does).  
      expect(whatever).toprobably wraps value into something that is easy to test with other tests (exactly what it does). And    eqproduces some object/matcher?..  
  I’d suppose it is something defining    ===generic test method + some additional services to output pretty strings when test is failed? (correct)  
  Now, let’s go to that “just plain Ruby, no DSL” thing:
  1. def test_if_it_is_engineering
  2.   assert_equal trigger.pull, "fire"
  3. end
复制代码
What do you think when you see a method named this way?.. It is just a method definition.
  Ah! If you are inside MiniTest context, there is a    magicconvention for this kind of method to have a special meaning (because it starts with    test_, you can’t guess from code itself it is so special, PURE MAGIC).  
  Then, this    assert_equalthing… It is not THAT worse than    expect().to eq, I just never able to properly remember what is “expected” and what is “actual” part of those two arguments… But yeah, cool, there is “no magic!”. Unless you want to define your own assertion/matcher with pretty messages, which could lead you to real Narnia in MiniTest case.  
  You can’t beat them, you lead them

  When MiniTest founds itself significantly less expressive than RSpec, it introduces “minitest/spec”, which looks exactly like RSpec, but is better somehow. How?.. Ah, it is still “good clean less-magic MiniTest!” And it is less magic how exactly?..
  Let’s investigate how the overall structure works
  1. class Foo
  2. end
  3. # that's rspec
  4. describe Foo do
  5.   p [self, self.name]
  6.   # => [RSpec::ExampleGroups::Foo, "RSpec::ExampleGroups::Foo"]
  7.   # it is just a class, with pretty explainable name
  8. end
  9. # and that's minitest/spec
  10. describe Foo do
  11.   p [self, self.name]
  12.   # => [#<Class:0x991a31c>, "Foo"]
  13.   # ok, guessing by name, it is original Foo class that all tests are injected are?..
  14.   p self == Foo
  15.   # => false
  16.   # nope.
  17.   # phew, what kind of world is it?..
  18.   p self.method(:name).source_location
  19.   # => ["/media/storage/home/zverok/.rvm/gems/ruby-2.2.4/gems/minitest-5.9.1/lib/minitest/spec.rb", 272]
  20.   # Ah, ok. It is just your usual "just plain Ruby, no magic" thing, that
  21.   # pretends current class is named the way it is NOT named in fact.
  22. end
复制代码
Here, I’ve managed to formulate MiniTest/spec motto for you:
  We don’t always DSL, but when we do, it is magic!

  Let’s look at matchers, probably?..
  1. # RSpec, 2016
  2. expect(page).to have_content("bar")
  3. # -> no pollution of tested class, just a DSL of "expect + matcher"
  4. #    `expect().to` part is default; `have_content` is matcher's name,
  5. #    in any "how to create matcher" guide you know how it is defined
  6. # MiniTest/Spec, 2016
  7. page.must_have_content 'bar'
  8. # -> looks familiar, right? really like RSpec 2+, which polluted everything
  9. #    with its methods... and was retired 2.5 years ago
复制代码
Whoops…
  The answer is simple: “Rails way” (which MiniTest closely follows) is “let’s not be replaced with superceding technology, let’s imitate it while stating we are superior”. While this imitation can’t follow the original too close, MiniTest’s imitation of RSpec feature will always be in “chasing the leader” state.
  And, truth to be told, minitest also recommends this Really Cool Replacement,    Minitest::MatchersVaccine, the name is explained as “Adds matcher support to minitest without all the other RSpec-style expectation infections.” This gem last updated May 31, 2016… you know, only 2 years after RSpec 3 introduced and promoted non-“infecting” matchers… which was three monthes before “MatchersVaccine” project have ever    started.  
  Yet “MatchersVaccine” fights, somehow, not with “MiniTest implementation of matchers”, but with “RSpec approach to matchers”, which does NOT even here currently; but it easier to fight dehumanized “enemy” (Bad RSpec Guys) than friends (Those MiniTest Guys, who intorduced their faulty matchers).
  That’s just Rails way

  One of MiniTest-praise    definitive articleyou can easily google around, states, alongside other things, as a separate important section:  
  7. Deviating from Rails defaults doesn’t always provide value
  Yeah, that’s the (Ruby) world we are living in: “what is good enough for Rails, should be good enough for Ruby”. But, in fact, this is just plain wrong.
  There are two separate ways, and the fact that Rails are written in Ruby, is, to be honest, unfortunate incident. Ruby still    can and shouldpraise Rails for its current popularity, but the ways just    are and shouldbe different.
友荐云推荐




上一篇:Building Instagram Stories using React Native
下一篇:从0开始学习 GitHub 系列之「GitHub 常见的几种操作」
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

氧气 发表于 2016-10-21 08:29:55
我也是坐沙发的
回复 支持 反对

使用道具 举报

邓雪娟 发表于 2016-10-25 01:27:25
锄禾日当午,发帖真辛苦。谁知坛中餐,帖帖皆辛苦!
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表