public class PlaceOrderServiceTests extends MockObjectTestCase{
Mockery context = new Mockery();
public void testUpdateRestaurant_good() throws Exception{
//setup
PlaceOrderService service = new PlaceOrderService();
final RestaurantRepository restaurantRepository = context.mock(RestaurantRepository.class);
final String restaurantId = "1";
final String pendingOrderId = "1";
//expectations
context.checking(new Expectations(){{
allowing(restaurantRepository).findRestaurant(restaurantId,pendingOrderId);
}});
//execute
service.updateRestaurant(restaurantId,pendingOrderId);
//verify
context.assertIsSatisfied();
}
}
ç„¶åŽåˆ†åˆ«å»ºç«‹ç›¸åº”的类和接å£ï¼š
public interface RestaurantRepository {
Restaurant findRestaurant(String restaurantId, String pendingOrderId);
}
public class Restaurant {
}
public class PlaceOrderService {
public void updateRestaurant(String restaurantId, String pendingOrderId) {
// TODO Auto-generated method stub
}
}
在过åŽÕdòq´ä¸åQŒèÊYä»¶å¼€å‘äh员对‹¹‹è¯•çš„çƒæƒ…æ—¥æ¸ä½Ž˜q—÷€‚åŒä¸€æ—¶æœŸå‡ºçŽ°çš„åŠ¨æ€è¯a€òq¶æ²¡æœ‰æä¾›ç¼–è¯‘ç¨‹åºæ¥æ•æ‰æœ€åŸºæœ¬çš„错误,˜q™ä‹É得测试å˜å¾—æ›´åŠ é‡è¦ã€‚éšç€‹¹‹è¯•½C‘ÖŒºçš„æˆé•¿ï¼Œå¼€å‘äh员开始注æ„到åQŒé™¤äº†æ•èŽ?bug ½{‰æœ€åŸºæœ¬çš„优点外åQŒæµ‹è¯•还å…ähœ‰ä»¥ä¸‹ä¼˜åŠ¿åQ?/p>
有关‹¹‹è¯•的优ç‚ÒŽ— 需赘述åQŒæˆ‘ž®†å‘您介¾l一个简å•的使用 RSpec 的测试驱动开å‘示例。RSpec 工具是一ä¸?Ruby 软äšg包,å¯ä»¥ç”¨å®ƒæž„徿œ‰å…³æ‚¨çš„软äšg的规范。该规范实际上是一个æ˜q°ç³»¾lŸè¡Œä¸ºçš„‹¹‹è¯•。ä‹Éç”?RSpec çš„å¼€å‘æµ½E‹å¦‚下:
实质上,RSpec å¼€å‘äh员所åšçš„工作ž®±æ˜¯ž®†å¤±è´¥çš„‹¹‹è¯•用例调试为æˆåŠŸçš„‹¹‹è¯•用例。这是一个主动的˜q‡ç¨‹ã€‚本文ä¸åQŒæˆ‘ž®†ä»‹¾l?RSpec 的基本用法ã€?
首先åQŒå‡è®¾æ‚¨å·²å®‰è£…了 Ruby å’?gems。您˜q˜éœ€è¦å®‰è£?RSpec。输入下é¢çš„内容åQ?
gem install rspec
![]() ![]() |
接下æ¥ï¼Œæˆ‘将逿¥æž„å¾ä¸€ä¸ªçŠ¶æ€æœºã€‚我ž®†éµå¾?TDD 规则。首先编写自å·Þqš„‹¹‹è¯•用例åQŒåÆˆä¸”ç›´åˆ°æµ‹è¯•ç”¨ä¾‹éœ€è¦æ—¶æ‰ç¼–写代ç 。Rake çš„åˆ›å»ø™€?Jim Weirich è®¤äØ“˜q™æœ‰åŠ©äºŽè§’è‰²æ‰®æ¼”ã€‚åœ¨¾~–å†™å®žé™…çš„ç”Ÿäº§ä»£ç æ—¶åQŒæ‚¨å¸Œæœ›å……当一å›?jerk å¼€å‘äh员的角色åQŒåªå®Œæˆæœ€ž®‘é‡çš„工作æ¥ä½¿æµ‹è¯•通过。在¾~–写‹¹‹è¯•æ—Óž¼Œæ‚¨åˆ™æ‰®æ¼”‹¹‹è¯•人员的角è‰ÔŒ¼Œè¯•图为开å‘äh员æä¾›ä¸€äº›æœ‰ç›Šçš„帮助ã€?/p>
以下的示例展½CÞZº†å¦‚何构å¾ä¸€ä¸ªçŠ¶æ€æœºã€‚如果您以å‰ä»ŽæœªæŽ¥è§¦˜q‡çŠ¶æ€æœºåQŒè¯·æŸ¥é˜… å‚考资æ–?/a>ã€‚çŠ¶æ€æœºå…ähœ‰å¤šç§çжæ€ã€‚毿UçŠ¶æ€æ”¯æŒå¯ä»¥è{æ¢çŠ¶æ€æœºçжæ€çš„事äšg。测试驱动开å‘入门的关键ž®±æ˜¯ä»Žé›¶å…¥æ‰‹åQŒå°½é‡å°‘åœîC‹É用å‡è®¾æ¡ä»¶ã€‚é’ˆå¯Ò޵‹è¯•进行程åºè®¾è®¡ã€?/p>
ä½¿ç”¨æ¸…å• 1 的内容创建åä¸?machine_spec.rb 的文件。该文äšgž®±æ˜¯æ‚¨çš„规范。您˜q˜ä¸äº†è§£ machine.rb æ–‡äšg的作用,目å‰å…ˆåˆ›å»ÞZ¸€ä¸ªç©ºæ–‡äšgã€?/p>
æ¸…å• 1. 最åˆçš„ machine_spec.rb æ–‡äšg
require 'machine' |
接下æ¥ï¼Œéœ€è¦è¿è¡Œæµ‹è¯•。始¾lˆé€šè¿‡è¾“å…¥ spec machine_spec.rb
˜q行‹¹‹è¯•。清å?2 展示了预料之ä¸çš„‹¹‹è¯•å¤ÞpÓ|åQ?/p>
æ¸…å• 2. ˜q行½Iºçš„规范
~/rspec batate$ spec machine_spec.rb /opt/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require': no such file to load -- machine (LoadError) from /opt/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' from ./state_machine_spec.rb:1 from ... |
在测试驱动开å‘ä¸åQŒæ‚¨éœ€è¦è¿›è¡Œå¢žé‡å¼€å‘ï¼Œå› æ¤åœ¨è¿›è¡Œä¸‹ä¸€‹Æ¡å¼€å‘å‰åQŒéœ€è¦å…ˆè§£å†³æ¤æ¬¡‹¹‹è¯•出现的问题。现在,我将扮演 jerk å¼€å‘äh员的角色åQŒå³åªå®Œæˆæ»¡‘›_º”用程åºè¿è¡Œæ‰€éœ€çš„æœ€ž®‘工作é‡ã€‚我ž®†åˆ›å»ÞZ¸€ä¸ªåä¸?machine.rb 的空文äšgåQŒä‹É‹¹‹è¯•通过。我现在å¯ä»¥ä»¥é€¸å¾…劻I¼Œ‹¹‹è¯•é€šè¿‡è€Œæˆ‘å‡ ä¹Žæ²¡åšä»ÖM½•事情ã€?/p>
¾l§ç®‹è§’色扮演。我现在扮演一个烦íwçš„‹¹‹è¯•人员åQŒä¿ƒä½?jerk å¼€å‘äh员åšäº›å®žé™…的工作。我ž®†ç¼–ç 以下规范,需è¦ä‹Éç”?Machine
¾c»ï¼Œå¦‚清å?3 所½Cºï¼š
require 'machine' describe Machine do before :each do @machine = Machine end end |
该规范æ˜qîCº†ç›®å‰ž®šä¸å˜åœ¨çš?Machine
¾c…R€?code>describe æ–ÒŽ³•æä¾›äº?RSpec æè¿°åQŒæ‚¨ž®†ä¼ 入测试类的å¿U°å’ŒåŒ…å«å®žé™…规范的代ç å—。通常åQŒæµ‹è¯•ç”¨ä¾‹éœ€è¦æ‰§è¡Œä¸€å®šæ•°é‡çš„讄¡½®å·¥ä½œã€‚在 RSpec ä¸ï¼Œž®†ç”± before
æ–ÒŽ³•完战q™äº›è®„¡½®å·¥ä½œã€‚您å?before
æ–ÒŽ³•ä¼ é€’ä¸€ä¸ªå¯é€‰çš„æ ‡å¿—和一个代ç å—。代ç å—ä¸åŒ…å«è®¾¾|®å·¥ä½œã€‚æ ‡å¿—ç¡®å®?RSpec 执行代ç å—çš„é¢‘çŽ‡ã€‚é»˜è®¤çš„æ ‡å¿—ä¸?:each
åQŒè¡¨½C?RSpec ž®†åœ¨æ¯æ¬¡‹¹‹è¯•之å‰è°ƒç”¨ set up 代ç å—。您也å¯ä»¥æŒ‡å®?:all
åQŒè¡¨½C?RSpec 在执行所有测试之å‰åªè°ƒç”¨ä¸€‹Æ?before
代ç å—。您应该始终使用 :each
åQŒä‹Éå„个‹¹‹è¯•å½¼æ¤ç‹¬ç«‹ã€?
输入 spec
˜q行‹¹‹è¯•åQŒå¦‚æ¸…å• 4 所½Cºï¼š
~/rspec batate$ spec machine_spec.rb ./machine_spec.rb:3: uninitialized constant Machine (NameError) |
现在åQŒçƒ¦íwçš„‹¹‹è¯•人员è¦ä¿ƒä½?jerk å¼€å‘äh员åšç‚¹ä»€ä¹ˆäº† â€?jerk å¼€å‘äh员现在需è¦åˆ›å»ºæŸä¸ªç±»ã€‚对我æ¥è¯ß_¼Œž®±æ˜¯ä¿®å¤‹¹‹è¯•出现的错误。在 machine.rb
ä¸ï¼Œæˆ‘输入最ž®‘é‡çš„代ç ,如清å?5 所½Cºï¼š
class Machine end |
ä¿å˜æ–‡äšgåQŒç„¶åŽè¿è¡Œæµ‹è¯•ã€‚æ¯«æ— ç–‘é—®ï¼Œæ¸…å• 6 昄¡¤ºçš„æµ‹è¯•报告没有出现错误:
~/rspec batate$ spec machine_spec.rb Finished in 5.0e-06 seconds 0 examples, 0 failures |
![]() ![]() |
现在åQŒæˆ‘å¯ä»¥å¼€å§‹å®žçŽ°æ›´å¤šçš„è¡ŒäØ“ã€‚æˆ‘çŸ¥é“åQŒæ‰€æœ‰çŠ¶æ€æœºå¿…须在æŸäº›åˆå§‹çжæ€ä¸‹å¯åŠ¨ã€‚ç›®å‰æˆ‘˜q˜ä¸æ˜¯å¾ˆæ¸…楚如何设计˜q™ä¸ªè¡ŒäØ“åQŒå› æ¤æˆ‘先编写一个éžå¸¸ç®€å•çš„‹¹‹è¯•åQŒé¦–å…ˆå‡è®?state
æ–ÒŽ³•会返å›?:initial
æ ‡å¿—ã€‚æˆ‘å¯?machine_spec.rb
˜q›è¡Œä¿®æ”¹òq¶è¿è¡Œæµ‹è¯•,如清å?7 所½Cºï¼š
require 'machine' describe Machine do before :each do @machine = Machine.new end it "should initially have a state of :initial" do @machine.state.should == :initial end end ~/rspec batate$ spec machine_spec.rb F 1) NoMethodError in 'Machine should initially have a state of :initial' undefined method `state' for #<Machine:0x10c7f8c> ./machine_spec.rb:9: Finished in 0.005577 seconds 1 example, 1 failure |
注愘q™æ¡è§„则åQ?code> it "should initially have a state of :initial" do @machine.state.should == :initial end。首先注æ„到˜q™æ¡è§„则读è“væ¥åƒæ˜¯ä¸€ä¸ªè‹±æ–‡å¥åã€‚åˆ é™¤æ ‡ç‚¹ï¼Œž®†å¾—åˆ?it should initially have a state of initial
。然åŽä¼šæ³¨æ„到这æ¡è§„åˆ™åÆˆä¸åƒæ˜¯å…¸åž‹çš„é¢å‘对象代ç 。它¼‹®å®žä¸æ˜¯ã€‚您现在有一个方法,¿UîCØ“ it
。该æ–ÒŽ³•å…ähœ‰ä¸€ä¸ªä‹É用引åäh‹¬èµäh¥çš„å—½W¦ä¸²å‚数和一个代ç å—。嗽W¦ä¸²åº”该æè¿°‹¹‹è¯•需求。最åŽï¼Œdo
å’?end
之间的代ç å—包嫋¹‹è¯•用例的代ç ã€?
å¯ä»¥çœ‹åˆ°åQŒæµ‹è¯•进度划分得很细。这些微ž®çš„æ¥éª¤äº§ç”Ÿçš„æ”¶ç›Šå´å¾ˆå¤§ã€‚它们ä‹É我能够改˜q›æµ‹è¯•密度,æä¾›æ—‰™—´ä¾›æˆ‘æ€è€ƒæœŸæœ›çš„è¡ŒäØ“ä»¥åŠå®žçŽ°è¡ŒäØ“æ‰€éœ€çš?API。这些æ¥éª¤è¿˜èƒ½ä‹Éæˆ‘åœ¨å¼€å‘æœŸé—´è·Ÿítªä»£ç è¦†ç›–æƒ…å†µï¼Œä»Žè€Œæž„å»ºæ›´åŠ ä¸°å¯Œçš„è§„èŒƒã€?/p>
˜q™ç§é£Žæ ¼çš„æµ‹è¯•具有åŒé‡ä½œç”¨ï¼š‹¹‹è¯•实现òq¶åœ¨‹¹‹è¯•çš„åŒæ—¶æž„建需求设计文档。ç¨åŽï¼Œæˆ‘将通过‹¹‹è¯•用例构å¾ä¸€ä¸ªéœ€æ±‚列表ã€?/p>
我ä‹É用最½Ž€å•的方å¼ä¿®å¤äº†æµ‹è¯•,˜q”回 :initial
åQŒå¦‚æ¸…å• 8 所½Cºï¼š
class Machine def state :initial end end |
当查看实现时åQŒæ‚¨å¯èƒ½ä¼šæ”¾å£°å¤§½W‘或感觉å—到了愚弄。对于测试驱动开å‘,您必™åȨ微改å˜ä¸€ä¸‹æ€è€ƒæ–¹å¼ã€‚æ‚¨çš„ç›®æ ‡åÆˆä¸æ˜¯¾~–写最¾lˆçš„生äñ”代ç åQŒè‡³ž®‘çŽ°åœ¨ä¸æ˜¯ã€‚æ‚¨çš„ç›®æ ‡æ˜¯ä½¿æµ‹è¯•é€šè¿‡ã€‚å½“æŽŒæ¡ä»¥è¿™¿Uæ–¹å¼å·¥ä½œæ—¶åQŒæ‚¨å¯èƒ½ä¼šå‘现新的实玎ͼŒòq¶ä¸”¾~–写的代ç 覘qœè¿œž®‘于采用 TDD 时编写的代ç ã€?/p>
ä¸‹ä¸€æ¥æ˜¯˜q行代ç åQŒæŸ¥çœ‹å®ƒæ˜¯å¦é€šè¿‡‹¹‹è¯•åQ?/p>
æ¸…å• 9. ˜q行åˆå§‹çŠ¶æ€æµ‹è¯?/strong>
~/rspec batate$ spec machine_spec.rb . Finished in 0.005364 seconds 1 example, 0 failures |
èŠ×ƒº›æ—‰™—´æ€è€ƒä¸€ä¸‹è¿™ä¸ªé€šè¿‡‹¹‹è¯•çš„è„P代。如果查看代ç çš„è¯ï¼Œæ‚¨å¯èƒ½ä¼šè§‰å¾—æ°”é¦ã€‚å› ä¸ºåÆˆæ²¡æœ‰å–得什么进展。如果查看整个è„P代,ž®†çœ‹åˆ°æ›´å¤šå†…容:您æ•获了一个é‡è¦éœ€æ±‚åÆˆ¾~–写‹¹‹è¯•用例实现需求。作ä¸ÞZ¸€å程åºå‘˜åQŒæˆ‘的第一个行为测试帮助我明确了开å‘过½E‹ã€‚å› ä¸ºå®žçŽ°ç»†èŠ‚éšç€‹¹‹è¯•的进行越æ¥è¶Šæ¸…æ™°ã€?/p>
现在åQŒæˆ‘å¯ä»¥å®žçŽ°ä¸€ä¸ªæ›´å¥å£®çš„状æ€å®žçŽ°ã€‚å…·ä½“æ¥è®ÔŒ¼Œæˆ‘需è¦å¤„ç†çŠ¶æ€æœºçš„多个状æ€ã€‚我需è¦åˆ›å»ÞZ¸€ä¸ªæ–°çš„è§„åˆ™èŽ·å–æœ‰æ•ˆçжæ€åˆ—表。åƒä»¥å‰ä¸€æ øP¼Œæˆ‘å°†˜q行‹¹‹è¯•òq¶æŸ¥çœ‹æ˜¯å¦é€šè¿‡ã€?/p>
æ¸…å• 10. 实现有效状æ€è§„èŒ?/strong>
it "should remember a list of valid states" do @machine.states = [:shopping, :checking_out] @machine.states.should = [:shopping, :checking_out] end run test(note: failing first verifies test) ~/rspec batate$ spec machine_spec.rb .F 1) NoMethodError in 'Machine should remember a list of valid states' undefined method `states=' for #<Machine:0x10c7154> ./machine_spec.rb:13: Finished in 0.005923 seconds 2 examples, 1 failure |
在清å?10 ä¸ï¼Œå‡ºçŽ°äº†ä¸€ä¸?RSpec å½¢å¼çš„æ–a€ã€‚该æ–言ä»?should
æ–ÒŽ³•å¼€å§‹ï¼Œç„¶åŽæ·ÕdŠ äº†ä¸€äº›æ¯”è¾ƒå…³¾p…R€?code>should æ–ÒŽ³•对应用程åºè¿›è¡ŒæŸ¿U观察。工作ä¸çš„应用程åºåº”该以æŸç§æ–¹å¼˜q行ã€?code>should æ–ÒŽ³•很好地æ•获了˜q™ç§éœ€æ±‚。在本例ä¸ï¼Œæˆ‘çš„çŠ¶æ€æœºåº”该记忆两ç§ä¸åŒçš„状æ€ã€?/p>
现在åQŒåº”è¯¥æ·»åŠ ä¸€ä¸ªå®žä¾‹å˜é‡æ¥å®žé™…记忆状æ€ã€‚åƒä»¥å¾€ä¸€æ øP¼Œæˆ‘在修改代ç åŽè¿è¡Œæµ‹è¯•用例,òq¶è§‚å¯Ÿæµ‹è¯•æ˜¯å¦æˆåŠŸã€?/p>
æ¸…å• 11. 创å¾ä¸€ä¸ªå±žæ€§ä»¥è®°å¿†çжæ€?/strong>
class Machine attr_accessor :states def state :initial end end ~/rspec batate$ spec machine_spec.rb .. Finished in 0.00606 seconds 2 examples, 0 failures |
![]() ![]() |
æ¤æ—¶åQŒæˆ‘òq¶ä¸æƒ›_†³å®šå°† :initial
状æ€ç§°ä¸ºçŠ¶æ€æœºçš„第一个状æ€ã€‚相å,我更希望½W¬ä¸€ä¸ªçŠ¶æ€æ˜¯çŠ¶æ€æ•°¾l„ä¸çš„ç¬¬ä¸€ä¸ªå…ƒç´ ã€‚æˆ‘å¯¹çŠ¶æ€æœºçš„ç†è§£åœ¨ä¸æ–æ¼”å˜ã€‚è¿™¿UçŽ°è±¡åÆˆä¸å°‘è§ã€‚测试驱动开å‘ç»å¸¸è¿«ä½¿æˆ‘釿–°è€ƒè™‘之å‰çš„å‡è®¾ã€‚由于我已ç»é€šè¿‡‹¹‹è¯•用例æ•获了早期需求,我å¯ä»¥è½»æ‘Öœ°å¯¹ä»£ç è¿›è¡Œé‡æž„。在本例ä¸ï¼Œé‡æž„ž®±æ˜¯å¯¹ä»£ç 进行调æ•ß_¼Œä½¿å…¶æ›´å¥½åœ°å·¥ä½œã€?/p>
修改½W¬ä¸€ä¸ªæµ‹è¯•,使其如清å?12 所½Cºï¼Œòq¶è¿è¡Œæµ‹è¯•:
it "should initially have a state of the first state" do @machine.states = [:shopping, :checking_out] @machine.state.should == :shopping end ~/rspec batate$ spec machine_spec.rb F. 1) 'Machine should initially have a state of the first state' FAILED expected :shopping, got :initial (using ==) ./machine_spec.rb:10: Finished in 0.005846 seconds 2 examples, 1 failure |
å¯ä»¥˜q™æ ·è¯ß_¼Œ‹¹‹è¯•ç”¨ä¾‹èµ·åˆ°ä½œç”¨äº†ï¼Œå› äØ“å®ƒè¿è¡Œå¤±è´¥ï¼Œå› æ¤æˆ‘现在需è¦ä¿®æ”¹ä»£ç 以使其工作。显而易è§ï¼Œæˆ‘çš„ä»ÕdŠ¡ž®±æ˜¯ä½¿æµ‹è¯•通过。我喜欢˜q™ç§‹¹‹è¯•目的åQŒå› 为我的测试用例æ£åœ¨é©±åŠ¨æˆ‘˜q›è¡Œè®¾è®¡ã€‚我ž®†æŠŠåˆå§‹çжæ€ä¼ 递给 new
æ–ÒŽ³•。我ž®†å¯¹å®žçް½E作修改åQŒä»¥½W¦åˆä¿®æ”¹åŽçš„规范åQŒå¦‚æ¸…å• 13 所½Cºã€?/p>
æ¸…å• 13. 指定åˆå§‹çжæ€?/strong>
start to fix it class Machine attr_accessor :states attr_reader :state def initialize(states) @states = states @state = @states[0] end end ~/rspec batate$ spec machine_spec.rb 1) ArgumentError in 'Machine should initially have a state of the first state' wrong number of arguments (0 for 1) ./machine_spec.rb:5:in `initialize' ./machine_spec.rb:5:in `new' ./machine_spec.rb:5: 2) ArgumentError in 'Machine should remember a list of valid states' wrong number of arguments (0 for 1) ./machine_spec.rb:5:in `initialize' ./machine_spec.rb:5:in `new' ./machine_spec.rb:5: Finished in 0.006391 seconds 2 examples, 2 failures |
现在åQŒæµ‹è¯•出çŽîCº†ä¸€äº›é”™è¯¯ã€‚我扑ֈ°äº†å®žçŽîC¸çš„一äº?bug。测试用例ä¸å†ä‹É用棼‹®çš„æŽ¥å£åQŒå› 为我没有把åˆå§‹çжæ€ä¼ é€’ç»™çŠ¶æ€æœºã€‚å¯ä»¥çœ‹åˆŽÍ¼Œ‹¹‹è¯•用例已ç»èµ·åˆ°äº†ä¿æŠ¤ä½œç”¨ã€‚我˜q›è¡Œäº†è¾ƒå¤§çš„æ›´æ”¹åQŒæµ‹è¯•å°±å‘现äº?bug。我们需è¦å¯¹‹¹‹è¯•˜q›è¡Œé‡æž„ä»¥åŒ¹é…æ–°çš„æŽ¥å£ï¼Œž®†åˆå§‹çжæ€åˆ—è¡¨ä¼ é€’ç»™ new
æ–ÒŽ³•。在˜q™é‡Œæˆ‘åÆˆæ²¡æœ‰é‡å¤åˆå§‹åŒ–代ç ,而是ž®†å…¶æ”„¡½®åœ?before
æ–ÒŽ³•ä¸ï¼Œå¦‚清å?14 所½Cºï¼š
require 'machine' describe Machine do before :each do @machine = Machine.new([:shopping, :checking_out]) end it "should initially have a state of the first state" do @machine.state.should == :shopping end it "should remember a list of valid states" do @machine.states.should == [:shopping, :checking_out] end end ~/rspec batate$ spec machine_spec.rb .. Finished in 0.005542 seconds 2 examples, 0 failures |
çŠ¶æ€æœºå¼€å§‹é€æ¸æˆåž‹ã€‚代ç ä»ç„¶æœ‰ä¸€äº›é—®é¢˜ï¼Œä½†æ˜¯æ£åœ¨å‘è‰¯å¥½çš„æ–¹å‘æ¼”化。我ž®†å¼€å§‹å¯¹çŠ¶æ€æœº˜q›è¡Œä¸€äº›è{æ¢ã€‚这些è{æ¢å°†ä¿ƒä‹É代ç 实际记忆当å‰çжæ€ã€?/p>
‹¹‹è¯•用例促ä‹É我全é¢åœ°æ€è€?API 的设计。我需è¦çŸ¥é“如何表½CÞZº‹ä»¶å’Œè½¬æ¢ã€‚首先,我将使用一个散列表表示转æ¢åQŒè€Œæ²¡æœ‰ä‹É用æˆç†Ÿçš„é¢å‘对象实现。éšåŽï¼Œ‹¹‹è¯•需求å¯èƒ½ä¼šè¦æ±‚我修改å‡è®¾æ¡ä»Óž¼Œä½†æ˜¯ç›®å‰åQŒæˆ‘ä»ç„¶ä¿æŒ˜q™ç§½Ž€å•性。清å?15 昄¡¤ºäº†ä¿®æ”¹åŽçš„代ç :
remember events... change before conditions require 'machine' describe Machine do before :each do @machine = Machine.new([:shopping, :checking_out]) @machine.events = {:checkout => {:from => :shopping, :to => :checking_out}} end it "should initially have a state of the first state" do @machine.state.should == :shopping end it "should remember a list of valid states" do @machine.states.should == [:shopping, :checking_out] end it "should remember a list of events with transitions" do @machine.events.should == {:checkout => {:from => :shopping, :to => :checking_out}} end end ~/rspec batate$ spec machine_spec.rb FFF 1) NoMethodError in 'Machine should initially have a state of the first state' undefined method `events=' for #<Machine:0x10c6f38> ./machine_spec.rb:6: 2) NoMethodError in 'Machine should remember a list of valid states' undefined method `events=' for #z7lt;Machine:0x10c5afc> ./machine_spec.rb:6: 3) NoMethodError in 'Machine should remember a list of events with transitions' undefined method `events=' for #<Machine:0x10c4a58> ./machine_spec.rb:6: Finished in 0.006597 seconds 3 examples, 3 failures |
ç”׃ºŽæ–°çš„‹¹‹è¯•代ç ä½äºŽ before
ä¸ï¼Œž®†æˆ‘的三个测试分解开æ¥ã€‚å°½½Ž¡å¦‚æ¤ï¼Œæ¸…å• 16 ä¸å±•½Cºçš„‹¹‹è¯•éžå¸¸å®ÒŽ˜“ä¿®å¤ã€‚我ž®†æ·»åŠ å¦ä¸€ä¸ªè®¿é—®ç¨‹åºï¼š
class Machine attr_accessor :states, :events attr_reader :state def initialize(states) @states = states @state = @states[0] end end ~/rspec batate$ spec machine_spec.rb ... Finished in 0.00652 seconds 3 examples, 0 failures test |
‹¹‹è¯•全部通过。我得到了一个能æ£å¸¸˜qè¡Œçš„çŠ¶æ€æœºã€‚接下æ¥çš„å‡ ä¸ªæµ‹è¯•å°†ä½¿å®ƒæ›´åŠ å®Œå–„ã€?/p>
![]() ![]() |
接近真实的应用程�/span>
ç›®å‰ä¸ºæ¢åQŒæˆ‘所åšçš„ä¸è¿‡æ˜¯è§¦å‘了一‹Æ¡çжæ€è{æ¢ï¼Œä½†æ˜¯æˆ‘å·²¾låšå¥½äº†æ‰€æœ‰åŸº¼‹€å·¥ä½œã€‚我得到了一¾l„需求。我˜q˜æž„å»ÞZº†ä¸€¾l„测试。我的代ç å¯ä»¥äØ“çŠ¶æ€æœºæä¾›ä½¿ç”¨çš„æ•°æ®ã€‚æ¤æ—Óž¼Œ½Ž¡ç†å•ä¸ªçŠ¶æ€æœºè½¬æ¢ä»…表½CÞZ¸€‹Æ¡ç®€å•的转æ¢åQŒå› æ¤æˆ‘ž®†æ·»åŠ å¦‚æ¸…å• 17 所½Cºçš„‹¹‹è¯•åQ?/p>
æ¸…å• 17. æž„å¾çŠ¶æ€æœºçš„状æ€è{æ?/strong>
it "should transition to :checking_out upon #trigger(:checkout) event " do @machine.trigger(:checkout) @machine.state.should == :checking_out end ~/rspec batate$ spec machine_spec.rb ...F 1) NoMethodError in 'Machine should transition to :checking_out upon #trigger(:checkout) event ' undefined method `trigger' for #<Machine:0x10c4d00> ./machine_spec.rb:24: Finished in 0.006153 seconds 4 examples, 1 failure |
æˆ‘éœ€è¦æŠµåˆ¶å¿«é€Ÿæž„å»ºå¤§é‡åŠŸèƒ½çš„è¯±æƒ‘ã€‚æˆ‘åº”è¯¥åªç¼–写少é‡ä»£ç ,åªè¦ä½¿æµ‹è¯•通过å›_¯ã€‚清å?18 展示的è„P代将表示 API 和需求。这ž®Þpƒö够了åQ?/p>
æ¸…å• 18. 定义 trigger æ–ÒŽ³•
def trigger(event) @state = :checking_out end ~/rspec batate$ spec machine_spec.rb .... Finished in 0.005959 seconds 4 examples, 0 failures |
˜q™é‡Œå‡ºçŽ°äº†ä¸€ä¸ªæœ‰‘£çš„è¾Ò޳¨ã€‚在¾~–å†™ä»£ç æ—Óž¼Œæˆ‘两‹Æ¡éƒ½å¼„错了这个简å•çš„æ–ÒŽ³•。第一‹Æ¡æˆ‘˜q”回äº?:checkout
åQ›ç¬¬äºŒæ¬¡æˆ‘将状æ€è®¾¾|®äØ“ :checkout
è€Œä¸æ˜?:checking_out
。在‹¹‹è¯•ä¸ä‹É用较ž®çš„æ¥éª¤å¯ä»¥ä¸ºæˆ‘节çœå¤§é‡æ—‰™—´åQŒå› ä¸ºæµ‹è¯•ç”¨ä¾‹äØ“æˆ‘æ•èŽïLš„˜q™äº›é”™è¯¯åœ¨å°†æ¥çš„å¼€å‘ä¸å¾ˆéš¾æ•èŽ·åˆ°ã€‚æœ¬æ–‡çš„æœ€åŽä¸€ä¸ªæ¥éª¤æ˜¯å®žé™…执行一‹Æ¡çŠ¶æ€æœºè½¬æ¢ã€‚在½W¬ä¸€ä¸ªç¤ºä¾‹ä¸åQŒæˆ‘òq¶ä¸å…›_¿ƒå®žé™…çš„æœºå™¨çŠ¶æ€æ˜¯ä»€ä¹ˆæ ·åçš„ã€‚æˆ‘ä»…ä»…æ˜¯æ ¹æ®äº‹ä»¶è¿›è¡Œç›²ç›®è{æ¢ï¼Œè€Œä¸è€ƒè™‘状æ€ã€?/p>
ä¸¤èŠ‚ç‚¹çš„çŠ¶æ€æœºæ— 法执行˜q™ä¸ªæ“作åQŒæˆ‘需è¦åœ¨½W¬ä¸‰ä¸ªèŠ‚ç‚¹ä¸æž„å¾ã€‚我没有使用已有çš?before
æ–ÒŽ³•åQŒåªæ˜¯åœ¨æ–°çжæ€ä¸æ·ÕdŠ å¦å¤–的状æ€ã€‚我ž®†åœ¨‹¹‹è¯•用例ä¸è¿›è¡Œä¸¤‹Æ¡è{æ¢ï¼Œä»¥ç¡®ä¿çŠ¶æ€æœºèƒ½å¤Ÿæ£ç¡®åœ°æ‰§è¡Œè{æ¢ï¼Œå¦‚清å?19 所½Cºï¼š
it "should transition to :success upon #trigger(:accept_card)" do @machine.events = { :checkout => {:from => :shopping, :to => :checking_out}, :accept_card => {:from => :checking_out, :to => :success} } @machine.trigger(:checkout) @machine.state.should == :checking_out @machine.trigger(:accept_card) @machine.state.should == :success end ~/rspec batate$ spec machine_spec.rb ....F 1) 'Machine should transition to :success upon #trigger(:accept_card)' FAILED expected :success, got :checking_out (using ==) ./machine_spec.rb:37: Finished in 0.007564 seconds 5 examples, 1 failure |
˜q™ä¸ª‹¹‹è¯•ž®†ä‹Éç”?:checkout
å’?:accept_card
事äšgå»ºç«‹æ–°çš„çŠ¶æ€æœºã€‚在处熽{‘Ö‡ºæ—Óž¼Œæˆ‘选择使用两个事äšgè€Œä¸æ˜¯ä¸€ä¸ªï¼Œ˜q™æ ·å¯ä»¥é˜²æ¢å‘生åŒå‘½ä»¤ã€‚ç¾å‡ÞZ»£ç å¯ä»¥ç¡®ä¿çŠ¶æ€æœºåœ¨ç¾å‡ÞZ¹‹å‰å¤„äº?shopping
状æ€ã€‚第一‹Æ¡ç¾å‡ºé¦–å…ˆå°†çŠ¶æ€æœºä»?shopping
转æ¢ä¸?checking_out
ã€‚æµ‹è¯•ç”¨ä¾‹é€šè¿‡è§¦å‘ checkout
å’?accept_card
事äšg实现两个转æ¢åQŒåÆˆåœ¨è°ƒç”¨äº‹ä»¶ä¹‹åŽæ£€éªŒäº‹ä»¶çŠ¶æ€æ˜¯å¦æ£¼‹®ã€‚ä¸Žé¢„æœŸä¸€æ øP¼Œ‹¹‹è¯•用例å¤ÞpÓ| â€?æˆ‘åÆˆæ²¡æœ‰¾~–写处ç†å¤šä¸ªè½¬æ¢çš„触å‘器æ–ÒŽ³•。代ç ä¿®æ£åŒ…å«ä¸€è¡Œéžå¸”R‡è¦çš„代ç 。清å?20 å±•ç¤ºäº†çŠ¶æ€æœºçš„æ ¸å¿ƒï¼š
def trigger(event) @state = events[event][:to] end ~/rspec batate$ spec machine_spec.rb ..... Finished in 0.006511 seconds 5 examples, 0 failures |
‹¹‹è¯•å¯ä»¥˜q行。这些粗¾p™çš„ä»£ç ½W¬ä¸€‹Æ¡æ¼”å˜äؓ真æ£å¯ä»¥¿UîC¹‹ä¸ºçŠ¶æ€æœºçš„东è¥Ñ€‚但是这˜q˜è¿œ˜qœä¸å¤Ÿã€‚ç›®å‰ï¼ŒçŠ¶æ€æœº¾~ÞZ¹ä¸¥å¯†æ€§ã€‚ä¸½Ž¡çŠ¶æ€å¦‚ä½•ï¼ŒçŠ¶æ€æœºéƒ½ä¼šè§¦å‘事äšg。例如,当处äº?shopping
çŠ¶æ€æ—¶åQŒè§¦å?:accept_card
òq¶ä¸ä¼šè{æ¢äØ“ :success
状æ€ã€‚您åªèƒ½å¤Ÿä»Ž :checking_out
状æ€è§¦å?:accept_card
。在¾~–程术è¯ä¸ï¼Œtrigger
æ–ÒŽ³•的范围应针对事äšg。我ž®†ç¼–写一个测试æ¥è§£å†³é—®é¢˜åQŒç„¶åŽä¿®å¤?bug。我ž®†ç¼–写一个负‹¹‹è¯•åQˆnegative teståQ‰ï¼ŒåÏx–a€ä¸€ä¸ªä¸åº”该出现的行为,如清å?21 所½Cºï¼š
it "should not transition from :shopping to :success upon :accept_card" do @machine.events = { :checkout => {:from => :shopping, :to => :checking_out}, :accept_card => {:from => :checking_out, :to => :success} } @machine.trigger(:accept_card) @machine.state.should_not == :success end rspec batate$ spec machine_spec.rb .....F 1) 'Machine should not transition from :shopping to :success upon :accept_card' FAILED expected not == :success, got :success ./machine_spec.rb:47: Finished in 0.006582 seconds 6 examples, 1 failure |
现在å¯ä»¥å†æ¬¡˜q行‹¹‹è¯•åQŒå…¶ä¸ä¸€ä¸ªæµ‹è¯•å¦‚é¢„æœŸä¸€æ ¯‚¿è¡Œå¤±è´¥ã€‚ä¿®å¤ä»£ç åŒæ ·åªæœ‰ä¸€è¡Œï¼Œå¦‚清å?22 所½Cºï¼š
def trigger(event) @state = events[event][:to] if state == events[event][:from] end rspec batate$ spec machine_spec.rb ...... Finished in 0.006873 seconds 6 examples, 0 failures |
![]() ![]() |
现在åQŒæˆ‘å…ähœ‰ä¸€ä¸ªå¯½Ž€å•è¿è¡Œçš„çŠ¶æ€æœºã€‚æ— è®ÞZ»Žå“ªæ–¹é¢æ¥è¯ß_¼Œå®ƒéƒ½ä¸æ˜¯ä¸€ä¸ªå®Œ¾ŸŽçš„½E‹åºã€‚它˜q˜å…·æœ‰ä¸‹é¢è¿™äº›é—®é¢˜ï¼š
submit
å’?cancel
事äšgå¯èƒ½éœ€è¦å¤„于多个状æ€ã€?
但是åQŒæ‚¨˜q˜å¯ä»¥çœ‹åˆŽÍ¼Œ˜q™ä¸ªçŠ¶æ€æœºå·²ç»èƒ½å¤Ÿæ»¡èƒö一些需求了。我˜q˜å…·å¤‡ä¸€ä¸ªæ˜q°ç³»¾lŸè¡Œä¸ºçš„æ–‡æ¡£åQŒè¿™æ˜¯è¿›è¡Œä¸€¾pÕdˆ—‹¹‹è¯•的好èµïL‚¹ã€‚æ¯ä¸ªæµ‹è¯•用例都支挾pÈ»Ÿçš„一个基本需求。事实上åQŒé€šè¿‡˜q行 spec machine_spec.rb --format specdoc
åQŒæ‚¨å¯ä»¥æŸ¥çœ‹ç”Þq³»¾lŸè§„范组æˆçš„基本报告åQŒå¦‚æ¸…å• 23 所½Cºï¼š
spec machine_spec.rb --format specdoc Machine - should initially have a state of the first state - should remember a list of valid states - should remember a list of events with transitions - should transition to :checking_out upon #trigger(:checkout) event - should transition to :success upon #trigger(:accept_card) - should not transition from :shopping to :success upon :accept_card Finished in 0.006868 seconds |
‹¹‹è¯•驱动æ–ÒŽ³•òq¶ä¸é€‚åˆæ‰€æœ‰ähåQŒä½†æ˜¯è¶Šæ¥è¶Šå¤šçš„人开始ä‹É用这¿U技术,使用它构建具有絋zÀL€§å’Œé€‚应性的高质é‡ä»£ç ,òq¶ä¸”æ ÒŽ®‹¹‹è¯•从头构å¾ä»£ç 。当ç„Óž¼Œæ‚¨ä¹Ÿå¯ä»¥é€šè¿‡å…¶ä»–框架åQˆå¦‚ test_unitåQ‰èŽ·å¾—ç›¸åŒçš„优点。RSpec ˜q˜æä¾›äº†ä¼˜ç§€çš„实现方法。这¿Uæ–°‹¹‹è¯•框架的一大亮点就是代ç 的表示。新手尤其å¯ä»¥ä»Ž˜q™ç§è¡Œäؓ驱动的测试方法ä¸å—益。请ž®è¯•ä½¿ç”¨è¯¥æ¡†æž¶åÆˆå‘Šè¯‰æˆ‘æ‚¨çš„æ„Ÿå—ã€?/p>