<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;DUUBSXc9eyp7ImA9WxNbF0k.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478</id><updated>2009-11-20T20:00:58.963Z</updated><title>David Peterson</title><subtitle type="html" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://blog.davidpeterson.co.uk/" /><link rel="hub" href="http://pubsubhubbub.appspot.com/" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>17</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/david_peterson" /><feedburner:info uri="david_peterson" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry gd:etag="W/&quot;D0IER3w4fip7ImA9WxNWFU0.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-8120805881022321781</id><published>2009-10-14T08:19:00.005+01:00</published><updated>2009-10-14T08:31:46.236+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-10-14T08:31:46.236+01:00</app:edited><title>Kanban Blog</title><content type="html">&lt;p&gt;Having leapt aboard the Kanbandwagon, I've started a new blog about Kanban at, funnily enough, &lt;a href="http://www.kanbanblog.com"&gt;kanbanblog.com&lt;/a&gt;. Here's a taster of my first post:&lt;/p&gt;  &lt;p&gt; Which of these feature ideas should we select as a priority? &lt;/p&gt;  &lt;table style="border-collapse: collapse; border: 1px solid black" cellpadding="4px"&gt; &lt;tr&gt; &lt;th align="center" style="border: 1px solid black; background-color: #ccc"&gt;Feature&lt;/th&gt; &lt;th align="center" style="border: 1px solid black; background-color: #ccc"&gt;Estimated&lt;br /&gt;Value&lt;/th&gt; &lt;th align="center" style="border: 1px solid black; background-color: #ccc"&gt;Estimated&lt;br /&gt;Production Time&lt;/th&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td align="center" style="border-right: 1px solid black;"&gt;A&lt;/td&gt; &lt;td align="right" style="border-right: 1px solid black;"&gt;$100,000&lt;/td&gt; &lt;td align="center"&gt;40 hrs&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td align="center"  style="border-right: 1px solid black;"&gt;B&lt;/td&gt; &lt;td align="right" style="border-right: 1px solid black;"&gt;$80,000&lt;/td&gt; &lt;td align="center"&gt;40 hrs&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td align="center" style="border-right: 1px solid black;"&gt;C&lt;/td&gt; &lt;td align="right" style="border-right: 1px solid black;"&gt;$60,000&lt;/td&gt; &lt;td align="center"&gt;40 hrs&lt;/td&gt; &lt;/tr&gt; &lt;/table&gt; &lt;br /&gt; &lt;p&gt; Feature A? It's a no-brainer, right? &lt;/p&gt;  &lt;p&gt; &lt;a href="http://www.kanbanblog.com/article/time-at-bottleneck.html"&gt;Not necessarily&lt;/a&gt;... &lt;/p&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-8120805881022321781?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/FQUbe4-HeTk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/8120805881022321781/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=8120805881022321781" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/8120805881022321781?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/8120805881022321781?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/FQUbe4-HeTk/kanban-blog.html" title="Kanban Blog" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2009/10/kanban-blog.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUMGR3k5cCp7ImA9WxJQE08.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-3470993298697054093</id><published>2009-05-25T18:57:00.013+01:00</published><updated>2009-05-26T08:37:06.728+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-26T08:37:06.728+01:00</app:edited><title>Concordion 1.3.1</title><content type="html">&lt;p&gt;Concordion 1.3.1 was released last month with a handful of &lt;a href="http://www.concordion.org/Download.html"&gt;new and noteworthy features&lt;/a&gt;. There was an announcement on the Yahoo mailing list, but I wanted to wait for the Concordion.NET port to catch up before making an announcement on this blog.&lt;/p&gt;&lt;p&gt;For those who didn't know, there are now &lt;a href="http://www.concordion.org/Ports.html"&gt;versions of Concordion&lt;/a&gt; for:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Java&lt;/b&gt; (&lt;a href="http://www.concordion.org"&gt;Concordion&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;&lt;b&gt;.NET&lt;/b&gt; (&lt;a href="http://code.google.com/p/concordion-net/"&gt;Concordion.NET&lt;/a&gt;) by Jeffrey Cameron&lt;/li&gt;&lt;li&gt;&lt;b&gt;Python&lt;/b&gt; (&lt;a href="http://code.google.com/p/pyconcordion/"&gt;PyConcordion&lt;/a&gt;) by JC Plessis&lt;/li&gt;&lt;li&gt;&lt;b&gt;Ruby&lt;/b&gt; (&lt;a href="http://ruby-concordion.rubyforge.org/"&gt;ruby-concordion&lt;/a&gt;) by Ben Goodspeed, Ariel Valentin and Chris Gardner&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So, if you're using Cucumber in Ruby, or FitNesse in .NET check out the alternative. No other acceptance testing framework lets you create such readable acceptance tests as Concordion's. The key advantage in having readable tests is that when you come back in three months time and wonder what the heck you were doing, you might actually have a clue.&lt;/p&gt;&lt;div style="background-color: #fffff7; padding: 8px;"&gt;&lt;b&gt;Update:&lt;/b&gt; Jeffrey Cameron has started a &lt;a href="http://living-in-concordion.blogspot.com/"&gt;blog about Concordion.NET&lt;/a&gt; and how to use it.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-3470993298697054093?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/zDrPVAKmLVI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/3470993298697054093/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=3470993298697054093" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/3470993298697054093?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/3470993298697054093?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/zDrPVAKmLVI/concordion-131.html" title="Concordion 1.3.1" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2009/05/concordion-131.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Dk4CRnwyfyp7ImA9WxVWEEs.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-6999702038716982148</id><published>2009-02-19T11:11:00.006Z</published><updated>2009-02-19T17:36:07.297Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-19T17:36:07.297Z</app:edited><title>Avoid not using positive method names</title><content type="html">&lt;p&gt;I'm integrating with &lt;a href="http://static.springsource.org/spring-security/site/index.html"&gt;Spring Security&lt;/a&gt; at the moment. It seems to be a well-designed framework, but I got myself confused this morning trying to implement some methods of the &lt;a href="http://static.springsource.org/spring-security/site/apidocs/org/springframework/security/userdetails/UserDetails.html"&gt;UserDetails&lt;/a&gt; interface:&lt;/p&gt;&lt;p style="padding: 8px 16px 8px 16px; background-color: #f7f7f7; line-height: 130%;"&gt;&lt;code style="font-size: 10pt"&gt;boolean &lt;b&gt;isAccountNonExpired()&lt;/b&gt;&lt;/code&gt;&lt;br/&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;Indicates whether the user's account has expired.&lt;br/&gt;&lt;code style="font-size: 10pt"&gt;boolean &lt;b&gt;isAccountNonLocked()&lt;/b&gt;&lt;/code&gt;&lt;br/&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;Indicates whether the user is locked or unlocked.&lt;br/&gt;&lt;code style="font-size: 10pt"&gt;boolean &lt;b&gt;isCredentialsNonExpired()&lt;/b&gt;&lt;/code&gt;&lt;br/&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;Indicates whether the user's credentials (password) has expired.&lt;br/&gt;&lt;code style="font-size: 10pt"&gt;boolean &lt;b&gt;isEnabled()&lt;/b&gt;&lt;/code&gt;&lt;br/&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;Indicates whether the user is enabled or disabled.&lt;/p&gt;&lt;p&gt;I imagine the designers wanted all the answers to be &lt;code style="font-size: 10pt"&gt;&lt;b&gt;true&lt;/b&gt;&lt;/code&gt; for the normal "happy" situation. But my brain couldn't cope with all the negatives, so I ended up writing a second set of positively-named methods to call from the negative ones. :-)&lt;/p&gt;&lt;pre style="font-size: 10pt; padding: 8px 16px 8px 16px; background-color: #f7f7f7;"&gt;&lt;b&gt;public boolean&lt;/b&gt; isAccountNonExpired() {&lt;br /&gt;    &lt;b&gt;return&lt;/b&gt; !isAccountExpired();&lt;br /&gt;}&lt;br /&gt;... etc.&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-6999702038716982148?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/AvN-_y2cdhY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/6999702038716982148/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=6999702038716982148" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/6999702038716982148?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/6999702038716982148?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/AvN-_y2cdhY/avoid-not-using-positive-method-names.html" title="Avoid not using positive method names" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2009/02/avoid-not-using-positive-method-names.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkYGRHY6cSp7ImA9WxVXFEk.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-3397977736589996955</id><published>2009-02-12T13:37:00.007Z</published><updated>2009-02-12T14:15:25.819Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-12T14:15:25.819Z</app:edited><title>GWT 1.6 Milestone 1</title><content type="html">In case you weren't aware, GWT 1.6 Milestone 1 has recently &lt;a href="http://groups.google.com/group/Google-Web-Toolkit/browse_thread/thread/3e7e6cc3b35ad98a"&gt;been released&lt;/a&gt;. The biggest change is that projects are now laid out using a standard WAR file structure. This makes hosted mode much better because you now have full control over the web.xml file. They've also switched from Tomcat to Jetty internally (Jetty's start-up time is quicker) and they've added a button to restart the server without having to exit and restart hosted mode. Very convenient.&lt;br /&gt;&lt;br /&gt;I'm combining &lt;a href="http://code.google.com/webtoolkit/"&gt;GWT&lt;/a&gt; with &lt;a href="http://tapestry.apache.org/tapestry5/"&gt;Tapestry 5&lt;/a&gt;, in my current project, so these changes are very helpful. I can now run them together in hosted mode.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-3397977736589996955?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/rXAfGrUjA3w" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/3397977736589996955/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=3397977736589996955" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/3397977736589996955?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/3397977736589996955?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/rXAfGrUjA3w/gwt-16-milestone-1.html" title="GWT 1.6 Milestone 1" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2009/02/gwt-16-milestone-1.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUEFRHw_fyp7ImA9WxRWEkQ.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-5445682949828859455</id><published>2008-10-14T18:06:00.027+01:00</published><updated>2008-10-29T16:26:55.247Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-10-29T16:26:55.247Z</app:edited><title>Label verbose items to simplify back-references</title><content type="html">This is quite an obvious little pattern, but I think it's still worth documenting.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Some repetition is harmless&lt;/b&gt;&lt;br /&gt;When I'm writing an example of a required behaviour, I find that I often have to refer to something I've already mentioned. For example:&lt;br /&gt;&lt;blockquote style="background-color: #d9ffd9; padding: 12px; background-image: url('http://www.concordion.org/image/technique/Good.png'); background-repeat: no-repeat; background-position: top right;"&gt;Given the following names: &lt;b&gt;Charles, Naomi, Melissa, Arnold&lt;/b&gt;&lt;br /&gt;Searching for '&lt;b&gt;ar&lt;/b&gt;' should find: &lt;b&gt;Charles, Arnold&lt;/b&gt;&lt;/blockquote&gt;&lt;p&gt;Here, I have mentioned the names Charles and Arnold twice: once when describing the context and once when describing the expected outcome. This isn't a problem because I purposely kept the names short to make the example easy to follow.&lt;/p&gt;&lt;p&gt;&lt;b&gt;But repetition can become awkward&lt;/b&gt;&lt;br /&gt;If the values are long or complicated then the examples can become hard to read and maintain. For example:&lt;/p&gt;&lt;blockquote style="background-color: #ffd9d9; padding: 12px; background-image: url('http://www.concordion.org/image/technique/Bad.png'); background-repeat: no-repeat; background-position: top right;"&gt;Given the following addresses:&lt;blockquote&gt;18 Cedar Row, Enfield, Middx, EN8 9TT&lt;br /&gt;"The Grange", 177 Hounslow Road, Epping, Essex, HA10 4PL&lt;br /&gt;2b Armsfield Avenue, London, SE1 2BN&lt;br /&gt;80 Commercial Street, Aberdeen, AR19 1TB&lt;/blockquote&gt;Searching for '&lt;b&gt;ar&lt;/b&gt;' should find:&lt;blockquote&gt;18 Cedar Row, Enfield, Middx, EN8 9TT&lt;br /&gt;2b Armsfield Avenue, London, SE1 2BN&lt;br /&gt;80 Commercial Street, Aberdeen, AR19 1TB&lt;/blockquote&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;b&gt;So, use labels for verbose values&lt;/b&gt;&lt;br /&gt;Labelling each item allows you to use the label, instead of the value, whenever you need to refer to it.&lt;/p&gt;&lt;blockquote style="background-color: #d9ffd9; padding: 12px; background-image: url('http://www.concordion.org/image/technique/Good.png'); background-repeat: no-repeat; background-position: top right;"&gt;Given the following addresses:&lt;blockquote&gt;&lt;b&gt;(1)&lt;/b&gt; 18 Cedar Row, Enfield, Middx, EN8 9TT&lt;br /&gt;&lt;b&gt;(2)&lt;/b&gt; "The Grange", 177 Hounslow Road, Epping, Essex, HA10 4PL&lt;br /&gt;&lt;b&gt;(3)&lt;/b&gt; 2b Armsfield Avenue, London, SE1 2BN&lt;br /&gt;&lt;b&gt;(4)&lt;/b&gt; 80 Commercial Street, Aberdeen, AR19 1TB&lt;/blockquote&gt;Searching for '&lt;b&gt;ar&lt;/b&gt;' should match addresses: &lt;b&gt;(1), (3), and (4)&lt;/b&gt;&lt;/blockquote&gt;&lt;p&gt;I call them &lt;i&gt;labels&lt;/i&gt; or &lt;i&gt;pseudo-identifiers&lt;/i&gt; to distinguish them from real identifiers in the system under test. For example, in the implementation we might have a database table called &lt;code style="font-size: 10pt"&gt;Address&lt;/code&gt; with an identifier column called &lt;code style="font-size: 10pt"&gt;addressId&lt;/code&gt;. But the identifiers we use in the examples are merely a way of making the example easier to read. They are not a reflection of any particular database model. Any mapping between real identifiers and pseudo-identifiers must always be handled behind the scenes in the fixture code.&lt;/p&gt;&lt;p&gt;The labels don't have to be numeric. They could be letters (A, B, C) or other more meaningful short strings.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Labels can also help you to hide details&lt;/b&gt;&lt;br /&gt;Another advantage of labels is that they can let you gloss over unnecessary details. For example:&lt;/p&gt;&lt;img src="http://www.davidpeterson.co.uk/image/misc/acceptance-test-pseudo-identifiers.png" style="border: 0px" alt="Example acceptance test showing use of pseudo-identifiers" /&gt;&lt;p&gt;Using labels for bookings (1, 2) and for booking references (A, B) means we don't need to go into any details of what we're booking or what the booking references look like. If the format of the booking references is important, we can cover that in another test, but, in this test, we're not locking ourselves into any particular format. All we're doing is demonstrating the rule: "Each booking is given a unique booking reference that can be used to look-up the booking."&lt;/p&gt;&lt;p&gt;The fixture code will have the following methods:&lt;/p&gt;&lt;pre style="font-size: 10pt; background-color: #f7f7f7;"&gt;&lt;br /&gt;&lt;b style="color: #7f0055"&gt;public class&lt;/b&gt; IdentifyingBookingsTest &lt;b style="color: #7f0055"&gt;extends&lt;/b&gt; ConcordionTestCase {&lt;br /&gt;&lt;br /&gt;    &lt;b style="color: #7f0055"&gt;public void&lt;/b&gt; makeBooking(String bookingNumber, String bookingRefAlias) {&lt;br /&gt;        ...&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;b style="color: #7f0055"&gt;public&lt;/b&gt; String lookupBooking(String bookingRefAlias) {&lt;br /&gt;        ...&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Having this extra level of indirection may make the fixture code work harder, but that is not a problem. It is much better to put complexity and implementation-related assumptions in the fixture code, where we have powerful refactoring tools at our disposal, than to complicate the specification and make it more fragile.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-5445682949828859455?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/c2Vscn9HzCk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/5445682949828859455/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=5445682949828859455" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/5445682949828859455?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/5445682949828859455?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/c2Vscn9HzCk/acceptance-testing-pattern-pseudo.html" title="Label verbose items to simplify back-references" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2008/10/acceptance-testing-pattern-pseudo.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUECQ30-fip7ImA9WxRQFE0.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-3375993026919100789</id><published>2008-10-07T19:15:00.005+01:00</published><updated>2008-10-07T19:21:02.356+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-10-07T19:21:02.356+01:00</app:edited><title>Please apply the patch and reboot.</title><content type="html">&lt;img style="border: 0" src="http://www.davidpeterson.co.uk/image/cartoon/patch.jpg" alt="Cartoon showing two image of the same woman, with different footwear and an eye patch. The caption reads: 'Please apply the patch and reboot.'" /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-3375993026919100789?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/qLiSLT4omGg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/3375993026919100789/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=3375993026919100789" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/3375993026919100789?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/3375993026919100789?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/qLiSLT4omGg/please-apply-patch-and-reboot.html" title="Please apply the patch and reboot." /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2008/10/please-apply-patch-and-reboot.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0UHQHY9eyp7ImA9WxZaF0k.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-1371225360603521590</id><published>2008-05-02T16:04:00.006+01:00</published><updated>2008-05-02T16:13:51.863+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-05-02T16:13:51.863+01:00</app:edited><title>Concordion for Ruby and Maven</title><content type="html">&lt;b&gt;Concordion for Ruby&lt;/b&gt;&lt;br/&gt;Ben Goodspeed has written a very neat &lt;a href="http://code.google.com/p/rcor/"&gt;Ruby port of Concordion&lt;/a&gt; and packaged it as a &lt;a href="http://code.google.com/p/rcor/downloads/list"&gt;gem&lt;/a&gt;. There are currently some &lt;a href="http://code.google.com/p/rcor/wiki/DifferencesBetweenRcorAndConcordion"&gt;minor syntactical differences&lt;/a&gt; between the Java version and the Ruby version because the Ruby version doesn't do &lt;a href="http://www.concordion.org/Tutorial.html#executeUnusualSentences"&gt;lookahead variable bindings&lt;/a&gt; yet (Ben's working on that) but the core functionality is already implemented (set, execute, assertEquals, and verifyRows).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Maven Integration&lt;/b&gt;&lt;br/&gt;Several people requested that the Java version of Concordion be added to Maven repositories and thanks to &lt;a href="http://jmbeas.blogspot.com/"&gt;José Manuel Beas&lt;/a&gt;, Wang Yi Zhou, &lt;a href="http://www.jroller.com/habuma/"&gt;Craig Walls&lt;/a&gt;, and others, this is now done (instructions for usage with Maven are &lt;a href="http://code.google.com/p/concordion/wiki/MavenizedSamples"&gt;here&lt;/a&gt;). Concordion 1.3.0 has the same functionality as 1.2.0 but with Maven support.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-1371225360603521590?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/q7z4lvJFGbU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/1371225360603521590/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=1371225360603521590" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/1371225360603521590?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/1371225360603521590?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/q7z4lvJFGbU/concordion-for-ruby-and-maven.html" title="Concordion for Ruby and Maven" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2008/05/concordion-for-ruby-and-maven.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0ICQH4zfyp7ImA9WxZWFUQ.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-2482824208183804654</id><published>2008-03-15T16:25:00.008Z</published><updated>2008-03-15T16:46:01.087Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-15T16:46:01.087Z</app:edited><title>Concordion 1.2.0</title><content type="html">I've released a new version of &lt;a href="http://www.concordion.org"&gt;Concordion&lt;/a&gt;.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Support for full set of JUnit4.4 annotations (&lt;i&gt;@Before, @After&lt;/i&gt; etc.)&lt;/li&gt;&lt;li&gt;New command: &lt;b&gt;assertTrue&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;For example, this instrumented specification:&lt;pre style="font-size: 10pt; background-color: #f7f7f7; padding: 4px;"&gt;&amp;lt;p&amp;gt;&lt;br /&gt;    The first name &amp;lt;span concordion:set="#firstName"&amp;gt;Bob&amp;lt;/span&amp;gt;,&lt;br /&gt;    &amp;lt;span &lt;b&gt;concordion:assertTrue="#firstName.startsWith(#letter)"&lt;/b&gt;&amp;gt;&lt;br /&gt;    starts with the letter &amp;lt;b concordion:set="#letter"&amp;gt;B&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;.&lt;br /&gt;&amp;lt;/p&amp;gt;&lt;/pre&gt;Will result in the following output:&lt;br /&gt;&lt;img style="border: 0" alt="Success" src="http://www.davidpeterson.co.uk/image/misc/concordion-1.2.0-assertTrue-success.png"/&gt;&lt;br /&gt;&lt;br /&gt;A failure would look like this:&lt;br /&gt;&lt;img style="border: 0" alt="Failure" src="http://www.davidpeterson.co.uk/image/misc/concordion-1.2.0-assertTrue-failure.png"/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-2482824208183804654?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/P9KTUhqCj9U" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/2482824208183804654/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=2482824208183804654" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/2482824208183804654?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/2482824208183804654?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/P9KTUhqCj9U/concordion-120.html" title="Concordion 1.2.0" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2008/03/concordion-120.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkADR30yeip7ImA9WxZWFU8.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-6515429463856834318</id><published>2008-03-13T12:21:00.011Z</published><updated>2008-03-14T19:59:36.392Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-14T19:59:36.392Z</app:edited><title>Big Hairy Tables</title><content type="html">I like Keith Braithwaite's "software gauges" metaphor. As &lt;a href="http://peripateticaxiom.blogspot.com/2007/09/gauges.html"&gt;he explains&lt;/a&gt;, a gauge is a shortcut for deciding whether something passes or fails some criteria. For example, if your bag fits inside the metal cage at an airport then it can be taken onboard as hand luggage. You don't need to use a tape measure. What's interesting about gauges is that you don't necessarily need to be able to articulate "the rules"; if you need to know the rules you can infer them from the gauge.&lt;br /&gt;&lt;br /&gt;However, I'm not convinced with what &lt;a href="http://peripateticaxiom.blogspot.com/2008/03/tests-and-gauges.html"&gt;Keith says&lt;/a&gt; about trader spreadsheets making good gauges. Obviously he's had success with them, so they definitely can be made to work, but would they have worked even better another way? Basically what it boils down to is that I like tables; I just don't like &lt;span style="font-style:italic;"&gt;big hairy tables&lt;/span&gt;!&lt;br /&gt;&lt;br /&gt;If the hand-luggage cage can be taken as an example of good practice then some of the properties of a good gauge appear to be: sturdy and reliable; correct enough for all practical purposes; obvious; unambiguous; quick to use; and simple to understand by the gauge user.&lt;br /&gt;&lt;br /&gt;The spreadsheets Keith showed me contained a large number of sparsely populated columns of denormalised data with lots of magic numbers and magic strings. This doesn't seem to stack up well against the list of desirable traits for a gauge. They're not designed with the gauge user in mind. If your domain model doesn't fit the gauge, is it because the model is wrong? Or the gauge is wrong? Or your interpretation of the gauge is wrong? With so many moving parts, it's got to be difficult to work out, even with help from the traders.&lt;br /&gt;&lt;br /&gt;Isn't it better to treat the spreadsheets as a starting point rather than an ending point? And then have someone skilled at analysis and abstraction work with the traders and extract smaller, more practical gauges. There's nothing to stop you holding these gauges in spreadsheets too. I have a lot of respect for the customer, but I don't see why, just because the traders have written a spreadsheet, you can't help them improve it. They've got their skills you've got yours.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-6515429463856834318?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/4FRDKfgILOI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/6515429463856834318/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=6515429463856834318" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/6515429463856834318?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/6515429463856834318?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/4FRDKfgILOI/big-hairy-tables.html" title="Big Hairy Tables" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2008/03/big-hairy-tables.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C04HSHw8eyp7ImA9WxZWEEQ.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-5247658967136994055</id><published>2008-03-09T17:10:00.009Z</published><updated>2008-03-09T19:45:39.273Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-03-09T19:45:39.273Z</app:edited><title>Acceptance Test Driven Development</title><content type="html">Most of the examples on the &lt;a href="http://www.concordion.org"&gt;Concordion website&lt;/a&gt; are technical in nature, so I've put together a short business-focused example. Please &lt;a href="http://www.concordion.org/Example.html"&gt;take a look&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I'm not trying to push Concordion on you. You can do something similar with other test frameworks. It's really the approach that I'm trying to get across: focusing the acceptance tests on goals, not solutions; and decomposing behaviours, keeping each test as isolated and simple as you can.&lt;br /&gt;&lt;br /&gt;While I'm here, let me join in the &lt;a href="http://www.testing.com/cgi-bin/blog/2003/08/21#agile-testing-project-1"&gt;2x2 matrix&lt;/a&gt; &lt;a href="http://www.testingreflections.com/node/view/6704"&gt;fun&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;img style="border: 0px" alt="2x2 Matrix: Abstract vs Concrete / Goal vs Solution" src="http://www.davidpeterson.co.uk/image/misc/GoalOriented.png"/&gt;&lt;br /&gt;&lt;br /&gt;The user-interface (UI) of an application is a solution, not a goal. I often see people writing test scripts in terms of direct user interface interactions (e.g. using a record/playback/verify tool like Selenium) unaware that by doing so they're locking themselves into a particular design.&lt;br /&gt;&lt;br /&gt;If, instead, they &lt;a href="http://www.concordion.org/Technique.html"&gt;hid the scripting&lt;/a&gt; behind goal-oriented acceptance tests they would leave themselves a lot more freedom to change the solution. I guess not all teams need that kind of freedom. But I wonder how many even know there's a choice?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-5247658967136994055?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/pPiRtmTYgWk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/5247658967136994055/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=5247658967136994055" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/5247658967136994055?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/5247658967136994055?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/pPiRtmTYgWk/acceptance-test-driven-development.html" title="Acceptance Test Driven Development" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2008/03/acceptance-test-driven-development.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEEGQnw-fCp7ImA9WxZRFkg.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-4252474055805754741</id><published>2008-02-09T20:31:00.000Z</published><updated>2008-02-10T15:17:03.254Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-10T15:17:03.254Z</app:edited><title>What do I mean by a "Scripting DSL"?</title><content type="html">I have had a couple of e-mail questions recently, from people who have read the &lt;a href="http://www.concordion.org"&gt;Concordion&lt;/a&gt; documentation, asking for more detail about what I mean by a DSL (Domain-Specific Language) for scripting, as shown in this diagram:&lt;br /&gt;&lt;br /&gt;&lt;img border="0" style="border: 0px;" src="http://www.concordion.org/image/technique/SpecFixtureScriptingSystem.png"/&gt;&lt;br /&gt;I think it's easiest to explain with a realistic implementation of a fixture:&lt;br /&gt;&lt;pre style="font-size: 10pt"&gt;&lt;br /&gt;&lt;b&gt;public&lt;/b&gt; String getGreetingFor(String firstName) {&lt;br /&gt;    User user = &lt;b&gt;new&lt;/b&gt; UserBuilder()&lt;br /&gt;        .withReasonableDefaults()&lt;br /&gt;        .withFirstName(firstName)&lt;br /&gt;        .build();&lt;br /&gt;    BrowserSession session = &lt;b&gt;new&lt;/b&gt; BrowserSession();&lt;br /&gt;    &lt;b&gt;new&lt;/b&gt; UserAdminAction(session).setUp(user);&lt;br /&gt;    &lt;b&gt;new&lt;/b&gt; LoginDriver(session).login(user);&lt;br /&gt;    &lt;b&gt;return new&lt;/b&gt; HomePageDriver(session).scrapeGreeting()&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;In this case, the DSL is made up of the Builder, Action and Driver classes. These implement and hide all the dirty work of setting up the user and navigating around the website.&lt;br /&gt;&lt;br /&gt;The Driver classes extend an AbstractDriver class. The abstract base class provides protected methods for clicking on links, scraping the contents of elements, typing text, selecting radio buttons etc. &lt;br /&gt;&lt;pre style="font-size: 10pt"&gt;&lt;br /&gt;&lt;b&gt;public abstract class&lt;/b&gt; AbstractDriver {&lt;br /&gt;&lt;br /&gt;    &lt;b&gt;private&lt;/b&gt; BrowserSession session;&lt;br /&gt;&lt;br /&gt;    &lt;b&gt;public&lt;/b&gt; AbstractDriver(BrowserSession session) {&lt;br /&gt;        this.setSession(session);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;b&gt;protected&lt;/b&gt; HtmlPage getHtmlPage() {&lt;br /&gt;        &lt;b&gt;return&lt;/b&gt; session.getHtmlPage();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;b&gt;protected&lt;/b&gt; HtmlElement getElement(String id) {&lt;br /&gt;        &lt;b&gt;return&lt;/b&gt; getHtmlPage().getHtmlElementById(id);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;b&gt;protected&lt;/b&gt; String scrapeText(String id) {&lt;br /&gt;        &lt;b&gt;return&lt;/b&gt; getElement(id).asText();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;b&gt;protected&lt;/b&gt; void click(String id) {&lt;br /&gt;        ((ClickableElement) getElement(id)).click();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // etc.&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;The subclasses use these methods, but do not reveal them publicly. The public interface is at a higher level of abstraction.&lt;br /&gt;&lt;pre style="font-size: 10pt"&gt;&lt;br /&gt;&lt;b&gt;public class&lt;/b&gt; HomePageDriver &lt;b&gt;extends&lt;/b&gt; AbstractDriver {&lt;br /&gt;&lt;br /&gt;    &lt;b&gt;public&lt;/b&gt; HomePageDriver(BrowserSession session) {&lt;br /&gt;        super(session);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;b&gt;public&lt;/b&gt; String scrapeGreeting() {&lt;br /&gt;        &lt;b&gt;return&lt;/b&gt; scrapeText(HomePage.GREETING_ID);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;b&gt;public void&lt;/b&gt; clickSearch() {&lt;br /&gt;        click(HomePage.SEARCH_LINK_ID);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // etc.&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Actions encapsulate a series of driver clicks to perform some higher-level action and remove duplication across tests. For example, behind the scenes the UserAdminAction may use a LoginDriver, UserAdminPageDriver, UserDetailsPageDriver, LogoutDriver. Actions may be nested.&lt;br /&gt;&lt;br /&gt;You should also read Nat Pryce's series of blog posts on &lt;a href="http://nat.truemesh.com/archives/000714.html"&gt;Test Data Builders&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-4252474055805754741?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/LUNjt-wn4fo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/4252474055805754741/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=4252474055805754741" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/4252474055805754741?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/4252474055805754741?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/LUNjt-wn4fo/what-do-i-mean-by-scripting-dsl.html" title="What do I mean by a &quot;Scripting DSL&quot;?" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2008/02/what-do-i-mean-by-scripting-dsl.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEQMQXk8cCp7ImA9WxZSFEg.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-3335321671784184515</id><published>2008-01-27T17:29:00.000Z</published><updated>2008-01-27T17:53:00.778Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-27T17:53:00.778Z</app:edited><title>Concordion 1.1.0</title><content type="html">I've released a new version of my Java acceptance testing framework: &lt;a href="http://www.concordion.org"&gt;Concordion&lt;/a&gt;. The main new feature is support for &lt;a href="http://www.junit.org"&gt;JUnit&amp;nbsp;4&lt;/a&gt; test runners.&lt;br /&gt;&lt;br /&gt;For example this &lt;code&gt;Demo.html&lt;/code&gt; specification:&lt;br /&gt;&lt;pre&gt;&amp;lt;html &lt;b&gt;xmlns:concordion="http://www.concordion.org/2007/concordion"&lt;/b&gt;&amp;gt;&lt;br /&gt;    &amp;lt;body&amp;gt;&lt;br /&gt;        &amp;lt;p&amp;gt;&lt;br /&gt;            The greeting for user &amp;lt;span &lt;b&gt;concordion:set="#firstName"&amp;gt;Bob&amp;lt;&lt;/b&gt;/span&amp;gt;&lt;br /&gt;            will be:&lt;br /&gt;            &amp;lt;span &lt;b&gt;concordion:assertEquals="greetingFor(#firstName)"&lt;/b&gt;&amp;gt;Hello Bob!&amp;lt;/span&amp;gt;&lt;br /&gt;        &amp;lt;/p&amp;gt;&lt;br /&gt;    &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Can be run with the following JUnit 4 test case:&lt;br /&gt;&lt;pre&gt;&lt;b&gt;import&lt;/b&gt; org.concordion.integration.junit4.ConcordionRunner;&lt;br /&gt;&lt;b&gt;import&lt;/b&gt; org.junit.runner.RunWith;&lt;br /&gt;&lt;br /&gt;@RunWith(ConcordionRunner&lt;b&gt;.class&lt;/b&gt;)&lt;br /&gt;&lt;b&gt;public class&lt;/b&gt; DemoTest {&lt;br /&gt;&lt;br /&gt;    &lt;b&gt;public&lt;/b&gt; String greetingFor(String firstName) {&lt;br /&gt;        &lt;b&gt;return&lt;/b&gt; String.format("Hello %s!", firstName);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And will result in the following output:&lt;br /&gt;&lt;img border="0" style="border: 0px" alt="Concordion 1.1.0" src="http://www.davidpeterson.co.uk/image/misc/concordion-1.1.0-results.png" /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-3335321671784184515?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/0qwB1r19LUc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/3335321671784184515/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=3335321671784184515" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/3335321671784184515?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/3335321671784184515?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/0qwB1r19LUc/concordion-110.html" title="Concordion 1.1.0" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2008/01/concordion-110.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkYHQn48fSp7ImA9WxZSE0s.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-2582849245307693753</id><published>2008-01-26T16:08:00.000Z</published><updated>2008-01-26T16:15:33.075Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-26T16:15:33.075Z</app:edited><title>There's only one thing for it...</title><content type="html">&lt;img border="0" style="border: 0px" alt="There's only one thing for it... We'll have to stop measuring." src="http://www.davidpeterson.co.uk/image/cartoon/stop-measuring.png"/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-2582849245307693753?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/PpNWJzYrLYU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/2582849245307693753/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=2582849245307693753" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/2582849245307693753?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/2582849245307693753?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/PpNWJzYrLYU/theres-only-one-thing-for-it-well-have.html" title="There's only one thing for it..." /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2008/01/theres-only-one-thing-for-it-well-have.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUEERHo-eyp7ImA9WB9bGEU.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-6160459714774348261</id><published>2007-12-05T20:56:00.000Z</published><updated>2007-12-28T23:13:25.453Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2007-12-28T23:13:25.453Z</app:edited><title>Groupthink</title><content type="html">Being an agile developer in a largely waterfall organisation has reminded me of a game we played in one of my MBA lectures where we were put into groups and asked to solve a problem.&lt;br /&gt;&lt;br /&gt;One guy in one of the groups (unbeknownst to us) had been primed by the lecturer to disagree with every idea that his group came up with. At the end of the session, that group had markedly better results than all the other groups. The constant voice of dissent had helped them to uncover their assumptions. &lt;br /&gt;&lt;br /&gt;Of course, before revealing the results, the lecturer asked each group to vote a person out and the disagreement-guy was unanimously given the boot.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-6160459714774348261?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/4GT76RwTFao" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/6160459714774348261/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=6160459714774348261" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/6160459714774348261?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/6160459714774348261?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/4GT76RwTFao/avoiding-groupthink.html" title="Groupthink" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2007/12/avoiding-groupthink.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0MMQ3g_eCp7ImA9WB9VEU0.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-5349962042977667583</id><published>2007-11-26T20:15:00.000Z</published><updated>2007-11-26T20:38:02.640Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2007-11-26T20:38:02.640Z</app:edited><title>Knowing When to Refactor</title><content type="html">You're in the middle of implementing some new functionality when you stumble across some poorly written code. Fixing it properly will take some time and, for the stuff you're working on, you can get away with making a couple of tweaks. What should you do? Refactor it now? Or leave it for another time? If so, when?&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.citconf.com/wiki/index.php?title=Douglas_Squirrel"&gt;Douglas Squirrel&lt;/a&gt; told me about a neat solution that I hadn't heard of before. The idea is that the first time you come across some smelly code you write a comment that says &lt;code&gt;FIXME&lt;/code&gt;. Each time you encounter the code again, or someone else does, an extra &lt;code&gt;X&lt;/code&gt; is added so it becomes &lt;code&gt;FIXXME&lt;/code&gt;, &lt;code&gt;FIXXXME&lt;/code&gt; etc. By the time the comment screams &lt;code&gt;FIXXXXXXXME&lt;/code&gt;, you know what you have to do!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-5349962042977667583?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/-1bxEvVUEng" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/5349962042977667583/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=5349962042977667583" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/5349962042977667583?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/5349962042977667583?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/-1bxEvVUEng/knowing-when-to-refactor.html" title="Knowing When to Refactor" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2007/11/knowing-when-to-refactor.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkMDRnw-cSp7ImA9WB9aE0s.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-2347718461367084784</id><published>2007-11-17T19:07:00.000Z</published><updated>2008-01-03T12:47:57.259Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-03T12:47:57.259Z</app:edited><title>UNICOM Agile Conference - February 2008</title><content type="html">I will be speaking about &lt;span style="font-style: italic;"&gt;Acceptance Test-Driven Development&lt;/span&gt; at the &lt;a href="http://www.unicom.co.uk/product_detail.asp?prdid=1547"&gt;UNICOM agile conference&lt;/a&gt; in London, 12/13 February 2008. The conference has a good line-up of speakers including &lt;a href="http://duncanpierce.org/"&gt;Duncan Pierce&lt;/a&gt;, &lt;a href="http://peripateticaxiom.blogspot.com/"&gt;Keith Braithwaite&lt;/a&gt;, and &lt;a href="http://twelve71.typepad.com/rachel/"&gt;Rachel Davies&lt;/a&gt;. Early-bird fees are on offer till 18th January.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update&lt;/b&gt;: If you're interested in attending, &lt;a href="mailto:david@crowdsoft.com"&gt;contact me&lt;/a&gt;. I can probably get you a discount.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-2347718461367084784?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/CGSN-jgYLkU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/2347718461367084784/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=2347718461367084784" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/2347718461367084784?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/2347718461367084784?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/CGSN-jgYLkU/unicom-agile-conference-february-2008.html" title="UNICOM Agile Conference - February 2008" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2007/11/unicom-agile-conference-february-2008.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkYEQn84fCp7ImA9WB9XGEw.&quot;"><id>tag:blogger.com,1999:blog-3933266989882835478.post-3695805646107272681</id><published>2007-06-28T18:57:00.000+01:00</published><updated>2007-11-11T19:41:43.134Z</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2007-11-11T19:41:43.134Z</app:edited><title>Tapestry 5 (Alpha)</title><content type="html">&lt;p&gt; I've been working with &lt;a href="http://tapestry.apache.org/tapestry5/"&gt;Tapestry 5&lt;/a&gt;, for a few weeks now, and I'm loving it. It's a big step up from Tapestry 4 and I think it could become a realistic competitor to Ruby on Rails. It has similar characteristics: convention over configuration and an almost religious devotion by its creator, &lt;a href="http://tapestryjava.blogspot.com/"&gt;Howard Lewis-Ship&lt;/a&gt;, to driving out duplication. &lt;/p&gt;  &lt;p&gt; Sometimes he possibly goes too far. For example, if your page class is &lt;code&gt;/article/ArticleEdit&lt;/code&gt; the duplication of the word "article" is removed from the URL, so the URL becomes &lt;code&gt;/article/edit&lt;/code&gt;. It seems like an improvement, but actually introduces ambiguity and has caused confusion, judging by queries on the mailing list. &lt;/p&gt;    &lt;p&gt;At the moment, you'd be taking a risk using Tapestry 5 in a production application. It's still "alpha" and unstable both in terms of the API, which hasn't fully settled down, and in terms of defects: There are lots of minor, and a handful of more serious, defects reported in JIRA. But if you're developing a non-mission-critical web application you might want to give it some serious consideration. It's already very good and only going to get better. &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3933266989882835478-3695805646107272681?l=blog.davidpeterson.co.uk' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/david_peterson/~4/s-1-FHBhTE8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.davidpeterson.co.uk/feeds/3695805646107272681/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=3933266989882835478&amp;postID=3695805646107272681" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/3695805646107272681?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3933266989882835478/posts/default/3695805646107272681?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/david_peterson/~3/s-1-FHBhTE8/ive-been-working-with-tapestry-5-for.html" title="Tapestry 5 (Alpha)" /><author><name>David Peterson</name><uri>http://www.blogger.com/profile/03475136061061572446</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="01215506581573414240" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.davidpeterson.co.uk/2007/11/ive-been-working-with-tapestry-5-for.html</feedburner:origLink></entry></feed>
