Sunday, June 19, 2005

Agile Web Development with Rails

The Pragmatic Programmers are releasing a book soon: Agile Web Development with Rails

"Rails is a full-stack, open-source web framework that enables you to create full-featured, sophisticated web-based applications, but with a twist... A full Rails application probably has less total code than the XML you'd need to configure the same application in other frameworks."

The book is currently available as a Beta Book. After reading only 20% of the book I feel can recommend it. So far it's an easy read and Rails is easy to work with. That means that you can read the book and speak intellegently about Rails in no time, and the more languages you know the better you become at all of them.

Saturday, June 11, 2005

Controlling Subversion with Ruby and the command line

I love the syntax and ease of developing in Ruby. Unfortunately, there isn't much Ruby work. Therefore, I use Ruby as often as I can for simple tasks I'd like to automate.

Subversion's command line client does everything you need, but not always as easily as possible. For example, I'd like a command that adds all the unknown files returned from a `svn status`. Yes, you can `svn add .`, but that seems to try to add everything and produces warnings.

I think you see where I'm going with this.

Where should we start? With tests obviously.

require 'svndir'
require 'test/unit'

class SvnTestCase < Test::Unit::TestCase
def testMassAdd()
tempFile = createTempFile()
assert_equal("?",tempFileStatus(tempFile))

svnDir = SvnDir.new()
svnDir.add();

assert_equal("A",tempFileStatus(tempFile))
removeTempFile(tempFile)
end

def createTempFile(fileName = "tempFile.temp.temp")
flunk("#{fileName} file already exists") if File.exist?(fileName)
`echo fileName > #{fileName}`
assert(File.exist?(fileName))
fileName
end

def removeTempFile(fileName)
`svn revert #{fileName}` if tempFileStatus(fileName) == "A"
File.delete(fileName) if File.exist?(fileName)
assert(!File.exist?(fileName))
end

def tempFileStatus(tempFile)
`svn status`.to_s =~ /(\?|A)\s+#{tempFile}/
$1
end
end

Now that I know what I'm looking for from a SvnDir class I can implement it:

class SvnDir
def add()
svnStatus().each { |line| addFile(line) if unknown?(line) }
end

def svnStatus()
`svn status`.to_s.split(/\n/)
end

def unknown?(statusLine)
statusLine =~ /^\?/
end

def addFile(statusLine)
statusLine =~ /^\?\s+(.*)$/
`svn add #{$1}`
end
end

I'm still learning Ruby, so please comment with better ways to do this.

I asked Subversion guru Mike Mason if there was an easier way, and he suggested TortoiseSVN. It's true, adding is easier in Tortoise, but I'm just too much of a control freak to give up my command line.

Saturday, June 04, 2005

What are you testing?

When I first started testing I thought code coverage was the most important statistic. In the past I had written zero tests for my code. Once I learned there was a better way, I went to the opposite extreme. Because of this focus, I attempted to test every line of code.

Imagine a DTO:

public class Foo
{
public int Bar;
}

Would have a test:

Foo f = new Foo();
f.Bar = 1;
Assert.AreEqual(1,f.Bar);

The problem with this type of testing is that it attempts to test the features of the language. Not only is this unnecessary, but it adds code to the code base, thus increasing the complexity of the project.

Along the same lines:

public class Foo
{
public Foo()
{
Bar = 1;
}
public int Bar;
}

Can have the associated test:

Foo f = new Foo();
Assert.AreEqual(1,f.Bar)

Once again, there's no interaction and the test simply tests the features of the language. I picked this example because I saw something similar in a production system. On a side note, I believe the assignment should be done on the declaration line and the constructor should be removed. Then, it is more obvious how unnecessary the test becomes.

Lastly, I have another example from a production system. Class Foo needed to subscribe to an event on Class Bar. If Foo did not subscribe an error would not occur, but invalid results would. Thus, a colleague of mine asked how to test this situation. It turns out that you can mock the add_* method, and therefore test that the subscription occurs. However, I believe this is simply testing that a subscription occurs, not that when the event is raised Foo will handle it correctly. Therefore, a functional test was needed for the business requirement and testing the subscription was unnecessary.

These days, I find myself constantly asking the question "What are you testing". The value in testing is often at interaction points. State based testing adds value, but is usually only necessary after an interaction occurs. As a result much of my tests include mocks.

Code coverage is important and tests can ease maintenece issues; however, having unnecessary tests decreases readibility. Therefore, always know what you are testing.