11

It seems like a chicken and egg problem.

You can make a write function write to some data store, but never know you saved it properly without a tested read function.

You can make a read function read from a data store, but how do you put stuff in that data store, to read, without a tested write function?

EDIT:

I am connecting to and making transactions with an SQL database to save and load objects for use. There's no point testing the access functions the DB provides, but I wrap such DB functions to serialize/deserialize the objects. I want to be sure I'm writing and reading the right stuff to and from the DB correctly.

It isn't like add/delete, as @snowman mentions. I want to know that the contents I've written are correct, but that requires a well-tested read function. When I read, I want to be sure that my read has correctly created an object equal to what was written; but that requires a well-tested write function.

  • Are you writing your own data store, or using an existing one? If you're using an existing one, assume that it already works. If you're writing your own, this works the same way as writing any other software using TDD does. – Robert Harvey Mar 6 '17 at 17:59
  • 1
  • 1
    While closely related, I do not think this is a duplicate of that particular question. The dupe target is talking about add/delete, this one is read/write. The difference is a read/write dependency likely relies on the contents of objects being read/written, while a simple add/delete test would likely be much simpler: does the object exist or not. – user22815 Mar 6 '17 at 18:33
  • 2
    You can stage a database with data and no write function at all to test a read function. – JeffO Mar 6 '17 at 20:40
7

Start with the Read Function.

  • In the test setup: create the database and add test data. either via migration scripts or from a backup. As this is not your code, it doesn't require a test in TDD

  • In the test: instanciate your repository, point at at your test db and call the Read method. Check that the test data is returned.

Now you have a fully tested read function, you can move to the Write function, which can use the existing Read to verify its own results

  • I guess you could create a DB in memory to speed things up, but that might be too complex. Why not use mocks instead in unit tests? – BЈовић Mar 7 '17 at 16:38
  • 1
    A bit of Devil's Advocate here, but how do you test that the database was created correctly? As OP stated, chicken and egg. – user949300 Mar 7 '17 at 19:01
  • 1
    @Ewan - I absolutely agree that you should not test their DB code. But how do you know that your DB setup code didn't forget an INSERT somewhere or put a wrong value in a column? – user949300 Mar 7 '17 at 19:24
  • 1
    from a pure TDD approach the test IS the requirement. so it logically cant be wrong. obvs in the real world you have to eyeball it – Ewan Mar 7 '17 at 19:32
  • 1
    Quis custodiet ipsos custodes? Or, "Who tests the tests?" :-) I agree with you that in a purist TDD world this would be the horribly tedious and bug-prone (esp. if it were a complicated multiple table structure with 8 JOINS) way to do it. Upvoted. – user949300 Mar 7 '17 at 19:36
6

I often just do a write followed by a read. e.g. (pseudocode)

Foo foo1 = setup some object to write
File tempfile = create a tempfile, possibly in memory 
writeFoo(foo1, tempfile) 
Foo foo2 = readFoo(tempfile) 
assertEquals(foo1, foo2); 
clean-up goes here

Added Later

In addition to this solution being "prgamatic" and "good enough", one could argue that the other solutions test the wrong thing. Testing whether the strings or SQL statements match is not a terrible idea, I've done it myself, but it is testing a side effect, and is fragile. What if you change capitalization, add a field, or update a version number within your data? What if your SQL driver switches the order of calls for efficiency, or your updated XML serializer adds an extra space or changes a schema version?

Now, if you must adhere very strictly to some official spec, then I agree that checking the fine details is appropriate.

  • 1
    Because it's 90% really dense pseudocode? Not sure. Maybe highlight the text and make the code less noisy? – RubberDuck Mar 7 '17 at 10:49
  • 1
    Yes @Ewan. The zealot would frown upon this, however the pragmatic programmer would say "good enough" and move on. – RubberDuck Mar 7 '17 at 13:31
  • 1
    i kinda read the question as.. "assuming i follow TDD like a zealot..." – Ewan Mar 7 '17 at 15:55
  • 1
    my interpretation of the OP and TDD is that your test should be written first and not use both read and write unless one is tested elsewhere as well. – Ewan Mar 7 '17 at 19:30
  • 2
    you are testing, 'read should return what i write' but the requirements are 'read should return data from the db' and 'write should write data to the db' – Ewan Mar 7 '17 at 19:41
3

Don't. Don't unit test I/O. It is a waste of time.

Unit test logic. If there's a lot of logic that you want to test in the I/O code, you should refactor your code to separate the logic of how you do I/O and what I/O you do from the actual business of doing I/O (which is nigh-impossible to test).

To elaborate a bit, if you wanted to test an HTTP server, you should do so through two types of tests: integration tests and unit tests. The unit tests should not interact with I/O at all. That's slow and introduces lots of error conditions that have nothing to do with the correctness of your code. Unit tests shouldn't be subject to the state of your network!

Your code should separate:

  • The logic of determining what information to send
  • The logic of determining which bytes to send in order to send a particular bit of information (how do I encode a response etc. into raw bytes), and
  • The mechanism of actually writing those bytes to a socket.

The first two involve logic and decisions and need unit tests. The last doesn't involve making many if any decisions and can be tested wonderfully using integration testing.

This is just good design in general, actually, but one of the reasons for that is that it makes it easier to test.


Here are some examples:

  • If you are writing code that gets data from a relational database, you can unit test how you map the data returned from relational queries to your application model.
  • If you are writing code that writes data to a relational database, you can unit test which pieces of data you want to write to the database without actually testing the particular SQL queries you use. For example, you might keep two copies of your application state in memory: a copy representing what the database looks like and the working copy. When you want to sync to the database, you need to diff these and write the differences to the database. You can quite easily unit test that diff code.
  • If you are writing code that reads something from a configuration file, you want to test your configuration file format parser, but with strings from your test source file rather than strings you get from disk.
2

I do not know if this a standard practice or not but it works fine for me.

In my non-database read write method implementations i use my own type-specific toString() and fromString() methods as implementation details.

These can be easily tested in isolation:

 assertEquals("<xml><car type='porsche'>....", new Car("porsche").toString());

For the actual read write methods i have one integration test that physically read and write in one test

By the way: Is there something wrong having one test that test read/write together?

  • It may not feel good or "pure", but this is the pragmatic solution. – RubberDuck Mar 6 '17 at 23:53
  • I too like the idea of testing read & write together. Your toString() is a nice pragmatic compromise. – user949300 Mar 7 '17 at 3:56
1

Known data must be formatted in a known manner. The easiest way to implement this is to use a constant string and compare the result, as @k3b described.

You aren't limited to constants though. There may be a number of properties of the written data that you can extract using a different kind of parser, such as regular expressions, or even ad hoc probes looking for features of the data.

As for reading or writing the data, it may be useful to have an in-memory file system that allows you to run your tests without the possibility of interference from other parts of the system. If you don't have access to a good in-memory file system, then use a temporary directory tree.

1

Use dependency injection and mocking.

You don't want to test your SQL driver and you don't want to test if your SQL database is online and set up properly. That would be part of an integration- or system-test. You want to test if your code sends the SQL statements it is supposed to send and if it interprets the responses the way it is supposed to.

So when you have a method/class which is supposed to do something with a database, don't have it get that database connection by itself. Change it so that the object which represents the database connection is passed to it.

In your production code, pass the actual database object.

In your unit tests, pass a mock object which just behaves like an actual database doesn't actually contact a database server. Just have it check if it receives the SQL statements it is supposed to receive and then responds with hardcoded responses.

This way you can test your database abstraction layer without even needing an actual database.

  • Devil's Advocate: How do you know which SQL statements it is "supposed to receive"? What if the DB driver optimizes the order from what appears in the code? – user949300 Mar 7 '17 at 19:03
  • @user949300 A database mock object usually replaces the database driver. – Philipp Mar 7 '17 at 19:07
  • when you are testing a repository theres no point injecting a mocked database client. You have to test that your code runs sql which works on the database. otherwise you end uo just testing your mock – Ewan Mar 7 '17 at 19:13
  • @Ewan That's not what unit testing is about. A unit test tests one unit of code, isolated from the rest of the world. You are not testing the interactions between components, like your code and the database. That's what integration testing is for. – Philipp Mar 7 '17 at 21:05
  • yes. im saying theres no point unit testing a db repository. integration test is the only thing worth doing – Ewan Mar 7 '17 at 21:07
0

If you're using an object relational mapper, there is typically an associated library that can be used to test that your mappings work correctly by creating an aggregate, persisting it, and reloading it from a fresh session, followed by verification of the state against the original object.

NHibernate offers Persistence Specification Testing. It can be configured to work against an in-memory store for fast unit tests.

If you follow the simplest version of the Repository and Unit of Work patterns, and test all of your mappings, you can count on things pretty much working.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.