In this chapter, we demonstrate how to write programmer tests when implementing a data access layer using ADO.NET. We limit the scope of this chapter by implementing only functionality that is needed to implement the data access aspects of the Web service. (See Chapter 4, The Media Library Example for a more complete discussion of the feature.) As you will see, not only do we have to implement the functionality associated with the feature, we have to augment the solution to be able to write tests appropriately. In later chapters, this implementation will evolve to address additional capabilities such as transactions, concurrency, and updates.
Testing a program that accesses data stored in a database has a number of challenges that are different from those we described previously.
Test time It takes significantly longer to run tests associated with data stored in a database than it does to run tests on in-memory data. The difference can easily be several orders of magnitude. Due to the increased amount of time it takes to execute the tests, you will see us use larger steps (more code) in this chapter to cut down on the number of times that we have to interact with the database.
Consistency The database ensures that data stored within it is consistent with the schema definition. This consistency checking is an issue when writing tests. Ideally, we want to be able to test each entity without having to create all the supporting objects. However, due to the consistency checks, we have to create all the supporting objects just to test the individual entity. In this chapter, we will show you how using a typed DataSet can mitigate this problem.
Test responsibility The test has to make sure that the environment in which it executes is known before the test is run. This means that it should not rely on any data already in the database. If you rely on data already in the database, you could run into trouble if another developer is manipulating the data at the same time or if it has changed since the last time you ran your tests. Therefore, prior to each test, you should insert into the database whatever is needed for your test, run your test, and then remove anything that was inserted. The amount of work needed in each test results in larger steps (more code). In this chapter, we use this technique for testing the individual entities and their relationships. This problem could be solved by each developer having his own copy of the database and a way to reset it to a known state during the setup of the test. A more advanced technique using transactions is also possible and will be presented in Chapter 10, Programmer Tests: Using Transactions.
Production database You should not run your programmer tests on the production database. The risk is too high that you will mess up the existing data. That said, how can you approximate the run- time environment for your tests? Running the tests on blank databases is not a good test because the databases themselves are rarely, if ever, blank. The solution that we advocate and have used on projects is to take a snapshot of the production database and run the programmer tests on the snapshot. This gives you a very good approximation of the actual database without having to worry about your tests messing up the production database.
By the end of this chapter, you should have a good understanding of the issues associated with writing programmer tests for databases and how some of the issues are mitigated using a combination of capabilities in the .NET Framework and some testing strategies presented here.