views:

120

answers:

5

Hi all,

i've started unit testing a while ago and as turned out i did more regression testing than unit testing because i also included my database layer thus going to the database verytime.

So, implemented Unity to inject a fake database layer, but i of course want to store some data, and the main opinion was: "create an in-memory database"

But what is that / how do i implement that?

Main question is: i think i have to fake the database layer, but doesn't that make me create a 'simple database' myself or: how can i keep it simple and not rebuilding Sql Server just for my unit tests :)

At the end of this question i'll give an explanation of the situation i got in on the project i just started on, and i was wondering if this was the way to go.

Michel

Current situation i've seen at this client is that testdata is contained in XML files, and there is a 'fake' database layer that connects all the xml files together. For the real database we're using the entity framework, and this works very simple. And now, in the 'fake' layer, i have top create all kind of classes to load, save, persist etc. the data. It sounds weird that there is so much work in the fake layer, and so little in the real layer.

I hope this all makes sense :)

EDIT: so i know i have to create a separate database layer for my unit test, but how do i implement it?

+2  A: 

i used Sqlite for unit test as fake DB

Andrey
Before i try this out: what is the use of substituting the SqlServer with Sqlite? Does it run faster or is it something else that makes it better or easier to test against sqlLite than the production data layer? Is it easier to setup the test data?
Michel
Sqlite is in-process database. it is very different. it is always local
Andrey
@Andrey: i typed another question at Rafael's answer, because he also mentioned sqllite. Can you also give comment on that?
Michel
+1  A: 

I've been using in memory Sqlite for my unit tests, its really usefull

Rafael Mueller
As i'm trying to understand the difference between unit testing on a sqlserver and a sqllite: what benefits do i have when i use this database in stead of my production one? It looks like i still have to write sql, and in that case won't i be writing it twice? (once for the production database and one for the testdatabase/sqllite). Or is it just the performance gain because it's lightweight, or are there other advantages.
Michel
Actually you dont need to write sql, nhibernate can get your mapping files (.hbm or whenever you use to map your domain) and generate the database from those mappings, like this:new SchemaExport(configuration).Execute(true, false, false, session.Connection, stringWriter);The advantage is performance (in memory sqlite is really fast) and also you dont need to mess with your production database while testing.
Rafael Mueller
+2  A: 

Why don't you use a mocking framework (like moq or rhino mocks)? If you access your data through an interface, you can mock that interface and specify whatever you want to return on every test. Other approach is to have a separate environment for testing purposes, with a "real" database, where you make tests before taking your code for the production environment.

uvita
Do i understand it correct that in the latter case only the data is different for the test, and the rest of the database layer stays the same?
Michel
Exactly, you use a SQL Server DB, just as in the development or production environment, but you have a separate environment, similar to that in production.
uvita
+3  A: 

Define an interface for your data access layer and have (at least) two implementations of it:

  • The real database provider, which will in turn run queries on an SQL database, etc.
  • An in-memory test provider, which can be prepopulated with test data as part of each unit test.

The advantage of this is that the modules making use of the data provider do not need to whether the database is the real one or the test one, and hence more of the real code will be tested. The test database can be simple (like simple collections of objects) or complex (custom structures with indexes). It can also be a mocked implementation that will assert that it's being called appropriately as part of the test.

Additionally, if you ever need to support another data storage method (or different SQL database), you just need to write another implementation that conforms to the interface, and can be confident that none of the calling code will need to be reworked.

This approach is easiest if you plan for it from (or near) the start, so I'm not sure how easy it will be to apply to your situation.

What it might look like

If you're just loading and saving objects by id, then you can have an interface and implementations like (in Java-esque pseudo-code; I don't know much about asp.net):

interface WidgetDatabase {
    Widget loadWidget(int id);
    saveWidget(Widget w);
    deleteWidget(int id);
}

class SqlWidgetDatabase extends WidgetDatabase {
    Connection conn;

    // connect to database server of choice
    SqlWidgetDatabase(String connectionString) { conn = new Connection(connectionString); }

    Widget loadWidget(int id) {
        conn.executeQuery("SELECT * FROM widgets WHERE id = " + id);
        Widget w = conn.fetchOne();
        return w;
    }

    // more methods that run simple sql queries...
}

class MemeoryWidgetDatabase extends WidgetDatabase {
    Set widgets;

    MemoryWidgetDatabase() { widgets = new Set(); }

    Widget loadWidget(int id) {
        for (Widget w: widgets)
            if (w.getId() == id)
                return w;
        return null;
    }

    // more methods that find/add/delete a widget in the "widgets" set...
}

If you need to run more other queries (such as batch selects based on more complex criteria), you can add methods to do this to the interface.

Likewise for complex updates. Transaction support is possible for the real database implementation. I'm not sure how easy it is to build an in-memory db that is capable of providing proper transaction support. To test it you'd need "open" several "connections" to the same data set, and to only apply updates to that shared dataset when a transaction is committed.

Edmund
Thanks, i was going in this direction, but was stuck how to implement the 'in-memory test provider'.
Michel
+1  A: 

Uhhhh...... If you're storing all your test data in XML files. You've just changed one database for another. That is not an in memory database. In PHP you would use something like this.

class MemoryProductDB {

    private $products;

    function MemoryProductDB() {
        $this->products = array();
    }

    public function find($index) {
        return $this->products[$index];
    }

    public function save($product) {
        $this->products[$product['index']] = $product;
    }
}

You notice that all my data is stored in a memory array and is retrieved from a memory array. This is a simple In Memory Database.

IMHO, if you're using XML to store test data then you really haven't disconnected the dependencies from the model and the database effectively. No matter how complex your business rules are, when they touch the database, all they really are doing is CRUD (create, retrieve, update, and delete) functionality.

If you what your dealing with in the model is multiple objects from the database then maybe you need to compose all those objects into a single object and have the model use that one object. An example would be an order composed of products. Don't be retrieving products then saving products. Retrieve orders then save orders and have your model work on orders. The model shouldn't know anything about products.

This is called granularity of abstraction.

[Edit] There was a very good question in the comments. When testing with an In Memory Database we don't care about how the select works in a database. The controller, first off, has to have functionality on the database to count the number of possible records that could be accessed for paging. The IMDb (in memory database) should just send a number. The controller should never care what that number is. Same with the actual records. Hopefully all your controller is doing is displaying what it gets back from the IMDb.

[EDit] You should never be unit testing your controllers with a live model and imdb. The setup code for the imdb will have a lot of friction. Instead when unit testing a controller, you need to unit test a mock, stub, fake model. The best use of an imdb is during an integration test or when unit testing a model. Isn't an imdb a fake?

My scenario is:

  1. In my client I use a plug in for a table. DataTables. Server side processing.
  2. Client GET requests items in table product.get(5,10). The return data will be encoded JSON.

The model will be responsible for forming the JSON from retrieving information from the gateway to the database. The gateway is just a facade over the database. I'm a mocker so my gateway is a mock not an in memory gateway.

public function testSkuTable() {
    $skus = array(
            array('id' => '1', 'data' => 'data1'),
            array('id' => '2', 'data' => 'data2'),
            array('id' => '3', 'data' => 'data3'));

    $names = array(
            'id',
            'data');
    $start_row = $this->parameters['start_row'];
    $num_rows = $this->parameters['num_rows'];
    $sort_col = $this->parameters['sort_col'];
    $search = $this->parameters['search'];
    $requestSequence = $this->parameters['request_sequence'];
    $direction = $this->parameters['dir'];
    $filterTotals = 1;
    $totalRecords = 1;

    $this->gateway->expects($this->once())
            ->method('names')
            ->with($this->vendor)
            ->will($this->returnValue($names));

    $this->gateway->expects($this->once())
            ->method('skus')
            ->with($this->vendor, $names, $start_row, $num_rows, $sort_col, $search, $direction)
            ->will($this->returnValue($skus));

    $this->gateway->expects($this->once())
            ->method('filterTotals')
            ->will($this->returnValue($filterTotals));

    $this->gateway->expects($this->once())
            ->method('totalRecords')
            ->with($this->vendor)
            ->will($this->returnValue($totalRecords));

    $expectJson = '{"sEcho": '.$requestSequence.', "iTotalRecords": '.$totalRecords.', "iTotalDisplayRecords": '.$filterTotals.', "aaData": [ ["1","data1"],["2","data2"],["3","data3"]] }';
    $actualJson = $this->skusModel->skuTable($this->vendor, $this->parameters);

    $this->assertEquals($expectJson, $actualJson);
}

You will notice that with this unit test that I'm not concerned what the data looks like. $skus doesn't even look anything like that actual table schema. Just that I return records. Here is the actual code for the model:

public function skuTable($vendor, $parameterList) {
    $startRow = $parameterList['start_row'];
    $numRows = $parameterList['num_rows'];
    $sortCols = $parameterList['sort_col'];
    $search = $parameterList['search'];
    if($search == null) {
        $search = "";
    }
    $requestSequence = $parameterList['request_sequence'];
    $direction = $parameterList['dir'];

    $names = $this->propertyNames($vendor);
    $skus = $this->skusList($vendor, $names, $startRow, $numRows, $sortCols, $search, $direction);
    $filterTotals = $this->filterTotals($vendor, $names, $startRow, $numRows, $sortCols, $search, $direction);
    $totalRecords = $this->totalRecords($vendor);

    return $this->buildJson($requestSequence, $totalRecords, $filterTotals, $skus, $names);
}

The first part of the method breaks the individual parameters from the $parameterList that I get from the get request. The rest are calls to the gateway. Here is one of the methods:

public function skusList($vendor, $names, $start_row, $num_rows, $sort_col, $search, $direction) {
    return $this->skusGateway->skus($vendor, $names, $start_row, $num_rows, $sort_col, $search, $direction);
}
Gutzofter
Allright, so if want to test my controller if it correctly returns 10 products when i call .GetProducts(5,10) (which means, get 10 products starting at product 5, use for paging), should i populate the MemoryProductDB array with 15 or 20 objects in code first, or am i missing the point here?
Michel
Good question. You only need 10 objects. Their are two events that your are dealing with here. Paging and Data retrieval. I just recently implemented something like this. See my edit.
Gutzofter
hi, read your edit, with the addition 'the imdb just send a number'. In my test, which may be wrong set up, i ask for GetProducs(5,10) at the controller, and i get a Model back with the # of products in this page, the page size, total # of pages, total # of products etc to show (later on) in the view, so i presume the imdb must not make up the numbers? if i put in 41 products, i expect page size 10, total pages 4, total products 41 etc. Or is there something wrong with my test here? I first thought it was may be tetsing too much, but it only tests one controller action.
Michel
@michel, Are you testing your controller with a model and the imdb? if so then I believe you are testing way too much. When testing this way you are also testing your model. So you end up having to setup-up your imdb to put your models in the correct state. That's a lot of setup and varying variable states. See my new edit.
Gutzofter
@michel, You should edit your question and put in the information that you supplied as a comment in there. It will bring the question back up as if it was a new question. This possibly will get you more answers
Gutzofter
@Gutzofter: thanks very much for your extensive reply.
Michel