K
K
KaminskyIlya2015-02-10 17:12:57
Software testing
KaminskyIlya, 2015-02-10 17:12:57

How to perform Dependency Injection of local variables?

Good afternoon everyone!
Suppose we have a "library" function like this:

// Возвращает полный возраст человека на текущий день
public static int getYearsOld(Date birthday)
{
    // Эту переменную необходимо мокировать
    Date today = new Date();

    return getYear(today) - getYear(birthday);
}

You need to write a test for it. It seems to be easy to do this:
@Test
public void testGetYearsOld()
{
    assertEquals( 23, getYearsOld(new SimpleDateFormat("dd-MM-yyyy").parse("15-07-1991")) );
}

But in fact, such a test after debugging will fail in a few months.
To avoid this, we must dynamically change either the expected constant (23) or the date of birth ("15-07-1991"). At the same time, we will explicitly / implicitly reproduce the tested algorithm and we can make a mistake twice: in the implementation and in the test. Especially if the test was written by the same programmer.
It would not be bad if we could "slip" the desired date into the body of the function. In other words, we need a dependency injection for a local variable.
Usually in such situations, it is suggested to resort to IoC and DI as follows: either pass the variable through the argument of the function itself, or turn the local variable into a class field. And in the question of how to initialize this variable, then there are options: set as a setter, in the constructor, through a reflection.
But this approach to the implementation of the function causes some problems. It turns out that in order to just conduct a test, I must:
1. Raise the level of a local variable, thereby making it available to all members of the class. And if this variable is not used anywhere else? Why then were local variables made at all? What if the function is static at all?
2. Break the encapsulation and reveal the details of the implementation of the function, moving its "guts" to a higher level, making it available to everyone in the class.
3. Save the value of a local variable, even after it is no longer needed. Instead of it becomes necessary at once after an exit from function. Those. the garbage collector will not free the memory it occupies as long as an instance of the class exists. And if it is a singleton that lives while the application lives?
4. In case of adding a setter, constructor or composition of the function argument, also change the public API.
And the question remains: what to do with more than 9000 of the same local variables used in other functions. Take everything out into the fields? What if they have the same name? Reuse them? But what about multithreading?
In general, such approaches to verification are, to put it mildly, not very good.
There is, however, another similar option: passing a special factory to the class that generates values ​​for local variables. But somehow it’s not that ...
Ah, if only it were possible to write like this:
public static int getYearsOld(Date birthday)
{
    @InjectForTest(name = "Date1")
    Date today = new Date();

    return getYear(today) - getYear(birthday);
}

@Test
public void testGetYearsOld()
{
    MockContext ctx = getContext();
    ctx.put( "Date1", new Date() );

    assertEquals( ..., getYearsOld(...) );
}

Does anyone know other options? Or did I approach the decision from the wrong side?
I want to warn you that this is not a specific example, but in general. A function can have many such local variables (although it will be no more than 10-15 lines - i.e. refactoring like "select a method" will not work).

Answer the question

In order to leave comments, you need to log in

1 answer(s)
V
Victor Alenkov, 2015-02-10
@KaminskyIlya

we make a mock for the Date class and that's it. an example can be seen here

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question