<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>nainc (nainc)</title>
    <link>https://ruby-china.org/nainc</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>[北京] 途家海外事业部 招聘 Ruby 工程师 (20K-40K)</title>
      <description>&lt;h3 id="关于途家"&gt;关于途家&lt;/h3&gt;
&lt;p&gt;途家，全球公寓民宿预订平台，于 2011 年 12 月 1 日正式上线。 &lt;br&gt;
作为中国住宿分享的引领者，途家致力于为房 户提供丰富、优质、更个性的出行住宿体验，同时也为房东提供高收益且有保障的闲置房屋分享平台。 &lt;br&gt;
目前途家已到 D 轮融资，是国内民宿行业的领头者。&lt;/p&gt;
&lt;h3 id="关于我们（大鱼自助游）"&gt;关于我们（大鱼自助游）&lt;/h3&gt;
&lt;p&gt;大鱼自助游已跟途家合并，成立海外事业部，独立负责途家的全部海外业务。&lt;br&gt;
大鱼一直致力于海外民宿行业的探索，有完整的供应链体系，B 端体系。&lt;br&gt;
我们今年的目标是海外预定间夜的 30 倍增长！目前 Q1 的目标已经达成，现招募更多的小伙与我们共同努力。&lt;/p&gt;
&lt;h3 id="招聘职位"&gt;招聘职位&lt;/h3&gt;&lt;h4 id="【Ruby工程师】"&gt;【Ruby 工程师】&lt;/h4&gt;
&lt;p&gt;【工作职责】&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;负责途家网海外事业部各产品线的研发，包括供应链产品、电商交易系统、全球分销体系等&lt;/li&gt;
&lt;li&gt;负责技术团队内部的基础架构研发、包括微服务化，持续集成交付、基础云平台等&lt;/li&gt;
&lt;li&gt;参与公司和部门级别的技术架构设计，评审与 code review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;【任职资格】&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;大学本科及以上学历，2 年以上开发经验&lt;/li&gt;
&lt;li&gt;熟练掌握互联网后端技术架构，熟悉 Ruby 语言，熟练使用 Ruby on Rails 框架进行开发和部署&lt;/li&gt;
&lt;li&gt;熟悉常用的数据库及缓存技术的使用和设计，如 Mysql，Mongodb，Redis&lt;/li&gt;
&lt;li&gt;对面向对象有深刻的理解，熟悉常用设计模式&lt;/li&gt;
&lt;li&gt;熟悉前端技术，包括 HTML、CSS 和 Javascript&lt;/li&gt;
&lt;li&gt;具备编写单元测试的良好习惯&lt;/li&gt;
&lt;li&gt;具备扎实的计算机基础知识，包括数据结构、常用算法、linux 系统&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;【加分项】&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;熟悉 Java，有 Java 系统开发经验（之后大鱼部分业务会用 java 开发，欢迎对 java 感兴趣的小伙伴）&lt;/li&gt;
&lt;li&gt;有对于复杂业务系统的架构设计，服务拆分经验&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="薪资待遇"&gt;薪资待遇&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;薪资范围：20K-40K&lt;/li&gt;
&lt;li&gt;五险一金，年假，病假，额外孝亲假，餐补，团建等等该有的都有&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="工作地点及联系方式"&gt;工作地点及联系方式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;大鱼新址位于北京市朝阳区亮马桥路 27 号院内 1903 号大鱼公司（地铁十号线亮马桥站／十四号线东风北桥站步行约 10-15 分钟）&lt;/li&gt;
&lt;li&gt;如对以上招聘信息感兴趣，请将简历发送至邮箱：xiaoyiw@tujia.com&lt;/li&gt;
&lt;li&gt;更多图片与技术栈可参考之前的招聘贴：&lt;a href="https://ruby-china.org/topics/33619" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/33619&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>nainc</author>
      <pubDate>Mon, 12 Mar 2018 11:46:37 +0800</pubDate>
      <link>https://ruby-china.org/topics/35212</link>
      <guid>https://ruby-china.org/topics/35212</guid>
    </item>
    <item>
      <title>Rails 如何写测试</title>
      <description>&lt;p&gt;本帖只是个人总结的一些实践经验，适用于一些不太会写测试的同学，高手请指教&lt;/p&gt;
&lt;h3 id="为什么要写单元测试？"&gt;为什么要写单元测试？&lt;/h3&gt;
&lt;p&gt;个人总结为以下两点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;现在的测试是为了避免以后的麻烦。当一个功能比较复杂，关系到比较重要的业务或者难以进行黑盒测试的时候，或许在你开发的时候你很有信心，相信这里不会出现 bug。但过了几个月甚至一年，你需要扩展功能或者别人需要接手你的代码时，这就会令人头疼了（尤其是公司没有测试工程师！）。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;依赖于第三方服务的功能，比如支付回调，支付平台退款。在本地环境无法以正确的数据调用第三方服务，这时候可以用测试来 mock 返回值，以便进行后续开发。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;节约回归测试的时间，每次回归测试都要跑的 case 可以写成单元测试&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;个人认为，如果开发时间充裕，尽可能的写测试覆盖全部功能。如果时间不够，比如在创业公司，测试最好也能够覆盖 service 和 api&lt;/p&gt;
&lt;h3 id="miniTest+fixtures+mocha的测试框架"&gt;miniTest+fixtures+mocha 的测试框架&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;minTest: rails 的测试框架&lt;/li&gt;
&lt;li&gt;fixtures: 生成测试数据&lt;/li&gt;
&lt;li&gt;mocha: 各种 mock 方法的补充&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="常用的测试方法"&gt;常用的测试方法&lt;/h3&gt;
&lt;p&gt;详细可见 &lt;a href="http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest/Expectations.html" rel="nofollow" target="_blank"&gt;http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest/Expectations.html&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;判断真假 must_equal
这是最常用的一个测试方法了。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'model save must be true'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;must_equal&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'service must return true'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;must_equal&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;判断一个对象的类型 must_be_kind_of&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'man must be kind of Human'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;man&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Human&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;man&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;must_be_kind_of&lt;/span&gt; &lt;span class="no"&gt;Human&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;判断一定会抛出异常 must_raise&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'model must raise error if name is nil'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;must_raise&lt;/span&gt; &lt;span class="no"&gt;ValidationError&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="常用的模拟方法"&gt;常用的模拟方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;模拟调用 stubs&lt;/p&gt;

&lt;p&gt;在写测试的过程中，我们常常会希望某个方法返回我们希望的值，不管它如何执行的，这时可以用 stubs。在下面这段代码中，我们需要测试可以成功申请支付宝退款，而实际代码中，申请支付宝退款是一个 http 请求，没有真实的订单号我们一定会申请失败，所以我们模拟一下它的返回。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AliPayDrawbackService&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_drawback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;RestClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'apply alipay drawback success'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;#any_instance表示该service的任意实例对象&lt;/span&gt;
  &lt;span class="no"&gt;AliPayDrawbackService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stubs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:apply&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
  &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;AliPayDrawbackService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;do_drawback&lt;/span&gt;
  &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;must_equal&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;#pass&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;期待调用 expects&lt;/p&gt;

&lt;p&gt;某个功能在执行过程中会调用一个其他系统服务，或者某个功能会插入一个任务到异步队列。这是我们需要秉承一个原则：自己的功能自己测。即我不关心其他服务的功能是否正确，我认为只要我成功调用了就是正确的。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#一个消息队列的pusher&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Mq::Publisher&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="c1"&gt;# push 消息体到队列&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;submit&lt;/span&gt;
    &lt;span class="c1"&gt;# ...业务逻辑&lt;/span&gt;
    &lt;span class="no"&gt;Mq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Publisher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;puhlish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'order.submit.success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;xxx&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'send a message if order submit success'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;#注意，期待调用的方法一定要写在实际调用前&lt;/span&gt;
  &lt;span class="c1"&gt;#这段代码表示期待Mq::Publisher的publish方法在本测试中至少调用一次，并且第一个参数是"order.submit.success"，any_parameters表示后面的可以是任意参数&lt;/span&gt;
  &lt;span class="no"&gt;Mq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Publisher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:publish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"order.submit.success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;any_parameters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at_least_once&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="c1"&gt;#xxx&lt;/span&gt;
  &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;
  &lt;span class="c1"&gt;#如果Mq::Publisher没有调用publish，测试结果会是失败&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;mock 对象&lt;/p&gt;

&lt;p&gt;有时某个方法可能会需要一个很复杂的参数，或者某个方法返回的一个结果对象会影响剩余方法的执行，这时我们可以使用 mock&lt;/p&gt;

&lt;p&gt;个人总结了两个方法来 mock&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 Minitest::Mock&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;
  &lt;span class="n"&gt;service_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ServiceA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;do_something&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success?&lt;/span&gt;
    &lt;span class="c1"&gt;#xxxx&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c1"&gt;#xxxx&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'execute must return true if do_something'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;#创建一个Mock对象，设置它的success?方法返回true&lt;/span&gt;
  &lt;span class="n"&gt;mock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Minitest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt; &lt;span class="ss"&gt;:success?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="c1"&gt;#设置ServiceA查到任意实例对象调用do_smoething方法返回mock&lt;/span&gt;
  &lt;span class="no"&gt;ServiceA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stubs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:do_smoething&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;must_equal&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 Class.new&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;
  &lt;span class="n"&gt;service_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ServiceA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;do_something&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success?&lt;/span&gt;
    &lt;span class="c1"&gt;#xxxx&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c1"&gt;#xxxx&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'execute must return true if do_something'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;mock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;define_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:success?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="no"&gt;ServiceA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stubs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:do_smoething&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;must_equal&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;##先总结这么多&lt;/p&gt;</description>
      <author>nainc</author>
      <pubDate>Sun, 03 Apr 2016 16:12:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/29532</link>
      <guid>https://ruby-china.org/topics/29532</guid>
    </item>
    <item>
      <title>关于乐观锁的重试</title>
      <description>&lt;p&gt;### 关于乐观锁的重试&lt;/p&gt;

&lt;p&gt;锁相关的知识可以参考 &lt;a href="/zamia" class="user-mention" title="@zamia"&gt;&lt;i&gt;@&lt;/i&gt;zamia&lt;/a&gt; 的这篇文章 &lt;a href="https://ruby-china.org/topics/28963" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/28963&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这篇文章算是一点小小的补充&lt;/p&gt;

&lt;p&gt;### 如何本地复现乐观锁冲突&lt;/p&gt;

&lt;p&gt;在访问量大的线上环境，使用了乐观锁的地方是有可能出现 StaleObjectError 的异常的，本地如何复现呢？这个有个小方法（可能比较 low）&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;使用 byebug 这个 gem，在锁的事务中加上 byebug 调试，例：&lt;/p&gt;

&lt;p&gt;假设这是一个库存的对象，库存比较容易出现抢锁的情况&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unforzen&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_lock&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;byebug&lt;/span&gt;
        &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frozen_num&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;开两个 console，获取两个对象（stock_a 和 stock_b）&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;调用 stock_a.unforzen，会进入到如下调试模式，可以看到已经进入到事务中了：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/116ce53845c1853ffd5b0901b3098f96.png" title="" alt=""&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;然后调用 stock_b.unforzen，跟 a 相同，也进入到了事务，这时将 a 的代码执行完，next 是单步执行，continue 是执行到下一个断点。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;执行 b 的代码，这时你会发现 b 的 unforzen 方法抛出了 StaleObjectError 的异常&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;###准备工作结束，如何重试&lt;/p&gt;

&lt;p&gt;值得注意的一点是，reload 需要给参数 lock: true，否则 lock_version 是不会更新的&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unforzen&lt;/span&gt;
    &lt;span class="n"&gt;optimistic_lock_retry_times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
        &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_lock&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="n"&gt;byebug&lt;/span&gt;
            &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frozen_num&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StaleObjectError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
        &lt;span class="n"&gt;optimistic_lock_retry_times&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;optimistic_lock_retry_times&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;lock: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;retry&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;### 一点疑问&lt;/p&gt;

&lt;p&gt;其实实际的代码要比例子复杂一点，这个释放库存的功能是通过一个 service 调用的，异常统一在 service 中处理，但是 service 不能直接获取到出现锁异常的 record，我尝试过 error.record.reload(lock: true)，如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StaleObjectError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock_version&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;lock: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock_version&lt;/span&gt;
    &lt;span class="k"&gt;retry&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现 lock_version 是变了的，但是重新 retry 的代码中的 record 的 lock_version 还是原来的。这块还没仔细研究原理，懂得同学也可以分享一下~&lt;/p&gt;</description>
      <author>nainc</author>
      <pubDate>Fri, 25 Mar 2016 11:19:06 +0800</pubDate>
      <link>https://ruby-china.org/topics/29453</link>
      <guid>https://ruby-china.org/topics/29453</guid>
    </item>
    <item>
      <title>为什么酷站中没有大鱼自助游了？</title>
      <description>&lt;p&gt;&lt;a href="/lgn21st" class="user-mention" title="@lgn21st"&gt;&lt;i&gt;@&lt;/i&gt;lgn21st&lt;/a&gt; &lt;a href="/huacnlee" class="user-mention" title="@huacnlee"&gt;&lt;i&gt;@&lt;/i&gt;huacnlee&lt;/a&gt;
之前记得大鱼自助游在酷站里的，怎么没有了呢，大鱼的竞品自在客反而在里面，而且自在客貌似是 php 写的吧？？&lt;/p&gt;</description>
      <author>nainc</author>
      <pubDate>Fri, 19 Feb 2016 13:44:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/29028</link>
      <guid>https://ruby-china.org/topics/29028</guid>
    </item>
    <item>
      <title>配置 gitlab-ci 进行持续集成</title>
      <description>&lt;h3 id="在日常工作中，我们经常会写一些单元测试来确保程序可以正确执行，但是写完单元测试后常常会面临这些问题："&gt;在日常工作中，我们经常会写一些单元测试来确保程序可以正确执行，但是写完单元测试后常常会面临这些问题：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;有一些小改动懒得跑单元测试&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;别人改动相关代码影响了你的代码逻辑，但他们不知道你写过这些单元测试或者没有跑单元测试的习惯&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;项目大了，单元测试很多，跑一遍要花很长时间&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="这时候可以选择进行持续集成"&gt;这时候可以选择进行持续集成&lt;/h3&gt;
&lt;p&gt;进行持续集成有很多种方式，介绍一下使用 gitlab-ci 进行持续集成的配置方式。&lt;/p&gt;
&lt;h3 id="准备"&gt;准备&lt;/h3&gt;
&lt;p&gt;gitlab 版本升级的很快，公司的 gitlab 是在去年 5 月份安装的，当时的版本还是 7.10，现在已然发布到 8.3 了。
建议把 gitlab 升级到 8.0 以上，8.0 以上的版本自动集成了 gitlab-ci 的功能，无需再自己配置一个 gitlab-ci-server 了。google gitlabci 相关的内容很多都是基于 8.0 以上介绍的。并且 gitlab7.12 以上版本支持使用.gitlab-ci.yml 进行 ci 配置，自己搭建 gitlab-ci 的话很容易出现 ci 版本和 gitlab 不一致导致找不到.gitlab-ci.yml 的问题。
升级方式就不详细说了，详见&lt;a href="https://about.gitlab.com/update/" rel="nofollow" target="_blank"&gt;https://about.gitlab.com/update/&lt;/a&gt;
公司之前用的 Omnibus gitlab，升级很方便，直接 yum install 就 ok 了，升级前记得备份数据库。&lt;/p&gt;
&lt;h3 id="gitlab开启build功能"&gt;gitlab 开启 build 功能&lt;/h3&gt;
&lt;p&gt;gitlab 项目下-》Project-Settings-》Features 里的 Builds 选项勾上-》保存-》完事儿
开启后会发现菜单多了几个功能，打开 runners，里面有 token 和 url，下面配置 runner 时会用到。&lt;/p&gt;
&lt;h3 id="配置ci-runner"&gt;配置 ci-runner&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;首先安装&lt;a href="https://gitlab.com/gitlab-org/gitlab-ci-multi-runner" rel="nofollow" target="_blank" title=""&gt;gitlab-ci-multi-runner&lt;/a&gt;，之前有一个 runner 是 gitlab-ci-runner，这个 runner 的代码已经不再维护了，不要装错。&lt;/li&gt;
&lt;li&gt;安装完成后启动：gitlab-ci-multi-runner start&lt;/li&gt;
&lt;li&gt;注册一个新的 runner：gitlab-cimulti-runner register，提示输入 url 和 token，输入上一步 runners 里的内容就行了，runner 的名字随便输入。然后提示选择 executer，个人选的 shell，还可以选择 docker 和 ssh&lt;/li&gt;
&lt;li&gt;完成后刷新 gitlab 的 runner 页面，可以看到刚才注册的 runner&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="配置gitlab-workhouse"&gt;配置 gitlab-workhouse&lt;/h3&gt;
&lt;p&gt;之前公司没有使用 gitlab 内置的 nginx，而是自己起的 nginx 做配置。这种情况可能导致 build 的时候 git clone 不下代码，会报这个错误：
fatal: reference is not a tree
google 了一下，建议的解决办法就是使用 gitlab 内置的一个 gitlab_git_http_server，好像是 8.2 之后的版本改名成了 gitlab-workhouse。
/etc/gitlab/gitlab.rb中增加配置：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gitlab_workhorse['listen_network'] = "tcp"
gitlab_workhorse['listen_addr'] = "127.0.0.1:8181"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;nginx 增加配置：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;upstream gitlab_repo {
   server 127.0.0.1:8181 fail_timeout=0;
   server 127.0.0.1:8181 fail_timeout=0;
}
#gitlab的server配置中增加
location ~* \.(git) {
      proxy_read_timeout      300;
      proxy_connect_timeout   300;
      proxy_redirect          off;

      proxy_set_header   Host              $http_host;
      proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
      proxy_pass http://gitlab_repo;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;###.gitlab-ci.yml 配置
在项目的根目录下创建.gitlab-ci.yml，下面是跑单元测试的配置，当然还可以干很多别的。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;before_script:
    - cp config/database.yml.sample config/database.yml
    - bundle install --path vendor/bundle --without development
    - "bundle exec rake db:create RAILS_ENV=test"
    - "bundle exec rake db:migrate RAILS_ENV=test"
  job1:
    script: 'RAILS_ENV=test rake test:units' 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进阶配置请参考文档&lt;/p&gt;

&lt;p&gt;到此配置完成，从此之后每一个 push 都会触发 ci 进行 build，提交 Merge quest 后也会有相关 build 结果提示，再也不用担心小伙伴们忘跑单元测试了。&lt;/p&gt;

&lt;p&gt;##TODO
其实不需要每个 push 都触发 build 的，希望只在提交 MR 后触发一次 build，在和代码之前看到 build 结果就可以了。还在研究，希望有了解的同学能指点一下。&lt;/p&gt;</description>
      <author>nainc</author>
      <pubDate>Wed, 13 Jan 2016 22:58:39 +0800</pubDate>
      <link>https://ruby-china.org/topics/28726</link>
      <guid>https://ruby-china.org/topics/28726</guid>
    </item>
  </channel>
</rss>
