At every company I’ve ever worked at we needed more automated tests.
The problem is that you can only do so much with Unit Tests in Cloud/Enterprise development. You need “integration tests”, but those become a huge pain in the ass. Some people point to “Mocks” as the answer. Except Mocks don’t seem to work.
Recently I read a blog post from someone about what he called the “NoMock movement”. I’d like to make my own contribution, because I believe in Cupcake testing instead of Mocks. Let me explain what I mean by that.
So most cloud/enterprise applications look like this:
- Persistence Layer
- Storage Layer/Database
- For debugging the persistence layer, pretty much if you can save your object once, you can save it most of the other times too. So debugging the Persistence and Storage Layers is interesting for the people working on those layers, is not that interesting otherwise. For the persistence/storage layers, you need unit tests, but you don’t need to include them in the integration tests for your application. You just need to temporarily record changes, which you can do in memory for the small amount of data you have to deal with in a test.
- The ViewLayer -> BusinessLogic layer is 90% reads. So debugging the BusinessLogic loading from the Persistence Layer isn’t interesting to the ViewLayer testing. What you really want it to test View Layer -> Business Logic.
- That leaves us with 2 types of tests to develop: Complex Business Logic tests, and View Layer -> Business Logic tests. These types of tests are much easier to develop, because we don’t have to bootstrap persistence and storage layers along with our other tests.
- Write unit tests for each object in the business logic.
- Setup something that can load an object tree into memory from a YAML file. SnakeYAML makes this easy. You can then use that to write unit tests against the BusinessLogic that work with multiple objects at once.
- Setup something that fakes out the PersistenceLayer by just using an in-memory copy of the object tree from #2. This is about the closest you get to a mock, but its a smarter kind of mock. It’s a mock persistence layer. You can’t use a Mock framework to do this, but if you’ve already done step #2, it will be pretty easy. You’ll need two implementations of your persistence layer, one that does this, one that does the real thing.
- You can now write view layer tests. First step is make sure every page works which you can do without a browser. You can even do this as an automated test that just loads every page.
- Setup Selinium tests that do more complicated stuff.
- Write something that can walk an object tree and generate a YAML file for step #2. Again, easy with SnakeYAML. Add the appropriate links in your admin tools and you can now dump real data from production into a file and have a good set of stuff from production to use in testing. Have a bug? Train Customer Service to click the dump button.
That’s what the Cupcake testing model looks like. The key bit is writing the to/from bit via SnakeYAML, but that’s not too bad because SnakeYAML can already follow collections and dump arbitrary JavaBeans. The only catch is that you need to prevent SnakeYAML from dumping out the entire database given one object. You can follow a customer to an order, and from there to a list of lineitems, because the order owns the line items. You can follow the lineitem to the product, because the relationship is to-one and safe, and from product to vendor because that’s a to-one, but you shouldn’t follow vendor back to products because that will end up pulling in the entire database! To write the dump code so it pulls in all relevant pieces of data but not the entire database you just need to have the following rules:
- By Default, follow all to-one relationships. It’s almost always safe to add one more objects to the pile of objects.
- By Default, do not follow all to-many relationships. It’s the to-many relationships that are dangerous.
- By Default, strip out any primary keys. That way your test cases are floating free as detached objects to start with and won’t conflict with other data. This is about a zillion times easier if your primary key columns all have the same name, i.e. “Id” or “uuid”, because you can just special case those in your SnakeYAML dumper.
- Have a method of overriding the defaults on relationships. That’s easy to do by merely loading a config file via YAML in your SnakeYAML dumper class. All that config file needs is a class name and the properties you do and don’t want to follow. i.e. com.chegg.Order.lineitems should be follow from my example above.
- In general, the follow rule is: follow relationships that imply ownership. Orders own lineitems, so its ok to follow them. This can end up finding problems in your data model because what you may find is that the ownership isn’t what you think. It’s natural to think of Vendors as owning their products, but actually, vendor is an attribute of a product; product owns vendor, vendors don’t own products. That seems counter intuitive, but that’s a topic for another day.
Well, that’s the Cupcake model. Let me know how it works for you.