3

I'm testing 3 new classes that make 3 collections of objects that are serialized to a database. 1 of the classes has a hard coded array of strings. The 3 collections end up the same size as the array of strings and each object in the collection gets a name/tag based on string in the array.

One of my tests is an end-to-end test that will try to make all three collections. I would like to have access to the array of strings for testing but it's private. I see three possible ways to deal with this:

  • Make it protected <- Office policy is to NOT modify code design for the sake of testing.
  • Copy array to test class <- Now array changes must be maintained in two locations. Ow.
  • Use reflection to peek at privates <- This works but is it more or less evil than the alternatives?

Can you think of a fourth way? Is there a good reason to not use reflection for this?

candied_orange
  • 7,036
  • 2
  • 28
  • 62
  • Could you add a getter to your application class that returns an unmodifiable view of the array? – Dawood ibn Kareem May 06 '14 at 05:44
  • So basically you have three factory classes that each generate three collection objects? And you'd like to access the generated collection objects throught reflection since they're private? – Vincent May 06 '14 at 05:45
  • A fourth way is to write tests that use only the public interface of the classes under test. – user253751 May 06 '14 at 05:49
  • Anyone have anything to say about using reflection for this? The way I see it, it would break encapsulation but only for the classes that use reflection to peek inside. I planed to only allow testing to do that. Is this a good or bad idea? – candied_orange May 06 '14 at 06:32

2 Answers2

2

It really depends on what your test is trying to exercise. In general, if you are testing the internal implementation details of your code, you are probably doing the wrong thing with your tests. After all, your tests should be exercising the observable effects of your code, not the implementation details. Tests that exercise implementation details are brittle (needing to be updated whenever the implementation changes) and usually are just a copy of the implementation. Also, because they mirror the implementation, bugs in the implementation are likely to be reflected in the tests, as well, so such tests are typically of dubious value.

A better approach for such a thing is to use an interface to represent your database object and to use a mocking framework such as mockito to verify that the data you expect is written to and read from the database, without inspecting the innards of how your code is storing the data internally before writing it to the database.

Also, in terms of not writing code differently for tests... in general, it is good practice not to have test-only methods in your production code. However, refactoring code to make it more modular (and therefore more testable), is a generally good coding practice to have.

Michael Aaron Safyan
  • 93,612
  • 16
  • 138
  • 200
  • 1
    Excellent answer. The last paragraph could be expanded a little - code that's testable is code that's maintainable, so testability is an extremely important attribute of code. Moreover, it's better to build in testability as you write your code, than to try and add it in later. – Dawood ibn Kareem May 06 '14 at 06:00
  • Among other things the test is trying to confirm that if the array has 10 elements that 3 collections of 10 objects were sent to the database. Stubbing / Mocking out the database doesn't help me count to 10. I already have the database stubbed. I'm asking about respecting encapsulation. – candied_orange May 06 '14 at 06:07
  • 1
    @CandiedOrange, but how does the array get to 10 elements? Perhaps there is some other form of setup that you can do that establishes this expected condition? – Michael Aaron Safyan May 06 '14 at 06:09
  • @Michael Aron Safyan. Ah, good question. The array is hard coded. I should have mentioned that. Thanks! The whole point is I don't want my test to break if it grows to 11 but I do want to confirm sizes match. – candied_orange May 06 '14 at 06:13
  • 1
    @CandiedOrange, got it. So, one way you can test this is to have a constructor that allows you to supply the array, and another constructor that default initializes the array to its hard-coded value. – Michael Aaron Safyan May 06 '14 at 06:14
  • @Michael Aron Safyan I see. You're suggesting a dependency injection solution. That is definitely a fourth method but given that we're discouraged from making design changes to operational code for the sake of testing I'm not sure it'll fly in my shop. Personally I like it better than making the array protected. – candied_orange May 06 '14 at 06:17
1

The variable itself should stay private, but you can add a getter with default (package-private) access.

If the test code is located in the same package. this solves the problem right away.

If the test code from other packages needs to access this getter, you will have to write some boilerplate of the following form:

The class under test has private field and a getter with default access:

public class MyClass {

    private Object someData;

    // you should research the usage of these or similar annotations
    // they will help your IDE to catch improper usage of test-only methods
    //@com.google.common.annotations.VisibleForTesting
    //@org.jetbrains.annotations.TestOnly
    Object getSomeData() {
        return someData;
    }

}

The test code contains a helper that exposes getter as public so it can be used from tests in other packages:

public class MyClassTestHelper {

    public static Object getSomeData(MyClass instance) {
        return instance.getSomeData();
    }

}

You should also look at How do I test a class that has private methods, fields or inner classes? (your question can be even considered duplicate of it) and annotation to make a private method public only for test classes .

Community
  • 1
  • 1
Oleg Estekhin
  • 8,063
  • 5
  • 49
  • 52
  • +1 for the links that talk about reflection. Need to know the draw backs of that. The annotation trick is new to me so thanks for that. However, the getter and annotation both run afoul of my shops "don't redesign for testing" rule. Not my idea. I'm just trying to live with it. – candied_orange May 06 '14 at 06:57