Writing/implementing an API: testability vs information hiding

后端 未结 6 1174
春和景丽
春和景丽 2021-01-21 12:56

Many times I am involved in the design/implementation of APIs I am facing this dilemma.

I am a very strong supporter of information hiding and try to use various techniq

相关标签:
6条回答
  • 2021-01-21 12:59

    SomethingThatExpectsMyInterface can be tested outside Foo, right? You can call its submit() method with your own test class that implements MyInterface. So that unit is taken care of. Now you are testing Foo.someMethod() with that well-tested unit and your untested inner class. That's not ideal - but it's not too bad. As you test-drive someMethod(), you are implicitly test-driving the inner class. I know that's not pure TDD, by some strict standards, but I would consider it sufficient. You're not writing a line of the inner class except to satisfy a failing test; that there's a single level of indirection between your test and the tested code doesn't constitute a big problem.

    0 讨论(0)
  • 2021-01-21 13:05

    In your example it looks like the Foo class really needs a collaborator InnerFoo.

    In my opinion the tension between information hiding and testability is solved by the "composite simpler than the sum of its parts" motto.

    Foo is a facade over a composite (just InnerFoo in your case, but does not matter.) The facade object should be tested on its intended behaviour. If you feel that the InnerFoo object code is not driven enough by the tests on the behaviour of Foo, you should consider what InnerFoo represents in your design. It may be that you miss a design concept. When you find it, name it, and define its responsibilities, you may test its behaviour separately.

    0 讨论(0)
  • 2021-01-21 13:06

    I don't see how information hiding, in the abstract, is reducing your testability.

    If you were injecting the SomethingThatExpectsMyInterface used in this method rather than constructing it directly:

    public void someMethod() {
       // calculate x, y and z
       SomethingThatExpectsMyInterface something = ...;
       something.submit(new InnerFoo(x, y, z));
    }
    

    Then in a unit test you could inject this class with a mock version of SomethingThatExpectsMyInterface and easily assert what happens when you call someMethod() with different inputs - that the mockSomething receives arguments of certain values.

    I think you may have over-simplified this example anyway as InnerFoo cannot be a private class if SomethingThatExpectsMyInterface receives arguments of its type.

    "Information Hiding" doesn't necessarily mean that the objects you pass between your classes need to be a secret - just that you aren't requiring external code using this class to be aware of the details of InnerFoo or the other details of how this class communicates with others.

    0 讨论(0)
  • 2021-01-21 13:06

    what I don't like is making any sacrifices when it comes to information hiding

    First, work with Python for a few years.

    private is not particularly helpful. It makes extension of the class hard and it makes testing hard.

    Consider rethinking your position on "hiding".

    0 讨论(0)
  • 2021-01-21 13:16

    I think you should reconsider using reflection.

    It has its own downsides but if it allows you to maintain the security model you want without dummy code, that may be a good thing. Reflection is often not required, but sometimes there is no good substitute.

    Another approach to information hiding is to treat the class/object as a black box and not access any non-public methods (Though this can allow tests to pass for the "wrong" reasons i.e. the answer is right but for the wrong reasons.)

    0 讨论(0)
  • 2021-01-21 13:17

    My go-to answer for this type of thing is a "test proxy". In your test package, derive a subclass from your system under test, which contains "pass-through" accessors to protected data.

    Advantages:

    • You can directly test, or mock, methods you don't want made public.
    • Since the test proxy lives in the test package, you can ensure it is never used in production code.
    • A test proxy requires far fewer changes to code in order to make it testable than if you were testing the class directly.

    Disadvantages:

    • The class must be inheritable (no final)
    • Any hidden members you need to access cannot be private; protected is the best you can do.
    • This isn't strictly TDD; TDD would lend itself to patterns that didn't require a test proxy in the first place.
    • This isn't strictly even unit testing, because at some level you are dependent on the "integration" between the proxy and the actual SUT.

    In short, this should normally be a rarity. I tend to use it only for UI elements, where best practice (and default behavior of many IDEs) is to declare nested UI controls as inaccessible from outside the class. Definitely a good idea, so you can control how callers get data from the UI, but that also makes it difficult to give the control some known values to test that logic.

    0 讨论(0)
提交回复
热议问题