Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I've this really simple program written in C with ncurses that's basically a front-end to sqlite3. I would like to implement TDD to continue the development and have found a nice C unit framework for this.

However I'm totally stuck on how to implement it.

Take this case for example: A user types a letter 'l' that is captured by ncurses getch(), and then an sqlite3 query is run that for every row calls a callback function. This callback function prints stuff to the screen via ncurses.

So the obvious way to fully test this is to simulate a keyboard and a terminal and make sure that the output is the expected. However this sounds too complicated.

I was thinking about adding an abstraction layer between the database and the UI so that the callback function will populate a list of entries and that list will later be printed. In that case I would be able to check if that list contains the expected values. However, why would I struggle with a data structure and lists in my program when sqlite3 already does this?

For example, if the user wants to see the list sorted in some other way, it would be expensive to throw away the list and repopulate it. I would need to sort the list, but why should I implement sorting when sqlite3 already has that? Using my orginal design I could just do an other query sorted differently.

Previously I've only done TDD with command line applications, and there it's really easy to just compare the output with what I'm expected. An other way would be to add CLI interface to the program and wrap a test program around the CLI to test everything. (The way git.git does with it's test-framework).

So the question is, how to add testing to a tightly integrated database/UI.

share|improve this question
    
What you are doing is integration or system testing, not TDD. If you had done TDD from the start, your database and UI wouldn't be so tightly coupled. –  Frank Nov 4 '13 at 6:10
1  
The problem is that I fail to see the point of having them more separete. I mean even if I do, what's the point of testing if a sql-query runs the way it should? Isn't that a too easy test? –  iveqy Nov 4 '13 at 6:12
    
And yes, it's a 2 year old program with 1000 lines of code. So it's not developed with TDD in mind so far. But I'm willingly to rewrite it if that's what's demanded do make TDD work. –  iveqy Nov 4 '13 at 6:13
    
read up on the terms coupling and cohesion. There are so many reasons, why it's a good idea to perform this separation, but they are not answers to this question. –  Frank Nov 4 '13 at 7:05
1  
@Frank: I think iveqy has a point: decoupling the application logic from the database is of course a good thing, but it feels a bit awkward to re-implement certain functions in your mock objects if they are already available in the DB. –  Giorgio Nov 4 '13 at 9:46

1 Answer 1

up vote 2 down vote accepted

Let's try an answer then. From what I understand we actually have two questions here:

  1. How can you apply TDD when adding new features to a codebase that has not been developed with TDD itself?

  2. How can you add tests for tightly coupled code?

Answer to question 1:

First of all, test-driven development is used for new code. By the very name of it TDD cannot be used to add tests to already existing code, because obviously, the development would not be driven by tests in that way. So TDD is out for question 2.

For question 1, however, you run into problems as soon, as your code interacts with the existing codebase in some way. Any completely new modules/classes can easily be developed with a TDD approach of course. When you have an interface to the existing code, you can still develop with a TDD approach, but that requires you to provide a mock or stub for the interface. This does not necessarily mean that you re-implement behavior.

A mock/stub need not have the full behavior of your original object. For example, it is perfectly fine to have a database stub that returns a constant list of thingies for every query you give it. It is sufficient to test whatever behavior you want to test that needs to work with that return list. You are after all not testing the database layer itself at that moment.

The problem gets more difficult, when you have to add code to existing classes/modules within a method, or maybe add calls to a new method. In those cases you suffer from the lack of tests of the original codebase and it can become troublesome to write tests. Mostly, this should be dealt with by improving the original codebase - see answer to question 2.

Answer to question 2:

When you have an existing codebase with high coupling and you want to test it, then first of all you should have a good reason to do so. It's a nice exercise, but does not per se add any value to your product, so you need some reason, where the existence of these tests gives you a future benefit.

Here's a bunch of typical reasons, why you want to do this:

  • The codebase is legacy and you are assigned to its maintenance. The tests will give you a better understand of what the code does, and provides a safety net against introducing errors when you make changes in the future.

  • The codebase will be significantly changed to adapt the product for new features. In that case you will be working with the code for a while, which means you will reap the benefits of the created tests during your ongoing development.

  • The codebase is subject to be replaced. For example, your database layer may be exchanged for another one. In that case, the tests will be used as a safety net to detect if the new database layer behaves differently.

Once you know why you want to write the tests, you can also focus your tests more thoroughly. If you want to continue development on the source, you will need fine-grained unit tests. If instead you just want to have tests against the whole module, any kind of integration test will suffice.

From what I can guess about your intentions, I'd say that your most likely reason seems to be that you have a legacy codebase that you want to continue working upon. In that case, the tests are step one and provide you a foundation upon which you can perform refactoring operations. Given the tests, you can rather quickly work on the coupling problems and improve the code design to make it more robust for future development.

Finally, to address your example about sorting lists: You need to think about which module is responsible for the sort operation, and whether or not it makes sense for you to test it. If you delegate sorting to the database layer (via sqlite queries), then you only need to test it if you plan to use the test against a new database layer implementation, in which you suspect sorting to behave differently. However, you need not re-implement any sorting to test your UI-side, because the UI itself has no sorting behavior whatsoever that could be tested. All you can test is that the UI respects the order of the elements it is given. In other words, there is no coupling between the database and the UI with respect to sorting. The database is responsible for sorting and just gives an ordered list to the UI. And the UI is only responsible for representing an ordered list of elements - no matter which order that is. Of course, you can also test your controller such that it does in fact call the correct query to create the requested sorting order.

It all comes down to the point that you want loose coupling: it forces you to define proper interfaces between your components, it allows you to test each individual component thoroughly and without needing any of the other component implementations.

share|improve this answer
    
Thanks for a very good answer. It leaves me with an other question though. Why would I want a loose coupling. The obvious answer would be "to be able to test your code", but loose coupling in this case would (as I see it) expand memory and CPU usage. And that's not wanted. The ode is highly modularized witch would be enough for maintainability. –  iveqy Nov 4 '13 at 11:17

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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