1

I understand protected method is good for unit testing since you can easily mock by the class by overriding the protected method for the sake of testing.

However, protected variable is just a global variable (although limited) and should be avoided at all.

Especially if you've all the essential getter/setter declared, you should have zero protected variable and it should not affect anything else.

So, in the future, if I am going to elimiate all protected variables in my codes, are there any caveat that I am not aware of?

user34401
  • 791

3 Answers3

5

There is no need to test private or protected member variables, and getter/setter methods are almost always part of the public API, so I'm going to assume that you're talking about unit testing private or protected methods.

This is probably going to be an unpopular opinion, but here goes anyway.

Principle Number One: If your unit testing framework cannot test methods unless they are public, virtual or backed by an interface, your unit testing framework sucks and you need to find a new one.

Now that you have a testing framework that can test private methods...

Principle Number Two: You don't need to mock a private method. Ever.

Do you see why? Private methods are not part of the public API of your class, so mocking them doesn't make any sense.

I know there's a school of thought that says that you should make all methods public so that they can be easily unit tested, and that, if your method is not public, it doesn't need to be unit tested, because it gets touched by some public method anyway.

Hogwash.

First of all, why should I pollute my public API with methods that may never be called directly by the consumer?

Second, why shouldn't I be able to test my private methods, directly? The whole point of refactoring code into separate methods is to sequester functionality behind those methods. Methods are a little testable black box, and the need to create them and test them independently doesn't diminish merely because I have marked them private.

Don't even get me started on the "one interface for every class" practice.

Robert Harvey
  • 200,592
2

You seem to have a misconception that is leading to your confusion.

However, protected variable is just a global variable (although limited) and should be avoided at all.

A protected field in a class limits its visibility to the class, other classes in the same package, and subclasses. This is not a global variable.

To have a 'global' variable in Java one would have something that looks roughly like:

class Foo {
    public static int bar;
}

The key points there is that there is only one Foo.bar in the code and it is accessible to everything. Changing this to protected static int bar will reduce its visibility, but it is still something that is a global shared state.

On the other hand a class that looks like:

class Foo {
    protected int bar;
}

This has a protected field that is only accessible to some things (as mentioned above) and is an instance variable - that there is one instance per object. It is not a global variable. There are good reasons to have code that looks like this (you will often find it in abstract classes).


Especially if you've all the essential getter/setter declared, you should have zero protected variable and it should not affect anything else.

Getters and setters are different than accessibility. The bean model for Java often dictates their existence. Typically, one has private properties rather than protected in this case.

Though I will point out again that a protected field is one of where it is visible.


In the context of testing...

One approach to making specific methods return a specific value is to extend the class and rewrite that method. This is only possible if the method is not final or private.

So, yes, you could indeed use that approach.

However, there is awkwardness here in that you need to make sure that your rewritten class is in test packages only to make sure it doesn't slip into your release code.

The other thing is that when you are doing this, you are very tempted to write additional logic in there that substantially differs from the extended class. This introduces the danger that you won't actually be testing what you think you are testing.

Both of these issues can be addressed by instead mocking the class in the test code and specifically returning the desired values. It makes it very clear what you are sticking in where with no additional logic and that the code doesn't get into production (mocked classes only exist at runtime in tests).

There are still gotchas to watch out for in here, because it still hasn't removed the logic (just ripped it out) and you still need to make sure that part works correctly.


Ahh, but "you can't mock or unit test private methods easily" people will point out. "If you can t do it easily, people will either not do it, or do it wrong." And that is true.

But there is another option. The default level protection within Java. See Controlling Access to Members of a Class

package com.se.prog.demo;

class Foo {
    public int bar() { return 42; }
    protected int baz() { return 4; }
    int qux() { return 7; }
    private int xyzzy() { return 13; }
}

The methods bar, baz and qux can all be mocked easily. qux can only be accessed by other classes in the same package as com.se.prog.demo -- and you control that list.

With the maven directory structure, one would have

src/main/java/com/se/prog/demo/Foo.java
   ^^^^^^

but there's also

src/test/java/com/se/prog/demo/TestFoo.java
   ^^^^^^

which can access qux() in Foo allowing for unit testing of the gutsier parts of Foo, without exposing it to the world (and Foo's subclasses).

In some circles, this is seen as a better approach for methods that are the innards of the class but still need to be tested or mocked than protected or private.

0

Protected variables exist for a reason: they give parent classes the ability to let their children know about the their internal representation. If your public get/set methods aren't just pass-through (maybe the get/set is typed as a DateTime, but internally you store everything as Unix time in an int field, for example), then the classes that derive from that class may need to access the underlying value in the field, and not what the object exposes to the outside world (via the get function).