How do I implement / create / create 'in memory database' for my unit test
I started unit testing a while ago, and as it turns out, I did more regression testing than unit testing because I included my database layer as well, so it's very easy to get into the database.
So, implemented by Unity to implement a fake database layer, but of course I want to store some data, and the main opinion was "create an in-memory database"
But what is it / how should I implement it?
Main question: I think I need to spoof the database layer, but that doesn't force me to create a "simple database" myself or: how can I keep it simple and not rebuild Sql Server just for my unit tests :)
At the end of this question, I will give an explanation of the situation in which I got into a project that I just started, and I was wondering if this would be the case.
Michelle
The current situation I have seen on this client is that testdata is contained in XML files and there is a "fake" database layer that links all the XML files together. For a real database, we are using an entity structure and it works very simply. And now, on the "fake" layer, I have a top one that creates all types of classes for loading, saving, saving, etc. Data. It sounds strange 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 need to create a separate database layer for my unit test, but how do I implement it?
a source to share
Uhhhh ...... If you save all your test data in XML files. You have just changed one database to another. It is not in the 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;
}
}
Have you noticed that all my data is stored in the memory array and retrieved from the memory array. It is a simple memory database .
IMHO, if you are using XML to store test data, you really haven't disabled model and database dependencies efficiently. No matter how complex your business rules are when they deal with the database, all they really do is CRUD (create, get, update and delete).
If you are working with a model in multiple objects from the database, you may need to collect all of these objects into one object and use the model for one object. An example would be order
consisting of products. Do not remove food and then store food. Receive orders, then save the orders and have your model work on the orders. The model doesn't need to know anything about the products.
This is called the grain of abstraction.
[Edit] There was a very good question in the comments. When testing with a memory-backed database, we don't care how selection works in the database. The controller must first have functionality in the database to count the number of possible records that could be accessed for swap. IMDb (in memory database) should just send the number. The controller doesn't have to care what the number is. It's the same with actual recordings. Hopefully all your controller does is display what it receives from IMDb.
[edit] You should never be a module checking your controllers with live model and imdb. The installation code for imdb will have a lot of friction. Instead, when unit testing a controller, you need to unit test a mock, stub, fake model. It is best to use imdb during an integration test or when unit testing a model. Is imdb fake?
My script:
- In my client, I am using a include file for a table. DataTables . Server side processing.
- The GET client requests items in the table
product.get(5,10)
. The return data will be JSON encoded.
The model will be responsible for generating JSON from fetching information from the gateway to the database. The gateway is just a facade over the database. I am a mockery, so my gateway is a layout not in a 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 I am not interested in what the data looks like. $skus
doesn't even look like this actual table schema. I just return the 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 down the individual parameters from $parameterList
which I get from the get request. The rest are calls to the gateway. Here's one way:
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);
}
a source to share
Define an interface for the data access layer and at least two implementations:
- A real database provider, which in turn will run queries against a SQL database, etc.
- An in-memory test provider that can be pre-populated with test data as part of each unit test.
The advantage of this is that modules using the data provider do not require whether the database will be real or test and therefore more real code will be tested. The test database can be simple (for example, simple collections of objects) or complex (custom structures with indexes). It could also be a mockery of the implementation claiming to be named appropriately within the test.
Also, if you ever need support for a different storage method (or a different SQL database), you just need to write a different implementation that conforms to the interface and you can be sure that none of the calling code needs to be reworked.
This approach is easiest if you plan it (or near) from the beginning, so I'm not sure how easy it would be to apply to your situation.
What it might look like
If you are just loading and saving objects by id, then you can have an interface and implementations like (in Java-esque pseudocode, 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 (for example, batch selections based on more complex criteria), you can add methods to do this in the interface.
Likewise for complex updates. Transaction support is possible for a real database implementation. I'm not sure how easy it is to create an inline db capable of providing proper transaction support. To test it, you need to "open" multiple "connections" to the same dataset and only apply updates to that shared dataset when a transaction is committed.
a source to share
Why don't you use a mocking framework (like mock or rhino)? If you access your data through an interface, you can mock that interface and specify whatever you want to return on every test. Another approach is to have a separate testing environment, with a "real" database where you do your tests before you commit your code to production.
a source to share