CSS-Spriter is a project that Tyler Jennings and I developed to eliminate the tedium of CSS sprite creation. While developing it, we stumbled across a style that's a combination of outside-in and bottom-up testing. It's a style that's helped us with other projects because it attempts to answer what levels of testing are appropriate and what time to do it.
First, lets understand what sprites are and how CSS-Spriter can help. Web browsers only request page resources a couple elements at a time, so, one way to reduce page load times is to reduce the number of connections needed to download everything. Sprites do this by combining many or all of the image elements into one large image. Instead of 30 requests to different images, you now have one request that delivers all 30 images to you. You then have to create css rules for every image to only show a small part of the total image. If you create the css by hand, it can be a tedious and error prone process that's also a pain to update.
CSS-Spriter automates the sprite generation process. It looks at the directories where you store your PNG files, and then combines them into one larger image. It then does the math and calculates all the CSS image offsets for you. Since CSS-Spriter is written in pure Ruby, this means you can use it in your JRuby, MRI, or Rubinius projects. There are no native extensions to compile! Also, it uses the fast and efficient chunky_png library to read and write PNG files. It works as a stand-alone app or as a rails plugin.
Since there were no pure ruby PNG parsing libraries like chunky_png when we started this project, we had to make our own. Tyler came up with a spike over the weekend that loaded an image and saved it. It was from this spike that we slowly added features and evolved the design. When developing the spike, Tyler didn't worry about design or testing. He was just concerned with proving one simple thing - can we read the png file format? He constructed the spike so it was the bare minimum needed to represent the whole stack.
The next step was to wrap a couple high level integration tests around the spike. We would use these as a sanity check for each change we made to the system. We made these tests high level enough that we could radically change the implementation of the system and they would still pass if the files were created correctly. We could then refactor the system and add features to slowly evolve the project toward a better design.
Tests were then added only when a few conditions were met. We put tests around the methods that were the most complex in the system. These regression tests were useful to ensure that we didn't break anything when we refactored them to reduce the complexity. We also put tests around the parts that absolutely had to work because a spriting library wouldn't be very useful if you saved a corrupt file format. When we did find a bug, we created a failing test and knew we fixed it when we passed.
We had to design these tests carefully to make sure we weren't accidentally implying an implementation. There were some parts of the library that quickly became stable, but some of the other code would keep the same features while radically changing the implementation. These tests would only tie down the parts we knew weren't changing. The general principle that we followed was "test what you know" because we wanted freedom to change our minds about the uncertain parts of the code. this strategy eliminated situations where interfaces were locked down prematurely in tests. We could radically change our mind, and only needed to create tests for the sections of code that were stable enough.
This testing strategy helped answer the hard questions of testing. It was clear what parts of the app we should test, and how we should test them. We also kept the fun hacking vibe of the app throughout the process because our testing allowed us to radically change the implementation without punishing us. For more information about CSS-Spriter, check us out on github