The point is missed

Museum of public mistakes and unfinished projects

Phasing over from java to scala pt. 4 – Writing specs with specs

with 7 comments

In the last part I figured out how to make Buildr run specs that are also runnable from Eclipse via JUnit. I started writing my first spec and we are now in the position to make it fail. What we are trying to acomplish is to make directories behave as nodes in a graph, at the moment, only the files show up as nodes.

The spec I wrote currently looks like this:

class PublishSiteGraphSpecTest extends JUnit4(PublishSiteGraphSpec)
object PublishSiteGraphSpec extends Specification {
  "directories are nodes" in {
  }
}

Let us fill in the details about what we actually mean when we say that directories should be nodes:

class PublishSiteGraphSpecTest extends JUnit4(PublishSiteGraphSpec)
object PublishSiteGraphSpec extends Specification {
  "directories are nodes" in {
    val source = new MemoryResourceRepository()
    val target = new MemoryResourceRepository()
    val problems = new ProblemsStub()
    val graph = new PublishSiteGraph(source, new SiteJobQueue(),
                                     new ContentNodeFactory(source, target, problems))
    source.importResource("directory/resource.ext",
                          new StringResource("directory/resource.ext", "the contents"))
    graph.findNode("directory") must haveClass(classOf[DirectoryNode])
  }
}

And it fails with an exception saying:

The file "directory" does not exist

The problem is that MemoryResourceRepository does not add resources for the directories on the path. So we ignore this test for now and move our focus to the MemoryResourceRepository:

class PublishSiteGraphSpecTest extends JUnit4(PublishSiteGraphSpec)
object PublishSiteGraphSpec extends Specification {
  "directories are nodes" in {
    skip("Won't work until MemoryResourceRepository adds resources for directories.")
    val source = new MemoryResourceRepository()
    ...
  }
}

And now JUnit ignores the test and builder outputs this:

Running tests in publisher:scala-core
Running java com.agical.publisher.content.site.PublishSiteGraphSpec
Specification "PublishSiteGraphSpec"
  specifies
  o directories are nodes
    Won't work until MemoryResourceRepository adds resources for directories.
    (PublishSiteGraphSpec.scala:19)

Total for specification "PublishSiteGraphSpec":
Finished in 0 second, 54 ms
1 example (1 skipped), 0 assertion, 0 failure, 0 error

Ok, let’s move on with the next spec:

class MemoryRepositorySpecTest extends JUnit4(MemoryRepositorySpec)
object MemoryRepositorySpec extends Specification {
  "directories are resources" in {
    val repository = new MemoryResourceRepository()
    repository.importResource("directory/name.ext",
                              new StringResource("directory/name.ext", "the content"))
    repository.getResource("directory") must haveClass(classOf[DirectoryResource])
  }
}

And a familiar failure is seen:

The file "directory" does not exist

Time to implement

Firstly break break the path into path-elements

override def defineResource(name:String):OutputStream = {
  val path = name.split("[\\/]")
  createDirectoryRecources(path.toList)
  ...
}

Secondly, define the method that creates the directories. All but the last elements are assumed to be directories. Therefore we use the dropRight(n:Integer) function before we iterate the path-elements. Note how we can pass the function createDirectoryResource(name:String) as an argument to foreach.

private def createDirectoryRecources(elements:List[String]) =  {
  elements.dropRight(1).foreach(createDirectoryNode)
}

Thirdly we implement the createDirectoryResource function. Here we use the getOrElseUpdate function on the mutable Map. The getOrElseUpdateMethod simply looks for an element in the map, if the element exists it is returned, if the element does not exist it is created by the function passed in the second argument and returned.

We don’t really care about the value here, we just use the second argument as an event to create directories that do not exist.

private def createDirectoryRecource(name:String):Resource =  {
  resources.getOrElseUpdate(name, {new DirectoryResource(name)})
}

We run the specs again:

Specification "PublishSiteGraphSpec"
  specifies
  o directories are nodes
    Won't work until MemoryResourceRepository adds resources for directories. (PublishSiteGraphSpec.scala:19)

Total for specification "PublishSiteGraphSpec":
Finished in 0 second, 87 ms
1 example (1 skipped), 0 assertion, 0 failure, 0 error

Running java com.agical.publisher.resource.memory.MemoryRepositorySpec
Specification "MemoryRepositorySpec"
  specifies
  + directories are resources

Total for specification "MemoryRepositorySpec":
Finished in 0 second, 149 ms
1 example, 1 assertion, 0 failure, 0 error

While MemoryResourceRepository does not work as we want it to work yet we can at least make the PublishSiteGraphSpec treat directories as nodes. So we remove the skip-statement and get the following error

Specification "PublishSiteGraphSpec"
  specifies
  x directories are nodes
    'com.agical.publisher.content.core.CopyNode@1f934ad'
    doesn't have class 'com.agical.publisher.content.core.DirectoryNode'
    but 'com.agical.publisher.content.core.CopyNode' (PublishSiteGraphSpec.scala:24)

Now the graph finds the directory resource but does not create a DirectoryNode but a CopyNode.

We jo-jo up and down in the code skipping as necesary until the SiteGraph-test passes and, implicitly, no skips remain.

We never specify something that is not driven by a need that can be traced back to our outer spec. Therefore when the outer spec works, by definition, no more skips remain. This is called outside in and is one of signifying aspects of BDD.

Making the spec into a contract

Now I find myself in the position where my specs pass but my userguide does not. I have made all the changes for MemoryResourceRepository and would like to make the same changes work for the FileSystemRepository. I need some way to test the same contract on both implementations.

Luckily specs provides a way:

class ResourceRepositoryTest extends JUnit4(ResourceRepositorySpec)
object ResourceRepositorySpec extends Specification {
  implicit def javaIterableToScalaIterable[T](iterable:java.lang.Iterable[T]):Iterable[T] = {
    new Iterable[T] {
      override def elements = {
        val iterator = iterable.iterator();
        new Iterator[T] {
          override def next = iterator.next()
          override def hasNext = iterator.hasNext()
        }
      }
    }
  }

  val root = getClass().getResource("/");
  commonSpecs(new MemoryResourceRepository())
  commonSpecs(new FileResourceRepository(new File(new File(root.toURI), "test")))

  def commonSpecs(repository:ResourceRepository) = {
    val oneResourceInOneDirectory = beforeContext {
      repository.importResource("directory/name.ext", new StringResource("directory/name.ext", "the content"))
    }
	"A "+ repository.getClass().getSimpleName +" with one resource in one directory" ->-(oneResourceInOneDirectory) should {
      "create directory resources for directories" >> {
        repository.getResource("directory") must haveClass(classOf[DirectoryResource])
      }
      "create regular resources for resources" >> {
        val resource = repository.getResource("directory/name.ext")
        resource.getName mustNot haveClass(classOf[DirectoryResource])
      }
      "the directory resource must " >> {
        val directory = repository.getResource("directory") match { case directory:DirectoryResource => directory}
        "be named after the directory" >> {
          directory.getName must beEqual("directory")
        }
      }
    }
  }
}

I simply wrap my specs in a method-definition and I pass the repository of choice as a parameter to my definition. Not too bad. The downside is that I now have a dependency to two different implementations from my contract-spec. The logical dependency direction would be the opposite in my opinion.

I’m on my way to be productive with specs. I’m still struggling a bit with how I should structure my specs but I think it is…

Time to wrap up with some final thoughts on specs

Specs does not impress me in the same way as rspec did. While most things seem to be possible it fails to guide me correctly. The documentation is pretty much features piled on top of features and it’s sometimes hard to figure out when I would use one feature over the other. Moreover, rspec just seems to do the most logical thing with a minimum of effort while specs allows me to do the most logical thing with some effort. Also specs naming seems a bit awkward

Compare rspec’s:

describe Account, " when first created" do
  it "should have a balance of $0"
end

that gives the output:

Account when first created
- should have a balance of $0 (PENDING: Not Yet Implemented)

Pending:
Account when first created should have a balance of $0 (Not Yet Implemented)

Finished in 0.007737 seconds
1 example, 0 failures, 1 pending

With specs:

package com.agical.publisher.resource
import org.specs._

object AccountSpec extends Specification {
  "Account when first created" should {
    "have a balance of $0" in {
      skip("PENDING: not yet implemented")
    }
  }
}

That outputs:

Specification "AccountSpec"
  Account when first created should
  o have a balance of $0
    PENDING: not yet implemented (AccountSpec.scala:8)

Total for specification "AccountSpec":
Finished in 0 second, 64 ms
1 example (1 skipped), 0 assertion, 0 failure, 0 error

The major thing to note is that rspecs that don’t have a body become pending by default. Specs that don’t have a body just pass and you can optionally add a skip statement with a description to achieve the same thing. I have this feeling in general that where rspec is very thought through specs seems to have more of a spray and pray attitude towards features. Ok, that’s a bit harsh, specs is not bad, it’s just not much more than ok in my book.

It’s quite possible that I missed some nuggets in specs due to my expectations formed by rspec and I will continue to use specs since I think it is good enough for my needs.

Pfewww, that turned out to be quite a beast of a post. I’ll try to keep them shorter in the future…

Time for a retrospect

About these ads

Written by johlrogge

September 14, 2008 at 10:05 pm

Posted in Uncategorized

Tagged with , , ,

7 Responses

Subscribe to comments with RSS.

  1. […] the next part we will make our specification […]

  2. Hi Joakim,

    Have you looked at the latest version of ScalaTest that has BDD support? It is a bit more like RSpec in that it uses “describe” and “it”, and its matcher DSL is more English-like. It doesn’t yet have a “pending” support, but will soon. In the meantime you could use “ignore” instead. But on that subject, Scala doesn’t let you overload this:

    it(“should do something”) {
    // actual test code
    }

    with this:

    it(“should do something else”)

    The latter one you’d like to just show up as pending. Well it unfortunately won’t compile. So my current plan is to support this syntax:

    pending(“should do something else”)

    I kind of like this anyway, because it is clearer in the code which examples (tests) are pending.

    http://www.artima.com/scalatest

    Bill

    Bill Venners

    March 11, 2009 at 7:35 pm

  3. Hi Bill,

    Yes, I have read a bit about it but haven’t gotten around to try it yet. I was about to include it some time ago but it had not yet made it to the scala-tools repository so thought I’d get around to it when it does.

    I agree it looks more like what I’m looking for and I will sure try it out when my time allows it.

    Thanks for your pointer!

    johlrogge

    March 14, 2009 at 1:49 am

  4. Hi Joakim,

    Re-reading your post, I actually realized that it was pretty straightforward to add the behavior you want by automatically throwing a SkippedException if the Example body is empty (more precisely if it has no expectations).

    You can try that with the latest specs-1.4.5-SNAPSHOT (available here: http://specs.googlecode.com/files/specs-1.4.5-SNAPSHOT.jar, but not yet in the scala-tools repository which is down for upload at the moment).

    Please tell me how you feel about it and don’t forget to check out on Bill’s ScalaTest DSL which has indeed a very nice syntax!

    Eric.

    Eric

    April 8, 2009 at 2:42 am

  5. Hi Eric,

    Yes I have played a bit with the ScalaTest DSL now and I like it. I’m still waiting for the pending feature though but the syntax is what I expect and ScalaTest gives me few surprises (expect that it breaks with Scala 2.8.x, whats up with that?).
    I have not tried the latest 1.4.5 spec but from your description I get a bit curious. Does it cover the interaction-based case when you only have expectations for interactions on mocks and no ” “should” expectations?
    I’ll see if I get around to play with it a bit to give you some feedback. Apart from my concern above it sounds good.

    Cheers!
    /J

    johlrogge

    April 8, 2009 at 7:55 am

  6. > Does it cover the interaction-based case when you only have expectations for interactions on mocks and no ” “should” expectations?

    In that case, I thought that the sensible thing was to declare the example body as implemented and to count the JMock “expect” block as containing at least one expectation.

    Is that what you’re expecting too?

    Eric

    April 8, 2009 at 3:11 pm

  7. That sounds like what I was after. It’s easy to forget the interaction-case and just consider any test without “asserts” as pending but that would be wrong. It seems to me that you have it covered and it sounds like a feature that brings some flow into spec-writing. I hope the community likes it!

    johlrogge

    April 10, 2009 at 12:16 pm


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: