.NET Unit Testing - best practices for hiding test seams in release code

I am looking for some tips for unit testing so as not to leave “test hooks” in the working code.

Let's say I have a static class (VB.NET module) called MethodLogger, which has a method called “void WriteEntry (string message)” that is designed to write the calling method and message to a log file on disk. The actual purpose of WriteEntry () can be redirected to a fake IMethodLogger implementation to run unit tests, and for the rest of them , this is a real IMethodLogger implementation.

Here is a rough decision of what I have hacked so far, and I like this approach, but some quick tests raise some questions and doubts:

[InternalAttributesVisibleTo("MethodLogger.Tests")] internal static class MethodLogger { public void WriteEntry(string message) { LogWriter.WriteEntry(message); } private IMethodLogger LogWriter { get; set; } #if DEBUG internal void SetLogWriter(IMethodLogger logger) { LogWriter = logger; } #endif } 

Here are my specific questions:

  • This is closely related to unit tests for working with the DEBUG design; when I run unit tests, although it seems that it doesn’t specifically rebuild the assembly under test in DEBUG - is it possible to do this?

    • Should I ever run unit tests against assembly without debugging? Above my head I see less meaning - but will it?
  • Is it possible that instead of "DEBUG" you can use your own flag of the preliminary compiler of type "TEST"? How can I say that Visual Studio always rebuilds the target using this flag for testing to ensure that your hooks / seams are accessible?

+6
unit-testing visual-studio
source share
11 answers

I don’t like the idea that I cannot run tests against real binary files myself.

Could you mark the installer as "obsolete" and disable the obsolete warning in the test code? Along with suitable documentation, this should stop the production code from being accidentally called, but still make it available for testing.

However, to answer your current questions:

  • Yes, it makes sense to run unit tests against non-debug builds - this gives you more confidence that changes between debug and non-debug builds do nothing.
  • Yes, you can use your own preprocessor character (like TEST) if you want; you can apply this in existing configurations or create a new assembly configuration.
+3
source share

We are sure to run tests against Release / Obfuscated collections, because we often disclosed the problems in this way, so yes, I think that there is a value for their work.

Obfuscation makes the internals completely unusable, which is enough for me, so we go into testing methods (and make them internals). A.

All in all, I would rather have the code tested than worry about being there - until they affect performance.

+4
source share

In fact, you are on your way to Dependency Injection - you have not realized this yet :)

There is nothing wrong with leaving the seams in place - you just need to model them so that instead of being specific to testing, they are actually enhancements to your API. This requires some practice, but, above all, a change in thinking.

I recently wrote about this in Tests really are the principle of open / closed .

In your specific case, there are several changes that you must make to the MethodLogger class:

  • Make an instance class instead of a static class. As all experienced TDD professionals will tell you, static is evil.
  • Let a single constructor accept an instance of IMethodLogger. This is called Constructor Injection and forces all clients to make an explicit decision about how to register.
  • Either configure the dependency graph manually, or use the DI container to associate MethodLogger with the desired IMethodLogger.
+4
source share

You can use Typemock Isolator to get around any design issues that harm testability. unlike other frameworks, it will also work with static methods, private methods, and non-virtual methods. as you learn how to develop better, your code will be more “tested” by default, but if you just want to get around these problems now, it will help

http://blog.typemock.com/2009/01/isolator-new-vbnet-friendly-api.html

(also, this is the only tool with a VB compatible API)

+4
source share

The presence of #IF DEBUG , etc. - bad form.

Production code should not depend on the verification / debugging code.

I would advise you to study dependency injection to help in unit testing.

+2
source share

According to Michael Persian's book, Working with Legacy Code, it would seem better if you called the installer something specific for testing.

Example: SetTestingLogger (IMethodLogger logger)

I would also delete preprocessor instructions. The book also says, and I quote

“You may have a little idea that you remove access protection when using a static setter, but remember that the goal of access protection is to prevent errors. We run tests to also prevent errors.

Conclusion: It would seem that it would be better to place these hooks, because you place them because of testing and, ultimately, to remove errors.

The "Enter Static Setter" section in the book explains this in more detail. I would recommend it as a book that all developers should have.

+2
source share
  • as others have said, go to Injection Dependency Injection and inversion-of-control (IoC)
  • isolating sub-test classes well, using isolation frameworks (Moq, etc.).
  • move tests to a separate assembly - no need for #if DEBUG ... #endif
+2
source share

This is an internal method. Just leave it as without conditional compilation, and name it only from your unit tests.

There is nothing wrong with leaving the hook inside - in fact, you might even consider making this method public and allowing any code to change the registration hook.

View code again; you don’t even need the SetLogWriter method; just use a private accessory for your static class, and you can install it yourself in your unit tests.

I see that everything is in order to leave the hook in place, the removal is just a preference, but I do not agree to make it publicly available - it feels too much like an elevation of rights just because it removes a bit of trouble. - yoooder

Well, I commented more on the general principles of code organization. It is usually a good idea to create such hooks for your code; in your case, you would not “raise permissions to troubleshoot”, you would “create a reliable and flexible logging structure”.

+1
source share

This seems like the best IoC container candidate for me. I would make my journal non-static and set up different configurations for testing, development and production.

+1
source share

The assembly process should include the assembly of production with the implementation of unit tests. If this is not the case, you cannot guarantee that someone has not slipped using pragmas (e.g. disconnecting from debug).

What I always do in case of logging, use Log4Net . During development and testing, log messages are set at the most detailed level, and during the production process I try to leave them at the level of errors or information, but should be more detailed if necessary to troubleshoot.

IMO, the best way to accomplish what you are trying to accomplish with SetLogWriter is to actually use an inversion of the control container, such as StructureMap , to assign concrete at runtime based on various configurations. During a unit test, you can use a library like Moq to produce concrete that behaves as expected (an SQL query that always returns the exact same result), although in your particular case you are doing just the opposite (desired logging during unit tests and lack of registration during the manufacturing process).

+1
source share

Is it possible to use a custom pre-compiler flag, for example, "TEST" instead of "DEBUG"? How will I go about installing Visual Studio always rebuild the target with this flag for testing so that my hooks / seams are accessible?

Yes. What you want to do is add a conditional compilation symbol. You can do this in the project properties on the "Assembly" tab. You can create another assembly configuration in Configuration Manager and add only a new TEST symbol to the new configuration.

0
source share

All Articles