4

Currently I needed to write SecondClass and decided to write using this kind of solution because otherwise it wouldn't be testable.

@Component
public class FirstClass {

    public void doStuff() {
        System.out.println("First Class stuff!");
    }

}

@Component
public class SecondClass {

    private final Random random;
    private final FirstClass firstClass;

    @Autowired
    public SecondClass(FirstClass firstClass) {
        this(new Random(), firstClass);
    }

    public SecondClass(Random random, FirstClass firstClass) {
        this.random = random;
        this.firstClass = firstClass;
    }

    public void doOtherStuff() {
        firstClass.doStuff();
        System.out.println("Second Class stuff " + random.nextInt(10));
    }

}

My colleagues didn't liked my way of solving it and preferred implementation of SecondClass like this:

@Component
public class SecondClass {

    private Random random;
    private final FirstClass firstClass;

    @Autowired
    public SecondClass(FirstClass firstClass) {
        this.random = new Random();
        this.firstClass = firstClass;
    }

    public void doOtherStuff() {
        firstClass.doStuff();
        System.out.println("Second Class stuff " + random.nextInt(10));
    }

    public void setRandom(Random random) {
        this.random = random;
    }

}

I disagree with this kind of solution because I think that Random is the necessary part of this class, it wont be changed in the run time and that setter is only necessary for testing purposes and that's why I prefer solution with two constructors.

We also came up with this kind of constructor:

@Autowired(required = false)
public SecondClass(FirstClasss firstClass, Random random) {
    this.random = (random == null ? new Random() : random)
    ...
}

But there is actually more components injected in the constructor so it would be preferable if all of them would have been required.

I'm interested if anybody here had this kind of experience before? What do you think about this case and if there is any better way of solving this problem?

GROX13
  • 4,605
  • 4
  • 27
  • 41
  • In my working experience I used quite a lot multiple constructor and `final` fields was mandatory (obviously where it has reason to be) – Paolo Forgia Aug 12 '16 at 14:47
  • @PaoloForgia could you tell us more about you experiences what has gone wrong and when this kind of multiple constructors really helped. – GROX13 Aug 12 '16 at 15:36
  • http://stackoverflow.com/questions/34571/how-to-test-a-class-that-has-private-methods-fields-or-inner-classes – Koos Gadellaa Aug 12 '16 at 23:02
  • This seems to me like XY problem (http://xyproblem.info/). Do you just want to add the second constructor? Or are you trying to solve some other problem by adding this constructor? Maybe you want to test the `SecondClass` and you need the `Random` to generate some constant values to be able to deal with them in the test? – Alexander Arutinyants Aug 15 '16 at 15:04

3 Answers3

2

Your problems spring from having final fields. Follow Uncle Bob, and feel free to make them protected so you can write to them in your test class. You're not writing an interface, you're writing code, so access modifiers don't really matter that much, and protected is fine since it allows package-access.

Or you could look at setting them yourself in your testcase by hard-wiring them, say ReflectionUtils.setField

Koos Gadellaa
  • 1,220
  • 7
  • 17
  • Yes we also thought about reflection but in my opinion it's just bypassing of that problem not solving it. I think it's almost same as additional setter in the class. – GROX13 Aug 12 '16 at 15:45
  • 1
    I've never seen anybody have a problem with reflection in a test class (unless you're overdoing it, of course, but...) Your problem is that you want random inside your class, shielded off. Businesswise, if that is the best decision, that's the way it should be. Testing is a secondary concern, and modifying your code just for testcases isn't necessarily bad, but if you can prevent it with some light hacking/reflection, most people have no problem at all. – Koos Gadellaa Aug 12 '16 at 17:52
  • No I'm not saying that I'll have problem with reflection or any other way of solution I'm just interested what would be "by the book" way of solving this kind of problem. Because I was always warned that creating new objects inside class without possibility of dependency injection would be bad idea but that's only the words this kind of stuff happens in real projects and show should I act if I want to maintain good oo architecture. – GROX13 Aug 12 '16 at 17:59
  • Well, if you *feel* your tests should not use reflection (why not? Performance is not an issue? Security hacks are neither, and it's clear?), you are only left with modifying your 'public interface'. It then becomes the question what is clearest for those using the interface. Is it an additional constructor argument, a separate setter, or making a field accessible for your test. All are 'okay', although I'd lean towards the one who is at least a bit out of the public eye - make your field accessible by making it `protected`. You're having to pick between two *guidelines*, not *rules* – Koos Gadellaa Aug 12 '16 at 20:17
  • -1 because protected not-final fields are never necessary in a well designed abstraction, because they make a poor encapsulation. Non-final instance fields must be always **private**, so that the abstraction controls if they should be modified and how, according to the [open/closed principle](https://en.wikipedia.org/wiki/Open/closed_principle). – Little Santi Aug 12 '16 at 21:00
  • Nothing but package access (and possible subclasses) can modify, so it is closed for modification by outside classes. Usually you don't need inheritance (...prefer composition...), so you lose nothing. You're not designing an API to be used by malicious third parties, so in effect, it *is* closed. – Koos Gadellaa Aug 12 '16 at 22:21
  • @KoosGadellaa No: If your class is not final it might be extended by third parties to modify its protected members in a way that is out of your control. And yes: Malicious third parties *do* use APIs too. You must always take security in account when desingning. Read "Effective Java" by Joshua Bloch. – Little Santi Aug 13 '16 at 08:56
  • Security is always a tradeoff, and needs to be given it's due diligence, not tinfoil-hat crazy conspiracy scared stance. When was the last hack made because some programmer did not use final classes. Tell me when the last Java hack was. It is all protocol level or buffer overflows. With the advent of microservices and cloud computing, all those API's are covered in REST or SOAP, and nothing will run on the same VM. So you'll 'always' know that such security aspects are waste of time. And with that, you've taken it into account. Having an extra constructor argument is worse then the cure. 99.9% – Koos Gadellaa Aug 13 '16 at 11:39
  • @KoosGadellaa Sorry to read your line saying that security is no more than a low-level or network matter. Look: Secutity covers every single step to ensure that the application serves to what it is expected to, and no more, no less. When a single, untententional bug produces an infinite loop, a NPE, or an OutOfMemoryError, security is being infringed. And those things usually occur because of a poor design. I recommend you to read about **robustness** in code. – Little Santi Aug 13 '16 at 12:46
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/120864/discussion-between-koos-gadellaa-and-little-santi). – Koos Gadellaa Aug 13 '16 at 13:14
2

If you declare two constructors in SecondClass and you use one of these only in unit-test, it means that the constructor for unit-test could be replaced by a more relevant trick for your unit-test such as reflection to set the value of the random field.
You should not open your API just for unit testing when you can avoid.
You should open the your API just for unit testing when you have not the choice.

If the random field is read-only and a known value as soon as the SecondClass is instantiated, you should have a single constructor for SecondClass. And the random field should be final and valued in the constructor of SecondClass.

davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • I haven't changed that term from my problem `Random` is really necessary in the `doTheStuff` method and yes it's read only never need to access it anywhere else but `doTheStuff` method. I dislike instantiating random in single constructor just because I think that it's just violation of OOP principles and if I'll instantiate it in constructor class will just loose flexibility. But this is only my opinion. – GROX13 Aug 12 '16 at 15:34
  • -1 because unit tests represent _whatever a real client code might do_. So if a constructor is necessary for unit testing, it is actually useful for real client code. For that reason, reflection is not a good choice for unit testing: The unit test should check if the public tested API is complete and well desinged; If reflection is necessary to check it, then the API is uncomplete. – Little Santi Aug 12 '16 at 20:58
  • That is totally true for a stateless class. That is totally not the given problem. The result is not determined by the client, but by the state *and* the client. That does not mean that the state is *public*, and should be modifyable as such. To test for the variability of inherent state, you need to control that state. That means either by *having* access to it, or gaining access to it through reflection. But for stateful objects, usually that state is closed, so access should not be possible. – Koos Gadellaa Aug 12 '16 at 22:31
  • @Koos I mostly agree. But the public-ness is not in the state itself, but in the _operations_ to the state: A component might have public access to _read_ its state and no access to write it, and that's OK. Actually, the state should be _always_ closed to be modified, and let the designer decide if it should be open for reading. – Little Santi Aug 13 '16 at 12:37
  • @LittleSanti But do you design for testing, or for security? Both have differing requirements. The question here is about exactly that. Do you allow the state to be modified (through constructors, setters) so proper testing can be done, or do you allow the state to be closed, and only open for reading, so security is maintained? I personally don't see how these two requirements can be aligned and resolved. – Koos Gadellaa Aug 13 '16 at 13:13
1

You are absolutely right, my friend. Your approach is the right one, because, according to Object Oriented design, an object must be initialized to a valid state after any of its constructors is executed. So, if the random field is necessary, it must be initialized in all constructors. Moreover, if it shall not be changed later, it must be final (and have no setters). So the second solution is not right because it violates this principle.

I don't like the third solution either because it can be foolish for the client code: While the client assumes that a null is being set as the input value, actually another (not-controlled) value is used instead.

So if you need that constructor for the SecondClass to be testeable, go and add it. Because if a class is not testeable, it is also not executable. Testing is the right way to ensure that a class might be usable.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
Little Santi
  • 8,563
  • 2
  • 18
  • 46
  • I disagree. A unit test doesn't represent what the client does with the application. It tests in an isolated way an unitary behavior of your component. But if you unit-test its component in a different way they are used by the client, you are cheating and you test code doesn't reflect the real behavior. Besides, we should not open an API just for an unit test if not used in production. It makes a non sense. – davidxxx Aug 12 '16 at 21:17
  • "A unit test doesn't represent what the client does with the application"??? How do you ensure that your component is useful and works as it is expected to, then? – Little Santi Aug 12 '16 at 21:20
  • I confirm. An integration test represents what the client does with the application. You mix different concepts that are not at the same level : component and application. – davidxxx Aug 12 '16 at 21:23
  • Note that I'm always referring to what client **code** shall do. I'm not talking about the "human" client or user. Maybe you have misunderstood me. Still, you have not answered my question: How do you ensure your component's behaviour is what is expected to be? – Little Santi Aug 12 '16 at 21:25
  • I said client and not client class or client code. You unit test the code of course.You can read my answer to understand it. It is balanced. "You should not open your API just for unit testing when you can avoid. You should open the your API just for unit testing when you have not the choice." All is not white or black. – davidxxx Aug 12 '16 at 21:31
  • @davidhxxx Just for the sake of the forum's organization, comments about your answer should be below your answer, not mine, please. – Little Santi Aug 12 '16 at 21:32
  • It's just a question of shape. I changed it. – davidxxx Aug 12 '16 at 21:35
  • If a class is not testable, it is also not executable. However, it does not mention UNIT tests. If a class is not unit testable, it is very possible to still be tested in integration tests. – Koos Gadellaa Aug 12 '16 at 22:40
  • @Koos Gadella It might, but we should always stick to good practices whenever we can. And in this case we can. – Little Santi Aug 13 '16 at 00:12