views:

203

answers:

11

I have an application that returns data dependent on the time specified, I can specify days, months or years. The issue is that if I were to run the application today and ask it to return data from 1 month ago and in 3 months time I were to ask the application to return data from that date for the previous 1 month (i.e. 1 month from date) the results will obviously be different. Due to the dynamic nature of this I am finding it difficult to create unit tests because I have to change the date depending on when I am running the tests. Does this symbolize bad design or is this an exception case?

+2  A: 

It is a problem case - but not necessarily bad design.

For the unit testing, you need to provide fake modules to handle the generation of time and time-based data. This is similar to what you need to do with handling databases.

(And I'm still not sure how people like me, who provide DBMS, are supposed to do unit testing when the unit testing people all assume "thou shalt fake out the database", but that's a separate discussion.)

Jonathan Leffler
If your unit is pulling the data or testing stored procedure in the database you don't mock it out. If you are testing logic that uses that data you should mock the database. If these pieces are mixed you are violating single responsibility. And of course there's still a need for integration testing.
Rob Spieldenner
@Rob: I actually work on the internals of the DBMS. It is hard to test it by mocking it out. It just requires a different mind-set. I should probably delete that comment - it is misleading to that vast majority that does not implement the internals of a DBMS.
Jonathan Leffler
A: 

Maybe. This is surely a symptom of a coupled design. BTW this is a very good and complicated question.

I hope to read a better answer than this one.

dfa
I don't see what's coupled about it. The request "Give me last month's data" is certainly simple and cohesive, and it doesn't need to have any details of the time functionality other than a very simple API. Coupling is not the same as dependency.
David Thornley
coupled is the same of "hidden dependencies" in this case
dfa
What's hidden about the dependency in this case? The module needs to access the time and the database. That's pretty obvious. Either can be accessed using a well-defined API, so there doesn't need to be any overcoupling there. Are you referring to dependencies in testing?
David Thornley
probably "today" is not a parameter but a variable assigned within the module, and cannot be customized by external code. Something like 'select items from data where date=now()'.
dfa
Seriously, if a module's internal variables can be customized by external code, you've got a real, actual, serious coupling problem. Are you saying that internal variables that are really internal are coupling problems? Are you saying that transforming input parameters (like "last month" to "May 2009") for further processing is a coupling issue? I'm genuinely puzzled.
David Thornley
in order to test a module you can set a parameter via a protected method if you are worried about it.
dfa
+2  A: 

One way round your specific problem would be to encapsulate the time itself in a separate object and then in your testing you could force this object to return some known time.

Phil Booth
+1  A: 

You could rewrite it so the piece that get's the current date and the piece that parses the date are separate. In other words you get the date in one function and pass it's result to another function that parses it. That way you can test the parsing by passing in a fixed date.

mamboking
+5  A: 

It's not necessarily a bad design, but the software hasn't been designed for easy testing, and design for easy testing is considered by many people to be a necessary aspect of a good design.

If the code could be modified to find data from 1 month of a specified date, the production code could easily pass the current date, and the test code could use a fixed date.

PeterAllenWebb
A: 

This is a common problem, especially when it comes to creating reports. I see three easy ways of going about this:

1) Roll back/forward the system's clock.

2) Update the date on the test data to always be within your range (ie update db set date = now()).

3) Run a query on a clean system, then apply your changes, and run the query again. This is probably the easiest way.

Mike DeMaria
A: 

After re-reading your question, I'm not sure what you're trying to test.

  1. Are you trying to test that the database returns the correct data for the query you are running?
  2. Are you trying to test that your code generates the appropriate query?
  3. Are you trying to test that your code processes the returned data appropriately?

If #1, then maybe you could use an in-memory database that you could initialize in your unit-test with fixed data. I've used H2 before for that purpose. Search for embedded databases.

If #2 or #3, you should be able fixture the data for the test and get back known results. If the sql generation or result processing is embedded then you can use mocking to mock-out the receiving methods.

For example, I usually have a class called QueryRunner that accepts the SQL that I want to run and a class that it will call to process the result set. That way I can mock QueryRunner and see if it is called with the SQL I was expecting and if it was called an appropriate number of times. Much easier than trying to actually mock the JDBC classes.

mamboking
A: 

If the test data changes, it means it's kind of "live" database. Why would you run your test on such a database ? If the tests results are not the same between two runs, it goes against the goal of a test, an unit test should be repeatable and reliable, if you're rewriting it every couple of months it's neither ...

You probably need a "static" database for your tests, once you're added all the data for the different scenarios/uses cases you need to test, you could do a backup and you'll restore it whenever the data gets "corrupted". Or even better, you have scripts that will insert all those data into an empty database before running the test suite.

Billy
I'm running my tests using a test DB. But lets look at it this way, if the current month is February and I want data for the previous month it will return January's data which will be, say, 5 records. Now if I want to run my test in 6 months time I still want it to return January's data so in order for that to happen I will have to manually set the date in my unit tests to February but that can't happen because my methods take in the current date.
Draco
Hmmm ... I think a small refactoring will fix this easily.Say you currently have a method getPreviousMonthsData(), that inside the method gets the current date/time and uses NOW in order to calculate the previous month.If you extract this NOW and make it a parameter and make it getPreviousMonthsData( startingMonth ), all you have to do is call this new method with the NOW parameter when testing today, and with the February parameter when testing in June.
Billy
A: 

I think, if it is truly unit testing, it is a bad design. Your unit/class has a dependency on time, if there is no way to mock out the time then the "unit" isn't a unit. To unit test you need to be able to influence all of the external dependencies that affect the flow of control. In practice this means that there is a class that is responsible for selecting the information you need that accepts some delegate that's responsible for the time. This might be a Calendar object or something similar.

If it is a complete module that you are testing, perhaps you are not unit testing. The same principle carries though, if time influences the output radically then date should be an optional input not only for testing, but because more often than not systems need to be run for a specific date. For some this might be speculative: but it is better practice not to refer to global state directly in modules deep in the system but to pass this state around. The difficulty in testing can turn into a difficulty in maintenance pretty easily. All that would need to happen would be for a customer to suggest a command to, say process batches (or whatever you're doing, for a particular day again because the system crashed. Burying the date deep, and possible multiple-times in the system when it is critical for the selecting logic can lead to greater maintenance later.

If your system has a great number of dependencies (is complex) you might want to consider a Container to manage Dependency Injection. These containers, often called IoC Containers allow the system to be written as loosely coupled objects that and these are "wired" together by the container through configuration of some kind.

andygavin
A: 

What is to prevent your test from supplying the data for your code to test against?

This can be achieved in two ways, either use some other method to programatically place the data into your data source or mock your data source.

Personally I would be tempted to change my code so that the datasource was accessed via an interface derived class. That way you can mock your datasource and supply known data which isn't dependent on time.

If you are using Visual Studio then you can use it to automatically create an interface class for your datasource object and modify it to use that interface as a base class.

ChrisBD
+1  A: 

What you are doing is not a unit test. Unit tests should exercise a small "unit" of your code and your code only. By incorporating the system time, you are also testing the environment in which you are currently running. That is a system test. Unit tests are very effective tools to make sure that the code you wrote was written correctly, but it helps a lot of you write your code in "testable" manner.

There are a few tricks that are easy to learn but difficult to master that will help you write testable code. They generally all follow the same pattern of creating what they call "seams" in your code and then injecting "stubs" or "mock objects" into those seams at test time.

The first important thing to figure out is where your seams go. This isn't that hard. Basically, any time you construct a new object, that's a good place for a seam. A prerequisite for this rule is that you have a pretty decent object-oriented design to begin with. (The guys over at the Google Testing Blog argue that you cannot unit test imperative code because you can't do dependency injection.) The other good place for a seam is any time you talk to an external data source, like the operating system, the file system, a database, the Internet, etc. This is what you are doing.

You need the system time. That's where your seam should go. I recommend you get a good book on this for a full treatment of all your options here, but here's one example of what you could do. There are at least 2 or 3 other ways to "inject your dependency" on the current system time. I'll use Python for pseudo-code, but it works in any OO-language:

class MyClass(object):
    def _get_current_time(self):
        '''This is a test seam'''
        return datetime.datetime.now()

    def age(self):
        return self._get_current_time() - self._birthday

Then in your test code, do this:

class FakeMyClass(MyClass):
    def __init__(self, test_time, *args, **kwargs):
        self._test_time = test_time
        MyClass.__init__(self, *args, **kwargs)

    def _get_current_time(self)
        return self._test_time

Now, if you test with FakeMyClass, you can inject whatever system time you want:

myclass = FakeMyClass(t)
self.assertEqual(myclass.age(), expected_age)

Again, this is a pretty big topic, so I recommend getting a good book.

Ryan Bright