jeudi 25 février 2016

Unit Testing: How to check non-constant arguments without forcing implementation

I'm writing a unit test for a class which needs to set an alarm that will expire 24 hours from now.

I'm trying to write the test in a way which is as implementation-agnostic as possible, so that it will only break when it really fails to do its job, and not break just because the underlying implementation changes.

So I've mocked only the most basic calls to dependencies, such as getting a reference to the alarm manager, creating pending intents, etc.

However, I'm running into an issue when I try to confirm that the call to AlarmManager.set() is actually scheduled for 24 hours from now. The second argument to AlarmManager.set() is supposed to be the time at which the alarm will expire, and of course the class under test must calculate that. The problem is that there are multiple ways to do that (using System.currentTimeMillis() + 24*60*60000, or GregorianCalendar alarmTime = new GregorianCalendar(); alarmTime.add(HOUR_OF_DAY, 24), or whatever.

I could try to mock the calls to one of the methods used to calculate the alarm expiration time, so that I can control and calculate a precise expectation for the time expiration argument. But any method I mock will force a particular implementation in the real class.

I tried to get around it by allowing a fuzzy check of the alarm time - that is, using my test code to estimate the expected alarm time, then using an argument matcher with some time window to allow for execution time. This creates an obvious race condition which is undesirable. I"m currently measuring about a 25 to 30 ms difference between the estimated alarm time and the actual alarm time.

long alarmTime = System.currentTimeMillis() + 24*60*60000;
mMockAlarmManager.set(EasyMock.eq(AlarmManager.RTC),
                      EasyMock.and(EasyMock.geq(alarmTime), //<-- Lower Bound
                      EasyMock.leq(alarmTime+300)),         //<--Upper Bound
                      EasyMock.eq(mMockPendingIntent));

Of course I can play with the timewindow to make the test pass, but this feels wrong to me. In this particular case, I can set the window pretty wide and be ok, because my program doesn't care if the alarm time is exact - even a couple of minutes is ok. But this is just a simple example of a larger problem that I'm sure I will encounter in future unit tests. What would I do if the time needs to be more exact? What would I do if the argument isn't a time, but something else that needs to be dynamically calculated and can be done in many different ways that should all pass the test?

Is there a better way to check that non-constant arguments are computed correctly without requiring mocks which force the class under test to use a particular implementation?

Aucun commentaire:

Enregistrer un commentaire