Sunday, March 27, 2005

Accessing Private Members in C#

I recently created a class that needed internal visibility; however, I wanted to unit test the methods of the class. I always put my tests in a separate library and couldn't access my class to test it. This brought me to the question, how do you limit the visibility of your classes (or methods etc) in release code, but still make it visible enough to test?

I knew I could use reflection; however, I was looking for a simpler example. I've never gone down the reflection road for unit testing, but it sounds more complicated than necessary. I decided to use conditional compilation like so:

#if TESTING
public
#else
internal
#endif

Obviously, I changed my NAnt build file to define the conditional compilation symbol TESTING, compile, run NUnit, and recompile without the TESTING symbol.

This worked, but made my method signatures ugly:

#if TESTING
public
#else
internal
#endif
void Foo()

So, I threw a #region into the mix and they now look like this

TESTING?public:internal
void Foo()

I heard another idea at ThoughtWorks. You could create a properly visible interface that you ask developers to code to. Then make your actual classes more visible to allow testing. This works also, but still increases complexity in my opinion. Also, I like to use NDoc to generate my documentation and it will document all my public classes that I'd probably like hidden. Sure, I can explicitly exclude things from NDoc, but that seems like a lot of work. Conditional compilation will give me correct visibility with the only sacrifice being my signature split to 2 lines.

4 comments:

  1. My concern with this approach is that you are not testing the code that goes into production.

    ReplyDelete
  2. Anonymous10:42 AM

    I agree that you should test production code. However, you should be testing code you've written, not the features of the language. Therefore, you should only put a visibility keyword in the conditional compilation. Also, you can separate your tests to tests that run through the public interfaces and code that can test private members. Then, you can run the public interface tests on the production compilation. Lastly, I'd rather violate the "test production code" rule than not test something. You can't test everything through the public interfaces, if you could 80% code coverage wouldn't acceptable, and 100% would be required.

    ReplyDelete
  3. "You can't test everything through the public interfaces" - that's a comment worth considering. What are the things that can't be tested via a public interface? Answer: Implicit dependencies. If a dependency exists - in this case on a contract with internal visibility - then it's preferable to make it explicit. This is after all what techniques like dependency injection are for. Violating this guideline (I almost said 'principle') elevates what would otherwise be implementation detail to a first class element of the design. If that doesn't break encapsulation then it certainly bends it a little.
    As for test coverage, why not shoot for 100% on public interfaces? (80% coverage of public interfaces + 100% unit test pseudo-coverage of implicit dependencies) != confidence for me.
    If for whatever reason there's no choice about testing in the manner you describe, then it might be preferable to include the tests in the production assembly to avoid the issue of not actually deploying what you've spent all that effort testing. (IP and governance issues aside, this approach can even be helpful in production problem diagnosis.)
    It's also worth noting that if we had to choose one approach or the other within a development team, I would put it to the vote as you described in another post. Now that's an idea I subscribe to whole-heartedly! :)

    ReplyDelete
  4. Anonymous12:48 PM

    I definately don't feel strongly enough to defend this approach at all costs.

    However, the idea of the original post was that if you were to access private members, I believe this solution is superior to reflection and correctly scoped interfaces with wide open concrete classes.

    Also, it's much more convienent to test a FooStrategy class, that should have internal visibility, directly. Much of the complication in testing is workarounds to deal with interfaces, mocking, dependencies, inheritance, etc. Anything that can simplify this process shouldn't be quickly discredited.

    Also, I wrote this post before I joined ThoughtWorks, and I was working with several developers who have never heard of DI. Since I've joined TW, I've never needed this technique.

    ReplyDelete

Note: Only a member of this blog may post a comment.