14

So I'm trying to make my Unit Tests as by-the-book as possible, but it becomes troublesome when I'm testing some simple Add/Delete methods.

For the add method, I basically have to create a dummy object and add it, then after the test is successful, I have to delete the dummy object.

And for the delete test, I obviously have to create a dummy object so that I can delete it.

As you can see if one test fails, the other will fail too, as they're both kinda needed.

Same with a system where you would need to write a test that "Cancels an Order"...well some dummy order would be needed to cancel first, doesn't this go against the guidelines of unit testing?

How are cases like this meant to be handled?

1

7 Answers 7

13

Well, there's nothing wrong with what you're doing. Multiple tests can cover the same code; it just means that one problem will cause several tests to fail. What you want to avoid is tests that depend on the results of other tests. I.e., your delete test depends on the add test having run, and thus if you run your delete test BEFORE your add test, it will fail. To avoid that problem, make sure that you have a "blank slate" at the start of each test, so that what happens in a given test can't affect subsequent tests.

A great way to do that is to run the tests against an in-memory database.

In your add test, create an empty database, add the object, and assert that it has indeed been added.

In your delete test, create the database with the object you'll be deleting already in it. Delete the object, and assert that it has been deleted.

Blow away the database in your tear-down code.

1
  • The in-memory database is just fast (in memory) and simple (in process). You could do this with any data store. Commented Jun 18, 2015 at 5:19
3

Use transactions.

If you are using a database that supports transactions, then run each test in a transaction. Rollback the transaction at the end of the test. Then each test will leave the database unchanged.

Yes, to cancel an order you will first have to create one. That's fine. The test will first create an order, then cancel it, then verify that the order is cancelled.

1
  • Love this idea. Implemented it with great effect today.
    – pim
    Commented May 7, 2018 at 20:26
3

You're doing it fine. The one and only fundamental principle of unit testing is to cover every code path you have, so you can be confident that your code is doing what it's supposed to do, and keeps doing it after changes and refactorings. Keeping unit tests small, simple and single-purpose is a worthwhile goal, but it is not fundamental. Having a test that calls two relate methods of your API is not in itself questionable, in fact, as you point out, it is often necessary. The drawback of having redundant tests is just that they take more time to write, but as almost everything in development, this is a trade-off you have to make all the time, and the best solution is almost never one of the extreme points.

3

Software testing techniques are extremely varied, and the more you educate yourself about them, you are going to begin seeing lots of different (and sometimes conflicting) guidance. There is no single 'book' to go by.

I think you are in a situation where you have seen some guidance for unit tests that say things like

  • Each test should be stand-alone, and not be affected by other tests
  • Each unit test should test one thing, and only one thing
  • Unit tests should not hit the database

and so on. And all of those are right, depending on how you define 'unit test'.

I would define a 'unit test' as something like: "a test that exercises one piece of functionality for one unit of code, isolated from other dependent components".

Under that definition, what you are doing (if it requires adding a record to a database before you can run the test) is not a 'unit test' at all, but more of what is commonly called an 'integration test'. (A true unit test, by my definition, won't hit the database, so you won't need to add a record before deleting it.)

An integration test will exercise functionality that uses multiple components (such as a user interface and a database), and the guidance that would apply to unit tests does not necessarily apply to integration tests.

As others have mentioned in their answers, what you are doing is not necessarily wrong even if you do things contrary to some unit test guidance. Instead, try to reason about what you are really testing in each test method, and if you find that you need multiple components to satisfy your test, and some components require pre-configuration, then go ahead and do it.

But most of all, understand that there are many kinds of software tests (unit tests, system tests, integration tests, exploratory tests, etc.), and don't try to apply the guidance of one type to all of the others.

3
  • So are you saying that you can't unit test deleting from the database?
    – ChrisF
    Commented Feb 22, 2012 at 23:32
  • If you're hitting the database, it's (by definition) an integration test, not a unit test. So, in that sense, no. You cannot 'unit test' deleting from a database. What you can unit test is that when the code you are testing is asked to delete some data, it interacts with the data access module correctly.
    – Eric King
    Commented Feb 23, 2012 at 0:24
  • But the point is, some folks may define 'unit test' differently, so we must be careful when applying 'unit test' guidance, as the guidance may not apply in the way we think it does.
    – Eric King
    Commented Feb 23, 2012 at 0:28
1

This is exactly why one of the other guidelines is to use interfaces. If you method takes an object that implements an interface instead of a specific class implementation, you can create a class that does not depend on the rest of the code base.

Another alternative is to use a mocking framework. These allow you to easily create these type of dummy objects that can be passed in to the method you are testing. It is possible that you might have to create some stub implementations for the dummy class, but it still creates a separation from the actual implementation and what the test is concerned with.

1

As you can see if one test fails, the other will fail too, as they're both kinda needed.

So?

... doesn't this go against the guidelines of unit testing?

No.

How are cases like this meant to be handled?

Several tests can independent and all fail because of the same bug. That's actually normal. A lot of tests may -- indirectly -- test some common functionality. And all fail when the common functionality is broken. Nothing wrong with it.

Unit tests are defined as classes precisely so they can easily share code, like a common dummy record used to test update and delete.

1

You can use a mock framework or use an 'environment' with an in-memory database. The last one is a class where you can create everything you need to make the test pass, before the test runs.

I prefer the last one - the users can help you input some data so your tests become closest to the real world.

1
  • True - but you're not actually testing the real database connection here. Unless you assume that that's always going to work - but assumptions are dangerous.
    – ChrisF
    Commented Feb 22, 2012 at 23:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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