Unit testing against large databases - database

I would like to ask about your suggestions concerning unit testing against large databases.
I want to write unit tests for an application which is mostly implemented in T-SQL so mocking the database is not an option.
The database is quite large (approx. 10GB) so restoring the database after a test run is also practically impossible.
The application's purpose is to manage the handling of applications for credit agreements. There are users in specific roles that change the state of agreement objects and my job is to test part of this process.
I'm considering two approaches:
First Approach
Create agreements that meet specific conditions and then test changes of agreement state (eg. transition from waiting in some office to handled in this specific office). The agreements will be created in application itself and they will be my test cases. All the tests will be in transactions that would be rolled back after performing these tests.
Advantages
The advantage of this approach is quite straightforward test. Expected data could be easily described because I exactly know how the object should look like after the transition.
Disadvantages
The disadvantage is that the database cannot change in a way that will break the test. Users and agreements used in test cases must always look the same and if there will be a need to change the database the preparation process will have to be repeated.
Second Approach
Create agreements in unit tests. Programatically create agreements that would meet specific conditions. The data used for creating agreement will be chosen randomly. Also the users that will change agreement state will be created randomly.
Advantages
The advantage of this approach is ease of making changes to objects and ability to run tests on databases with different data.
Disadvantages
Both objects (agreement and user) have lots of fields and related data and I'm afraid it would take some time to implement creation of these objects (I'm also afraid that these objects may contain some errors because the creation method will be quite hard to implement without errors).
What do you think about these two approaches?
Do any Stack Overflow readers think it is worth the effort create objects as described in second approach?
Does anyone here have any experience creating such tests?

I'm not sure I entirely agree with your assumption that you cannot restore the database after a test run. While I definitely agree that some tests should be run on a full-size, multi-TB database, I don't see why you can't run most of your tests on a much, much smaller test database. Are there constraints that need to be tested like "Cannot be more than a billion identical rows?"
My recommendation would actually be to use a smaller test database for most of your functional specs, and to create-drop all of its tables with each test, with as little sample data as is necessary to test your functionality.

For creating fixture data for tests, you have a few choices:
(a) Create a script that creates an empty database, and then adds a small number of records as the fixture data. This data can be hand-constructed, or a few records from the real database. This is the Rails approach, and pretty common in the Java world.
(b) It's also common to use a "factory" to create this data (some sort of application code). There is an initial investment in building these classes, but once they are built they can be re-used for all your tests. This is now very popular in Ruby/Rails code. (This is your Second Approach above.)
(c) Of course you can use a copy of the "production" data, and try to test against that. But this is probably the hardest approach as you will always be competing against he real world changing the data. And it also tends to be orders of magnitude slower than a small set of fixture data.
There's definitely a cost of getting from state (c) to state (a) or (b)-- but it is an investment in the future. It won't take that long-- even if it takes a whole day, the speed-up in the test running will make up for it quickly.
There's a someone independent issue. After you get your data into the database, and then run your tests, you need to restore it. There are a few common approaches:
(1) rollback the transaction. This is a great way to go-- if practical. Sometimes, though, you actually need to confirm that transactions completed, so this doesn't work.
(2) just re-load a new set of fixture data. If your fixture data is small this is workable. A little slower than (1).
(3) manually undo what the tests have done. This is the most error prone and difficult approach, but possible.
Recommendation?
It sounds like your application is complicated. I'd recommend hand-crafting a small set of data for your tests (a). Keep it separate from your main database so that it's easier to keep track of and reload. Try to rollback transactions, but if that doesn't work for you, you can reload from a script before each test (remember-- the data is small).
The other piece of the puzzle is database migrations, if you don't already have that nailed. These are scripts that use use to evolve your database. If you have these organized and automated, you can apply them to your test/fixture data as well as your production data.

How about testing everything in a transaction and then roll it back? E.g:
BeginTransaction
DoThings
VerifyResult
RollbackTransaction

Related

Why is recommended to recreate the whole database schema for each test?

Its said that in order to execute database tests with Spring data(JPA) for example, its a good practice to recreate the whole database schema for each test in order to drop all the objects from database after the test and clean the whole environment for the next test (https://reflectoring.io/spring-boot-data-jpa-test/). But according to the question I made (Sql scripts for creation of same table in repository tests), if its recommended to create the whole schema for the database for tests and not just a part of it, why not to reuse this schema for all tests if they share the same schema and delete just the data?
Thanks
The whole point is to make sure that your tests are isolated and data required or generated by a test, won't affect the result of a different test.
Re-creating the schema gives you this guarantee because each tests will start with a clean database.
That said, the creation/drop of the schema is a pretty heavy operation, and the more tests you add to the test suite, the slower it will become.
The drawback is that you need to make sure that the data is cleaned after each test. This might not be too complicated to achieve in practice (you might need to pay special attention about different entities in different tests mapping to table with the same name) but if you are working an a small project or your database is really quick updating the schema, it might not be worth the hassle.
It's up to you to decide what works for your project.
As a practical example, in Hibernate Reactive we need to run the same tests on several different databases. We started re-creating the factory for each unit test, but because running tests was becoming too slow (especially on some databases), we've switched to re-creating the factory once for each test class. It helped us to save a lot of time because tests in the same class became so much faster.
In the end, you need to decide how your test suite is organised and the benefits of one approach over the other. But as long as you are reasonably sure that the tests won't affect each other, you can use the solution you prefer.

SpecFlow Integration Testing with Database Patterns

I'm attempting to set up SpecFlow for integration/acceptance testing. Our product has a backing database (not a huge one though) in Sqlite.
This is actually proving to be a slightly sticky point though; how do I model the database for the tests?
I would like to know what patterns others out there use for doing integration/acceptance testing with backing databases.
I can think of the following approaches:
Compile a database into the assembly with the tests, then shadow-copy it for each test. Seems slow though.
I could create the database in memory and populate it with pre-determined data.
I could create the database in memory and somehow have Givens populate the database. This seems like it would bloat the tests horribly, but might give them more control and make the tests less fragile.
I could abstract every database interaction and use mocks. Not in love with this idea since I'd like to use this to test the database interactions as well.
Compile the database into the tests and rely on clean-up code to return it to the base state (this one seems dodgy to me). Don't want to do it with transactions since there will be multiple interactions with some tests (so write an item then attempt to read it back with different privileges).
Before considering the How to test, I think you might find it valuable to look at What you want to test.
Starting with what data, I find that it really helps to take a single element, or a small number, and imagine a set of events around them in order to give you the right test data to run your tests with. For example;
If you were working on a healthcare system, you might define a person "Bob" and then produce his life events. Bob was born 37 years ago today, fell off his bike as a child and broke his arm, got married, and has two children.
If you are working on a financial trading system, you might look at a day between opening and closing for a couple of stocks, e.g. "MSFT" and "APPL". On this day you might see one starting low and climbing, the other starting high and falling. A piece of news comes out that reverses their fortunes.
Now you have the what you can actually evaluate which of your scenarios actually work for your data, e.g. “MSFT” and “APPL” could have 1,000s of price changes throughout the day, so generating the Givens and Mocks would be very time consuming. This data lends itself to being pre-captured. On the other hand the “Bob” data works particularly well when using generated data because the data can always change so that it is his birthday today.
One thing your question doesn’t seem to need to consider is updating your data. For example you might want to have a set of tests that work at various stages of your entities life cycle, e.g. Some tests deal with “Baby Bob”, others with “10yr old Bob”,or “Married Bob” etc. If your DB is read only then this isn’t a problem if you can write your tests so that they just don’t see the other data, but sometimes you want build a story through your tests. If your tests do change the data, then you will have problems with ensuring that either your tests run in order (see MSTest OrderedTest or mbUnit DependsOn), or that you can separate your tests so they each deal with an isolated data entity (this is fine if your entity can be described in a single row, but gets harder when you have to read many tables to get it).
You also might want to consider what code you are testing, you can vary the approach inside your different test sets. I currently work on a multi-tier application that has a UI Views, View Models, Client Models, multiple communication systems, and server models. I also have different sets of tests for these. I have some tests that work in a single tier, mocking out other tiers to keep my tests small. Other tests fire up a local server and local client and wire the two up directly. Finally I have some tests that launch a full server process, communicate via EMS and run some simple client side operations using everything but the UI Views.
So now to actually answer your question,
Shadow copy your database - Yes, I’ve done this once with SQLServer Developer and had an xxx.mdb that got copied in before running the tests. However some modern testing frameworks will run tests in parallel e.g. NCrunch, so this just breaks.
Create the database and pre-populate - Not done this one, but my concerns would be what happens where a test changes the database to an unexpected state. Other tests will fail when they have done nothing wrong.
Create the database and use Givens - I’ve done this with NUnit via [SetupFixture]on top of a Linq-to-sql DB.You still have concerns about parallel test runs and you have to balance the granularity of your givens (see StackOverflow-When do BDD scenarios become too specific), and you have the data update ordering/data isolation problem, but this can work really well to allow you to work through your data stories and grow the data throughout your tests. On the other hand, should one test fail and leave the data in a bad state you can end up with lots of failures, but at least you simply need to look at the one that fails first. This kind of testing will also be not play very nicely for developers on their workstation as they can’t just run a single test, particularly with tools such as NCrunch, which can just run tests whose code has changed.
Mock the database This is how I choose to do things now. The trick is that if you are personally following a reasonably strict TDD process where you only test the method you are working on, then you actually end up with some tiers that test the database interaction, e.g. [Test]DALLayerTests.ShouldReadARowAndCreatePOCO(), but most others that used mocked data to test what actually happens e.g. [Test]BusinessObjectPersonTests.ShouldGetBirthdayCongratulations().
Use clean up code - Never tried it, it sounds dodgy :-)

What's the better practice for testing code which relies on a DB? Mocks and stubs? Or seeded data?

Seems like forever I've read that, when testing, use a mock database object or repository. No reason to test someone else's DB code, right? No need to have your code actually mess with data in a database, right?
Now lately I see tests which set up a database (possibly in-memory) and seed it with test data, just for running tests against.
Is one approach better than the other? If tests with seeded data are worth running, should one even bother with mock databases connections? If so, why?
There are a lot of ways to test code that interacts with a database.
The repository pattern is one method of creating a facade over the data access code. It makes it easy to stub/mock out the repository during test. This is useful when a piece of business logic needs tested in isolation and dummy values can help test different branches of the code.
Fake databases (in-memory or local files) are less common because there needs to be some "middle-ware" that knows how to read data from a real database and a fake database. It usually just makes sense to have a repository over the whole thing and mock out the repository. This approach is more feasible in some older systems where there is an existing infrastructure. For instance, you use a real database and then switch over to a fake database for test performance reasons.
Another option is using an actual database, populating it with bogus data. This approach is slower and requires writing a lot of scripts. However, this approach is fairly common as part of integration testing. I used to write a lot of "transactional" tests where I used a database transaction to rollback changes after running my tests. I'd write one large test that collectively performed all of my CRUD operations on a particular table.
The last approach makes sense when you are testing the code that converts SQL results into your objects. Your SQL could be invalid (or you use the wrong stored procedure name). It is also easy to forget to check for nulls, perform an invalid cast, etc. when mapping to objects. This code should be tested at some point. An ORM can help alleviate a lot of this testing.
I am typically pretty lazy these days. I use repositories. Most of my data layer code is touched when performing actual integration tests (hitting a real database with dummy data), so I don't bother testing individual database calls (no more transactional tests). I also use ORMs for doing most of my SELECT statements. I think a lot of the industry is moving towards this more lazy approach.
You should use both.
The business services should rely on DAOs, and be tested by mocking the DAOs. This allows for fast, easy to implement, easy to maintain tests.
The DAOs unique responsibility is to contain database access code (queries, etc.), and should also be tested. So you should use a test database, with test data, and check that their queries return/save what they're support to return/save.
I'm not a big fan of using an in-memory database, different from the one used in production. The behavior of some queries, constraints, etc. will be different from database to database, and you'd better be sure that the code will work on the production database, and not in an in-memory database used only by tests.

Recording test data in Hibernate

I have an automated test framework for testing hardware widgets. Right now only pass/fail results of test cases are stored into a relational database using hibernate. I'd like to change this so that various characteristics of the test are stored in the database. (e.g. how many gerbils are running inside the widget, the inputs to various assertions in the tests, etc.).
Each test case is represented as a Java class, so the first thing I thought of was using hibernate to just create a table for each test case. However, we have lots and lots of test cases so I don't think that having a table for each test case is necessarily the best idea.
The amount and type of data for specific test cases will not change on different executions of the test case, but the data needed for each test case will be dramatically different. To use a silly example: for the gerbil-gnawing test we always want to record the age and color of the gerbils gnawing at the wires, but for the smash test we only need to record how many rocks were thrown at the widget.
Ideally we would be able to query this information from the database using SQL so the data can't be stored as binary blobs or other un-queryable entities.
Any ideas on how to structure the database to meet these requirements? Am I totally off-base on not wanting a large number of tables?
I'd say you have two major options:
Make your TestCase classes supclasses of a common superclass and then use one of the inheritance mapping strategies (http://hibernate.org/hib_docs/reference/en/html/inheritance.html) don't worry to much about the number of tables/columns but of cause you should ensure that you do not hit a limit of your database engine.
Or you do a EAV model (http://en.wikipedia.org/wiki/Entity-Attribute-Value_model)
It is extremely flexible, but be aware that it causes extremely complex queries for simple questions.
Make sure that you run tests with realistic volumes before commiting to one or the other.
I would not worry about a lot of tables. If that is the correct schema, then that's what it is. DB's are designed to handle it.

What's the best strategy for unit-testing database-driven applications?

I work with a lot of web applications that are driven by databases of varying complexity on the backend. Typically, there's an ORM layer separate from the business and presentation logic. This makes unit-testing the business logic fairly straightforward; things can be implemented in discrete modules and any data needed for the test can be faked through object mocking.
But testing the ORM and database itself has always been fraught with problems and compromises.
Over the years, I have tried a few strategies, none of which completely satisfied me.
Load a test database with known data. Run tests against the ORM and confirm that the right data comes back. The disadvantage here is that your test DB has to keep up with any schema changes in the application database, and might get out of sync. It also relies on artificial data, and may not expose bugs that occur due to stupid user input. Finally, if the test database is small, it won't reveal inefficiencies like a missing index. (OK, that last one isn't really what unit testing should be used for, but it doesn't hurt.)
Load a copy of the production database and test against that. The problem here is that you may have no idea what's in the production DB at any given time; your tests may need to be rewritten if data changes over time.
Some people have pointed out that both of these strategies rely on specific data, and a unit test should test only functionality. To that end, I've seen suggested:
Use a mock database server, and check only that the ORM is sending the correct queries in response to a given method call.
What strategies have you used for testing database-driven applications, if any? What has worked the best for you?
I've actually used your first approach with quite some success, but in a slightly different ways that I think would solve some of your problems:
Keep the entire schema and scripts for creating it in source control so that anyone can create the current database schema after a check out. In addition, keep sample data in data files that get loaded by part of the build process. As you discover data that causes errors, add it to your sample data to check that errors don't re-emerge.
Use a continuous integration server to build the database schema, load the sample data, and run tests. This is how we keep our test database in sync (rebuilding it at every test run). Though this requires that the CI server have access and ownership of its own dedicated database instance, I say that having our db schema built 3 times a day has dramatically helped find errors that probably would not have been found till just before delivery (if not later). I can't say that I rebuild the schema before every commit. Does anybody? With this approach you won't have to (well maybe we should, but its not a big deal if someone forgets).
For my group, user input is done at the application level (not db) so this is tested via standard unit tests.
Loading Production Database Copy:
This was the approach that was used at my last job. It was a huge pain cause of a couple of issues:
The copy would get out of date from the production version
Changes would be made to the copy's schema and wouldn't get propagated to the production systems. At this point we'd have diverging schemas. Not fun.
Mocking Database Server:
We also do this at my current job. After every commit we execute unit tests against the application code that have mock db accessors injected. Then three times a day we execute the full db build described above. I definitely recommend both approaches.
I'm always running tests against an in-memory DB (HSQLDB or Derby) for these reasons:
It makes you think which data to keep in your test DB and why. Just hauling your production DB into a test system translates to "I have no idea what I'm doing or why and if something breaks, it wasn't me!!" ;)
It makes sure the database can be recreated with little effort in a new place (for example when we need to replicate a bug from production)
It helps enormously with the quality of the DDL files.
The in-memory DB is loaded with fresh data once the tests start and after most tests, I invoke ROLLBACK to keep it stable. ALWAYS keep the data in the test DB stable! If the data changes all the time, you can't test.
The data is loaded from SQL, a template DB or a dump/backup. I prefer dumps if they are in a readable format because I can put them in VCS. If that doesn't work, I use a CSV file or XML. If I have to load enormous amounts of data ... I don't. You never have to load enormous amounts of data :) Not for unit tests. Performance tests are another issue and different rules apply.
I have been asking this question for a long time, but I think there is no silver bullet for that.
What I currently do is mocking the DAO objects and keeping a in memory representation of a good collection of objects that represent interesting cases of data that could live on the database.
The main problem I see with that approach is that you're covering only the code that interacts with your DAO layer, but never testing the DAO itself, and in my experience I see that a lot of errors happen on that layer as well. I also keep a few unit tests that run against the database (for the sake of using TDD or quick testing locally), but those tests are never run on my continuous integration server, since we don't keep a database for that purpose and I think tests that run on CI server should be self-contained.
Another approach I find very interesting, but not always worth since is a little time consuming, is to create the same schema you use for production on an embedded database that just runs within the unit testing.
Even though there's no question this approach improves your coverage, there are a few drawbacks, since you have to be as close as possible to ANSI SQL to make it work both with your current DBMS and the embedded replacement.
No matter what you think is more relevant for your code, there are a few projects out there that may make it easier, like DbUnit.
Even if there are tools that allow you to mock your database in one way or another (e.g. jOOQ's MockConnection, which can be seen in this answer - disclaimer, I work for jOOQ's vendor), I would advise not to mock larger databases with complex queries.
Even if you just want to integration-test your ORM, beware that an ORM issues a very complex series of queries to your database, that may vary in
syntax
complexity
order (!)
Mocking all that to produce sensible dummy data is quite hard, unless you're actually building a little database inside your mock, which interprets the transmitted SQL statements. Having said so, use a well-known integration-test database that you can easily reset with well-known data, against which you can run your integration tests.
I use the first (running the code against a test database). The only substantive issue I see you raising with this approach is the possibilty of schemas getting out of sync, which I deal with by keeping a version number in my database and making all schema changes via a script which applies the changes for each version increment.
I also make all changes (including to the database schema) against my test environment first, so it ends up being the other way around: After all tests pass, apply the schema updates to the production host. I also keep a separate pair of testing vs. application databases on my development system so that I can verify there that the db upgrade works properly before touching the real production box(es).
I'm using the first approach but a bit different that allows to address the problems you mentioned.
Everything that is needed to run tests for DAOs is in source control. It includes schema and scripts to create the DB (docker is very good for this). If the embedded DB can be used - I use it for speed.
The important difference with the other described approaches is that the data that is required for test is not loaded from SQL scripts or XML files. Everything (except some dictionary data that is effectively constant) is created by application using utility functions/classes.
The main purpose is to make data used by test
very close to the test
explicit (using SQL files for data make it very problematic to see what piece of data is used by what test)
isolate tests from the unrelated changes.
It basically means that these utilities allow to declaratively specify only things essential for the test in test itself and omit irrelevant things.
To give some idea of what it means in practice, consider the test for some DAO which works with Comments to Posts written by Authors. In order to test CRUD operations for such DAO some data should be created in the DB. The test would look like:
#Test
public void savedCommentCanBeRead() {
// Builder is needed to declaratively specify the entity with all attributes relevant
// for this specific test
// Missing attributes are generated with reasonable values
// factory's responsibility is to create entity (and all entities required by it
// in our example Author) in the DB
Post post = factory.create(PostBuilder.post());
Comment comment = CommentBuilder.comment().forPost(post).build();
sut.save(comment);
Comment savedComment = sut.get(comment.getId());
// this checks fields that are directly stored
assertThat(saveComment, fieldwiseEqualTo(comment));
// if there are some fields that are generated during save check them separately
assertThat(saveComment.getGeneratedField(), equalTo(expectedValue));
}
This has several advantages over SQL scripts or XML files with test data:
Maintaining the code is much easier (adding a mandatory column for example in some entity that is referenced in many tests, like Author, does not require to change lots of files/records but only a change in builder and/or factory)
The data required by specific test is described in the test itself and not in some other file. This proximity is very important for test comprehensibility.
Rollback vs Commit
I find it more convenient that tests do commit when they are executed. Firstly, some effects (for example DEFERRED CONSTRAINTS) cannot be checked if commit never happens. Secondly, when a test fails the data can be examined in the DB as it is not reverted by the rollback.
Of cause this has a downside that test may produce a broken data and this will lead to the failures in other tests. To deal with this I try to isolate the tests. In the example above every test may create new Author and all other entities are created related to it so collisions are rare. To deal with the remaining invariants that can be potentially broken but cannot be expressed as a DB level constraint I use some programmatic checks for erroneous conditions that may be run after every single test (and they are run in CI but usually switched off locally for performance reasons).
For JDBC based project (directly or indirectly, e.g. JPA, EJB, ...) you can mockup not the entire database (in such case it would be better to use a test db on a real RDBMS), but only mockup at JDBC level.
Advantage is abstraction which comes with that way, as JDBC data (result set, update count, warning, ...) are the same whatever is the backend: your prod db, a test db, or just some mockup data provided for each test case.
With JDBC connection mocked up for each case there is no need to manage test db (cleanup, only one test at time, reload fixtures, ...). Every mockup connection is isolated and there is no need to clean up. Only minimal required fixtures are provided in each test case to mock up JDBC exchange, which help to avoid complexity of managing a whole test db.
Acolyte is my framework which includes a JDBC driver and utility for this kind of mockup: http://acolyte.eu.org .

Resources