Sunday, 21 April 2013

Testing your Database with TinyNH, the Simple NHibernate Infrastructure

TinyNH is a demo project and series of articles designed to help you get your project up and running with a simple solid NHibernate persistence infrastructure. See contents for a full list of articles.


So far, you've:
  • Created your domain model entity classes
  • Added a ConfigurationBuilder to initialise your NHibernate Configuration and class mappings
  • Added a ConfigurationStore as an application-wide point of access for your Configuration and ISessionFactory
You'll now be thinking about checking whether everything you've done actually works. Is NHibernate set up correctly for your target database environment? Are all of your properties and relationships in your model mapped so that your entities can be loaded and saved as expected?

One way to do this would be to:
  1. Write some application code
  2. Manually test that you can load and save your entities
  3. Fix any issues that come up
  4. Restart the application and try again
  5. Repeat steps 1-4 until everything works properly. 
All very well, but quite a burden, especially on a larger project, and it's a process that you'd need to repeat often if making changes to your model.

Automated Database Testing

TinyNH demonstrates how to write automated tests to validate your NHibernate set up and persistence infrastructure.

End-to-end database tests interact with, like, a real database in exactly the same way as your application does and can drastically reduce the amount of manual testing you'll need to do. They provide rapid feedback after you have made changes to your entity classes, tweaked your NHibernate mappings or upgraded NHibernate and any related components.

Database Test Examples 

You can review the database tests in the TinyNH.DemoStore.Tests project:

Here’s an example of a test used to validate the saving of Category entities to the database:

Look at CategoryPersistenceTests.cs on github if you can't see the code in-line above.

The test essentially "round-trips" a Category entity object to and from the database, comparing the original saved object with a different loaded version. You'll notice that several assertions are used to validate that each property has been saved and loaded correctly. This could become quite tedious for entities with lots of properties. The invention of a reliable mechanism for comparing the structure of 2 objects has been left as a creative exercise for the reader for now - a demonstration of some techniques may be added to TinyNH in the future.

We also include some very basic validation of our configuration in ConfigurationTests.cs. This is quite a coarse test that tells us if the Configuration and ISessionFactory are created correctly. This is a great way to flag up any errors with your configuration and entity classes when first developing your model, such as missing empty constructors or virtual modifiers on public members. Running this test is a lot quicker than hitting F5 and waiting for all of the other parts of your application to initialise in addition to NHibernate, only to find that you've got one more non-virtual property to fix.

Database Testing Infrastructure

The DatabaseTests base class provides some useful shared set up functionality to our database test classes. It does two useful things:
  • Database setup - A dedicated test database is automatically created for use by our tests
  • Data cleanup - Unwanted data is deleted before each test runs, allowing tests to begin with the database in a consistent state.
Let's look at the database and schema set up first, which happens once per test run:

Look at the static DatabaseTests constructor in DatabaseTests.cs on github if you can't see the code in-line above.

A ConfigurationStore is used to initialise and make available a shared Configuration and ISessionFactory throughout the test run. Initialising these objects takes a bit of time and using a ConfigurationStore benefits our tests in the same way as it would a regular application.

We also make use of ConfigurationStore's "configurationReady" hook to trigger creation of the database after the Configuration has been initialised. The SetupDatabase method first drops and creates the database that NHibernate will connect to when running the tests. The DatabaseSetUpHelper.cs class is used to manage database creation. It works by using a special "setup" connection string to connect to the master database on the server where the tests are running, then runs some SQL to drop and recreate the database.  We then apply the schema to this new empty database. These tests demonstrate a code-first approach, where the database schema is generated from our domain model and NHibernate mappings, using the NHibernate SchemaExport tool. Below, we discuss alternatives for setting up the database schema.

With the database in place, we also need to look at cleaning up the data that is generated by our tests. The ResetDatabase method is called before each individual test runs, so that each test starts with the database is in a known clean state.

Look at the SetUp and ResetDatabase methods in DatabaseTests.cs on github if you can't see the code in-line above.

As you can see, tables are cleared using a simple direct SQL script. See Using a SQL script to reset data in database integration tests for further background on this approach and Generating a script in SQL server to delete data from your database for an interesting way of generating a reset script for a larger database.

Not bad for a 74 line base class!

Code-First or Database First

There are 2 approaches to domain model design and schema creation.
  • Code-first - You design your domain model entities as C# classes, then generate the database schema needed to support these entities.
  • Database-first - You prefer to design your entities as database tables, then think about writing the classes that map to them.
A code-first approach works really well on greenfield projects and feels like a purer OO approach but it really comes down to personal preference. A hybrid approach is best: code first, but don’t forget about the database! Developers who understand both good OO design practices and the mechanics and limitations of relational databases will probably make domain models that work best in the real world.

TinyNH demonstrates a code-first scenario - in that it demonstrates how you can generate a database schema from domain model entities you have designed as C# classes. 

If you are working database-first and / or have an existing database schema to work to, you can replace the SchemaExport mechanism with an alternative means of setting up the structure of your database.

Generated vs Versioned Database Schemas

If you develop your model in a code-first fashion, automatically generating your database schema at runtime is a useful short cut that will serve you well during the earlier stages of a project's development.

However, there will be a point where you need to bring your database creation under version control, ideally using a database migration tool to apply a sequence of change scripts. At this point, both your application and tests should be running against a schema that has been created from version controlled database migration scripts, not a schema generated at runtime using your ORM! 

Unfortunately, this is outside of the scope of TinyNH at present. If you are familiar with evolutionary database design and migration scripts, you would probably find it simple to swap out the SchemaExport mechanism used here.

Do you need database tests?

Hopefully, the above demonstration has shown that automated database testing needn't be difficult or complex. It is a practice that would benefit any project.

Here are some guidelines to help you decide whether you want to include database tests in your application:
  • Familiarity / Expertise - If you’re learning NHibernate at the moment, aren’t familiar with automated testing tools and practices and are worried that your head might explode, you may wish to put test development on hold and write something dirt-simple to get you started. A console application that loads and saves your objects and writes to the console is a good way to start.
  • Model Complexity - If you have a larger model with some complex mappings, tests are an extremely useful way to check that you have mapped your model correctly. If you have a simple model that is easy to manually test within your application, then you might prefer not to bother.
  • Project Lifetime - If your application is an ongoing project that is likely to evolve over time, you’ll get a better pay-off from the time invested in setting up tests. You might be happy that manual testing is enough for a simpler throw-away demo or prototype project.
  • Test Database Environment - Automated database tests are harder to write if you have restricted access to your test database, if a test environment isn't permanently available, if you don’t have control over the data, or if it is shared by multiple users. 

Testing Framework and Structure

TinyNH uses NUnit, a widely used unit testing framework. You can adapt the tests to your preferred framework if you wish.

There is a single Tests project in the TinyNH solution. This contains an XXXTests.cs file for each class under test, with the test classes being located in a parallel namespace hierarchy within the Tests project. For example, TinyNH.DemoStore.Tests\Core\Domain\EntityTests.cs is used to test TinyNH.DemoStore.Core\Domain\Entity.cs.

We have Category attribute on any tests that access the database. Database tests are a lot slower than “textbook” unit tests, which run entirely in-memory and do not access databases, network, file system etc. When running your tests, you may prefer to run the faster unit tests first.

Tests for Other TinyNH Elements

TinyNH isn't a separate framework that you make use of by referencing a dll, it’s a template project containing elements that you copy into your own project. You’ll notice that TinyNH contains tests for these elements, such as the base Entity class, ConfigurationBuilder and ConfigurationStore. If you copy Entity.cs and ConfigurationBuilder.cs into your project, you may be wondering whether you should copy over EntityTests.cs and ConfigurationTests.cs too.

If you already have tests in place for other parts of your project, then you may wish to copy all of the tests over from TinyNH tests across so that any code you've added from TinyNH is covered by tests.

How to do it

If you don't already have a test project in your solution, add a Tests class library project to your solution

Add a reference to NUnit (or preferred testing framework).

Add the DatabaseSetupHelper.cs class to a suitable location within your solution. This isn't used only for testing so it needs to be available to application code as well as test code. Maybe you have somewhere within your project for shared utility functionality.

Establish the correct namespace within your test project for your database tests and add the DatabaseTests.cs base class. Modify the resetSql constant within the ResetDatabase method in line with your database schema.

Add ConfigurationTests.cs to the same namespace as DatabaseTests.cs.

Create some end-to-end load-and-save test for the relevant entities in your solution, based on examples in ProductPersistenceTests.cs.

No comments:

Post a Comment