Generally, one would better be judicious about using protected access at all. The reasons for that are laid out in answers to prior questions over here: Why is Clean Code suggesting avoiding protected variables?
As for using it the way you think of here - weakening access limitation because it feels like more comfortable to test - it looks like a terribly bad idea.
The "right option" when you test functionality covered by private method is to leave its modifier alone and figure testing approach based on assumption that method is and will stay private.
There could be many cases and reasons when one needs to redesign code for testability. Various difficulties in writing unit tests often serve as good indication for such a need. But, "oh, that method is private" is not one of those indicative difficulties.
I wrote an explanation for this in an answer to prior question as a side note because that other question and answer were focused on different matters. But for the question you asked, it seems to apply fully and directly, so I will simply copy that part over here.
<rant "I think I heard enough whining. Guess it's about time to say loud and clear...">
Private methods are beneficial to unit testing.
Note below assumes that you are familiar with code coverage. If not, take a time to learn, since it's quite useful to those interested in unit testing and in testing at all.
All right, so I've got that private method and unit tests, and coverage analysis telling me that there's a gap, my private method isn't covered by tests. Now...
What do I gain from keeping it private
Since method is private, the only way to proceed is to study the code to learn how it is used through non-private API. Typically, such a study reveals that reason for the gap is that particular usage scenario is missing in tests.
void nonPrivateMethod(boolean condition) {
if (condition) {
privateMethod();
}
// other code...
}
// unit tests don't invoke nonPrivateMethod(true)
// => privateMethod isn't covered.
For the sake of completeness, other (less frequent) reasons for such coverage gaps could be bugs in specification / design. I won't dive deep into these here, to keep things simple; suffice to say that if you weaken access limitation "just to make method testable", you'll miss a chance to learn that these bugs exist at all.
Fine, to fix the gap, I add a unit test for missing scenario, repeat coverage analysis and verify that gap is gone. What do I have now? I've got as new unit test for specific usage of non-private API.
New test ensures that expected behavior for this usage won't change without a notice since if it changes, test will fail.
An outside reader may look into this test and learn how it is supposed to use and behave (here, outside reader includes my future self, since I tend to forget the code a month or two after I'm done with it).
New test is tolerant to refactoring (do I refactor private methods? you bet!) Whatever I do to privateMethod
, I'll always want to test nonPrivateMethod(true)
. No matter what I do to privateMethod
, there will be no need to modify test because method isn't directly invoked.
Not bad? You bet.
What do I loose from weakening access limitation
Now imagine that instead of above, I simply weaken access limitation. I skip the study of the code that uses the method and proceed straight with test that invokes my exPrivateMethod
. Great? Not!
Do I gain a test for specific usage of non-private API mentioned above? No: there was no test for nonPrivateMethod(true)
before, and there is no such test now.
Do outside readers get a chance to better understand usage of the class? No. "- Hey what's the purpose of the method tested here? - Forget it, it's strictly for internal use. - Oops."
Is it tolerant to refactoring? No way: whatever I change in exPrivateMethod
, will likely break the test. Rename, merge into some other method, change arguments and test will just stop compiling. Headache? You bet!
Summing up, sticking with private method brings me a useful, reliable enhancement in unit tests. In contrast, weakening access limitations "for testability" only gives me an obscure, hard to understand piece of test code, which is additionally at permanent risk of being broken by any minor refactoring; frankly what I get looks suspiciously like technical debt.
</rant>