Posted by Ken Brooks
Mon, 04 Feb 2008 21:45:00 GMT
One of the challenges of maintaining and enhancing an existing system is that the documentation goes out of date probably before you even finish it the first time. Whenever someone has a question for me about how does something work I usually refer to the location of truth. That would be the code itself, which is always the most up to date source of documentation. So the closer to the source you get the better chance your documentation has of standing the test of time.
Unit Tests helps in this area in that they can outline the assertions we are placing on our code. That too must stay up to date because it is also close to the code. Unfortunately unit tests are often more complex than the code they are testing and ultimately end up not being very good sources of documentation either.
There has to be a way to build human (possibly even business person) consumable documentation that stays in sync with the truth.
Enter EasyB.
EasyB is a BDD testing framework written in groovy and java. It provides what we like to call executable documentation. This is achieved by taking TDD, all sopping wet with techy mess, and throwing it in the evolutionary dryer with a nice fabric softener. Out from the other end pops a nice fluffy testing framework that can not only provide up-to date documentation about the application every time it is built, but can even bridge the gap between business requirements and developer interpretation.
It has two supported ways to write specifications. Behaviors and Stories. Its just a matter of personal taste on which you use for each component in your project. Behaviors often are shorter and good for utilities. Stories are more verbose and usually map to a series of interactions.
Behaviors look like this:
it "should return null for null input to trim", {
ensure(StringUtilPartial.trim(null)) {
isNull
}
}
it "should return the string without whitespace at either the end", {
StringUtilPartial.trim(" somestring ").shouldNotBe null
StringUtilPartial.trim(" somestring ").shouldBe "somestring"
}
Stories look like this:
scenario "appending string to empty string buffer", {
given "an empty string buffer", {
stringBuffer = new StringBuffer()
}
when "a string is appended", {
stringBuffer.append("somestring")
}
then "the buffer should contain the string appended", {
stringBuffer.toString().shouldBe "somestring"
}
}
scenario "appending string to existing string buffer with existing data", {
given "a string buffer with an initial value", {
stringBuffer = new StringBuffer("abcd")
originalStringBufferValue = stringBuffer.toString()
}
when "a string is appended", {
stringBuffer.append("somestring")
}
then "the buffer should contain the original value plus the appended value", {
stringBuffer.toString().shouldBe(originalStringBufferValue + "somestring")
}
}
What you have just witnessed is that we have tested our code and written documentation at the same time. That documentation will evolve right along with the code its testing and nobody has to be the low man on the totem pole and write documentation.
All well and good, but what we wrote above still wouldn’t be what a business person would consider documentation. Too much techy goobledeegook in there. Easyb has another trick up its sleeve especially when dealing with the story based specifications. When I ran the story above a report is generated and here is what the contents look like:
Story: string buffer
scenario appending string to empty string buffer
given an empty string buffer
when a string is appended
then the buffer should contain the string appended
scenario appending string to existing string buffer with existing data
given a string buffer with an initial value
when a string is appended
then the buffer should contain the original value plus the appended value
What?! Where did that english documentation come from? Shhh.. don’t tell your developers that they wrote it.
Check out easyb at easyb.org and join the group to keep up on the latest developments.
The current release of easyb is 0.6 and some of the syntax presented above (shouldBe for example) is in the trunk and not a release yet.
Posted in easyb | Tags bdd, easyb, groovy | no comments | 1 trackback
Posted by Ken Brooks
Sat, 26 Jan 2008 22:53:00 GMT
Maintaining consistency within a shop is one of the easiest things to do and one of the best ways to save time and reduce errors.
Consistency comes in many flavors.
- Languages used
- Tools used (build systems, scm)
- Code standards
- Processes (code reviews)
- many more.
One thing that wastes a tremendous amount of our time is context switching. Context switching happens when ‘being in the zone’ is interrupted, forcing you to drop what you were working on and get up to speed on a totally different topic. By having consistent set of items from any or all of the above categories the context switching is reduced.
Keeping your list of languges and tools to a minimum goes against what we as creative people (known as developers) thrive on. We love to learn new languages and we love to use the newest and shiniest toys available. Unfortunately, introducing new languages and tools most often serves to complicate our environment and eat up more of our precious time. Unless a plan is put into place to consolidate multiple tools or replace existing tools then what you end up with is having to support each. That costs usually outweighs the benefit.
I’m all for “using the right tool for the job”. However, it seems hard enough to find good developers for one language, much less those that know, or are willing to learn, ‘the right tool’.
Of course, all of this is different for each shop. Many of the problems I mention may not affect small startups. They might only have one application and thus only one language, one build system and one SCM. If you come from the enterprise world or a company that has been around for a long time then you probably can relate.
Take a look around next time you are at work and take stock. Do you already have too many tools? Do you notice a trend of new tools being consistently added? Do you notice your productivity waning?
Tags productivity | no comments
Posted by Ken Brooks
Fri, 25 Jan 2008 00:20:00 GMT
Any intelligent fool can make things bigger,
more complex, and more violent. It takes a
touch of genius—and a lot of courage—to
move in the opposite direction.
—Albert Einstein
This speaks volumes. The mission statement for the group I’m part of at work is ‘Simplicity, Consistency’.
Simplicity is difficult. It is probably the hardest thing to achieve in development.
A developer usually goes thru a maturity process in stages. See if any of this sounds familiar.
- Developer produces very little code. They struggle to get anything done
- Developer produces alot of code as they become familiar with the language of choice
- Developer produces alot of code but now does it fast!
- Developer produces the right solution in very little code.
Q: Why would I want to spend time simplifying if I can rip thru the code at lightspeed?
A: The code you are writing is probably going to be around alot longer than you think. That means that many people will probably have to open it up and understand it. The less lines of code you write, the less they’ll have to digest and the meaning of the code is usually not obfuscated. (Now, to be fair, I’m not saying that making code terse for the sake of being clever is a good thing. That is probably my biggest gripe with perlisms. They are terse at the expense of clear meaning.) The easier it is to understand, the faster you can change it and the likely hood that your changes will be successful will go up.
In my years of development one thing remains quite constant. It usually never fails that when a challenge is presented, the best answer invariably turns out to be the simplest. However, its usually the hardest to arrive at.
So next time someone tries to use lines of code written or some other ‘objective’ way of measuring your developer and codebase maturity perhaps you should rebut that you value those that spend more time on less lines of code than less time on more lines of code. It will save you time and money in the long run.
Tags development, good | no comments
Posted by Ken Brooks
Wed, 26 Dec 2007 14:16:00 GMT
I feel crippled when I work on windows. The reason is that I rely on QuickSilver on my mac so much. The closest thing I’ve been using (and I think I’ve tried them all) is Launchy. While not living up to the lofty bar set by quicksilver, launchy has worked relatively well for me in my windows (read: work) life.
Today I saw a notification that Launchy 2.0 was out and decided to upgrade. Shame on me because I didn’t do any reading about it first. Normally I wouldn’t figure this to be an issue. Just download it, install it and move on. However that little miss cost me an hour or so of time.
Launchy was rewritten and as a casualty of that all existing configurations (directories to be scanned, custom commands, etc..) paid the ultimate price. Gone. So just be warned that if you plan on upgrading to 2.0 you should probably jot down your configurations.
Another change, which I don’t want to waste the brain cycles on figuring out why, is that I used to be able to start a .bat file that we use to set environment variables and so on. That would open up and stay in the command prompt. Since the upgrade it opens the batch file, runs it and then exits the shell. Hrm… not exactly what I hoping for.
I created a workaround that actually provides a little more functionality than before. There is a plugin called runny or runner bundled with launchy and we can leverage that.
- Open up launchy
- Right click and choose options
- Select the 4th tab ‘Plugins’
- Click on ‘Runner’ in the left pane
- Click the + to add another command to be run
- give it a name (the shortcut you will refer to it as when invoking launchy)
- put C:\WINNT\System32\cmd.exe or C:\Windows\System32\cmd.exe in the program column
- put /K pathtobat\batfile.bat $$ in the arguments column
- click ok
Now when you invoke launchy you can give it the short name and hit enter, or give it the short name, hit tab and then pass additional arguments to your batch file..
Tags launchy | no comments
Posted by Ken Brooks
Sun, 23 Dec 2007 20:36:00 GMT
if you aren’t familiar with easyb (http://easyb.org), then do yourself a favor and pay that site a visit to learn the basics of this great bdd framework for java.
one of the strengths of easyb is its domain specific language that allows us to write behaviors and stories that act as executable documentation. we also have a way to allow you as a user to extend that by writing plugins.
here are the steps to do so:
- implement Plugable on our plugin classes (optional)
- create the following file META-INF/services/org.disco.easyb.core.delegates.Plugable
- package it all up into a jar and make it available on the classpath
lets start with our class, write any java/groovy class that exposes a public method and (for readability) implement the org.disco.easyb.core.delegates.Plugable interface.
now, create a file named org.disco.easyb.core.delegates.Plugable and when you package everything into the plugin jar put that file into the META-INF/services directory. the contents of the file need only be a list of classes (fully qualified) that optionally implement the Plugable interface and expose public functionality you wish to mixin to easyb.
package your classes and the property file into a jar file (outside the scope of this article) and make sure that jar file is available on the classpath when easyb stories are run
three steps, thats it? seem too simple! thats what easby is all about.
So whats happening under the covers? when easyb encounters a method call in the story that it can’t resolve it then goes thru the list of classes found in the META-INF/services/org.disco.easyb.core.delegates.Plugable file. Each class is then inspected to see if it contains a method that can handle our call. If it finds one then everything is golden and the call is made, if not then a MethodMissing exception is thrown.
If you wish to see an example of this completely implemented then checkout the easyb source and look in the plug-ins dir for easyb-dbunit. This mixes in dbunit functionality into easyb.
Tags easyb | no comments
Posted by Ken Brooks
Sat, 30 Jun 2007 20:11:00 GMT
… or is it?
Always seems like our industry goes in dry spells of NBT (next big thing) or something with just a general wow factor about it.
I haven’t really seen anything recently that tickled my fancy. That all changed this morning and my wife and little girl were packing up and about to head to Ocean City, MD. I figured I would hit maps.google.com because we were going there but was also going to take a detour to see some relatives. Normally that means I have to map two routes. One from my house to theirs, and then one from theirs to the beach. Not anymore. I mapped the route to the beach and then something possessed me to hover over the route as if I was going to drag it around to create a detour. It did!
Now that is something completely wow to me, seeing that recalculate like it did on the fly and then update the directions in the left nav.
Oh, and the beach is great, since I knew you all were really wondering. Blogging this from the balcony of the Lankford hotel right on the boardwalk.
no comments
Posted by Ken Brooks
Thu, 21 Jun 2007 20:07:00 GMT
I just got an old Mac G4 at work. Its old and slow but I’m so stinking happy I have a Mac at work.
The challenge: Make it productive for development.
My dirty little secret is that ever since installing my first linux distro years ago I have always hated compiling from source and installing it and not really knowing where all the artifacts end up. MacPorts to the rescue. Well… that is until you try to use it from behind a gauntlet of corporate proxies and firewalls.
Installed XCode. Check.
Installed MacPorts. Check
Update MacPorts definitions. (Insert screeching tire sound here).
The synchronizing is done via rsync. I believe that goes out over TCP 873. Yeah, like that is going to work.
The solution to this was to ssh into my webhost and checkout the info via subversion from MacPorts Subversion I then tar’d up that whole directory and threw it into the public area of my site. Over on the work mac create rsync.rsync.darwinports.orgdpupdatedports in /opt/local/var/db/dports/sources. I was able to download the tar, and untar it into the /opt/local/var/db/dports/sources/rsync.rsync.darwinports.orgdpupdatedports directory. That allowed macports to have a list of available ports and their dependencies. What I can’t do or haven’t bothered to figure out is how to update the macports version itself.
One last little thing before we can actually use it. We need to set an environment variable to allow macports to download the sources for our ports via our proxy. Edit your .profile or .bash_profile and add something like this:
http_proxy=http://myproxy.foo.bar:8080
Ok. So lets take inventory again.
Installed XCode. Check.
Installed MacPorts. Check
Update MacPorts definitions. Check.
Now feel free to install as normal. Ex: sudo port install ruby
Score one for the good guys.
Tags macports | no comments
Posted by Ken Brooks
Mon, 04 Jun 2007 21:18:00 GMT
Seems like a flurry of just bugfixes in edge rails lately. Mixed in today was a few additions.
The ActiveSupport::CoreExtensions::Date:Calculations had since, ago, beginning_of_day, end_of_day added.
Here are the tests so you can see some of the basic usage.
def test_since
assert_equal Time.local(2005,2,21,0,0,45), Date.new(2005,2,21).since(45)
end
def test_ago
assert_equal Time.local(2005,2,20,23,59,15), Date.new(2005,2,21).ago(45)
end
def test_beginning_of_day
assert_equal Time.local(2005,2,21,0,0,0), Date.new(2005,2,21).beginning_of_day
end
def test_end_of_day
assert_equal Time.local(2005,2,21,23,59,59), Date.new(2005,2,21).end_of_day
end
beginningof_day and endof_day make sense, they give you the beginning of the date you are operating on.
not so sure since and ago read correctly.
no comments
Posted by Ken Brooks
Thu, 24 May 2007 22:49:00 GMT
Tonight we’ll be starting down the road of those refactorings I promised and along the way we’ll introduce before and after.
First lets change that snowball itself to be a little more rubyish. In this case that means we will use symbols for the color.
class Snowball
def initialize
@color = :WHITE
end
def color?(color)
@color == color
end
end
Now we can modify the spec to do the same, passing in symbols for the color param.
require 'snowball'
describe Snowball do
before(:each) do
@snowball = Snowball.new
end
it 'Should NOT be yellow' do
@snowball.should_not be_color(:YELLOW)
end
it 'Should be white' do
@snowball.should be_color(:WHITE)
end
after(:each) do
@snowball = nil
end
end
You may have noticed that I did a little more than change the params to use symbols. There are now calls to before and after. If you are from an xUnit background you can think of these as setup and teardown. The before method runs prior to each example. before(:each) is the same as before because :each is the default param. The after method runs post each example. The same default of :each applies to after so after(:each) is identical.
Updated 2007-05-25
Aslak (Core developer of RSpec) was kind enough to point out something that I should have clarified:
Each example (instance) runs in a new instance anyway, and the instance variables are garbage collected.
The after(:each) here has no effect, and since it has no effect, doing it is confusing.
So in my example above the after method is not doing anything of value. That @snowball will be garbage collected without having to set it to nil. I just needed something to plop in there to show that I could be doing something.
End Update
There are also before(:all) and after(:all) that can be used. These are mostly discouraged as it may cause side-effects by having dependencies between examples. I came across a scenario at work (in Java-land) where this actually applies. I was using JUnit to write a func test that can be used as a test client of a deployed J2EE service. Before any calls can be made to the service we must initialize (authenticate). Once that initialization is done then we can call our service as many times from anywhere in our code using a static service wrapper. So in my test since there is only one setup method I was forced to write code similar to this:
private static boolean initialized;
public void setUp() {
if(!initialized) {
SubSystem.init();
initialized = true;
}
doSomeOtherSetupTask();
}
That saves the overhead of authenticating before each test. Unfortunately its ugly to me.
In rspec (altho I’m not sure I would use it to drive a client test like I did with JUnit) I suppose that code would look like this:
before(:all) do
sub_system.init
end
before(:each) do
do_some_other_setup_task
end
Now that is tasty goodness.
Tags bdd, rspec | 4 comments
Posted by Ken Brooks
Mon, 21 May 2007 00:30:00 GMT
RSpec has reached 1.0.0 (congrats to that team).
What is RSpec you ask? Straight from their site:
RSpec provides a Domain Specific Language with which you can express executable examples of the expected behaviour of a system.
A little better (and foreshadowing) explanation from their site:
…use RSpec to #describe Behaviour of a system using Examples of how #it should work.
Over the next few posts I’ll be going thru some of the features with small tutorials.
First up, the basics of installing RSpec and writing your first working spec. We’ll be going thru the specs of a snowball, now that summer is here and I spent the day sweating my snowballs off.
Get rspec
Create your first spec by creating snowball_spec.rb and adding the following to it.
That is telling us that we are going to describe the behavior and requirements for a successful implementation of a snowball.
Now is as good a time as any to fire up rspec and see what the magic is all about.
That just produced a ./snowball_spec.rb:1: uninitialized constant Snowball (NameError) message. Not exactly shaping up to be the greatest tool in the world is it? Take a closer look. Its actually telling you where to go next. Yep, create a Snowball class.
Don’t bother running spec again as you’ll be presented with the same error again. You need to tell the spec we depend on that snowball class. Modify your snowball_spec.rb to require the snowball.
require 'snowball'
describe Snowball do
end
Now if you run spec again you should see a little more positive results.
$ spec snowball_spec.rb
Finished in 7.0e-06 seconds
0 examples, 0 failures
That basically tells us that we are ready to really start describing the behavior of our snowball.
Lets add an example to our spec. We are going to make the statement that snowballs should not be yellow (unless you really don’t like the kid down the street).
require 'snowball'
describe Snowball do
it 'Should NOT be yellow' do
snowball = Snowball.new
snowball.should_not be_color("yellow")
end
end
That is pretty easy to read, and if you now run spec with the --format specdoc it finally hits you why RSpec is different and has its niche as compared to xUnit tests. We really are documenting the behaviour of a system and writing code to meet that required behavior. Now you can have your Keanu Reeves (Neo) ‘Whoah’ moment.
Ok, enough of that moment, lets finish up the snowball code to make this spec run again. And I’ll get back to creating that snowball instance in the middle of the test. If I don’t then please ping me and remind me.
class Snowball
def color?(color)
color == "white"
end
end
$ spec snowball_spec.rb --format specdoc
Snowball
- Should NOT be yellow
Finished in 0.006263 seconds
1 example, 0 failures
Without changing the Snowball class, we should now try and see if the positive test works also.
require 'snowball'
describe Snowball do
it 'Should NOT be yellow' do
snowball = Snowball.new
snowball.should_not be_color("yellow")
end
it 'Should be white' do
snowball = Snowball.new
snowball.should be_color("white")
end
end
Obviously (for the more experienced users) there are a few things that will need to be refactored out but in the interest of simplicity I left them that way to start.
That covers the basics of using RSpec.
We described the behavior of an object and then created some examples of its behavior.
Next section we’ll dig a little deeper and start doing those refactorings.
Tags bdd, rspec, ruby | no comments