In a current project, I am dealing with a client who insists that code generation is evil for unit testing. Some of the technical/evaluation staff on their team are extremely experienced developers so this surprises me. So much so, that after seeing their profiles, I am soliciting community opinion on a subject I would not otherwise question (i.e. automation).
The title refers to code generation for creating unit tests. That can be done to various extents depending on the scenario. I wrote this particular library myself and consider it to be very simple in terms of branching, complexity, maintainability, etc. An abstract is listed below to illustrate the same.
The library in question is a generic finite state machine that makes use of two enumerations to determine a list of possible states and commands. Commands are used to specify an abstract transaction while an explicit arbitrary state may also be requested without a command as long as it is legal. Examples of states include Processing, Cancelled, WaitingForUserInput, etc. Examples of commands include WakeUp, Sleep, StartProcessing, etc.
The decleration is as follows:
// Both [TState] and [TCommand] will ALWAYS be enumerations.
public abstract class StateMachineBase<TState, TCommand>: IDisposable
{
public delegate void DelegateStart (StateMachineBase<TState, TCommand> sender, EventArgs e);
public delegate void DelegateStop (StateMachineBase<TState, TCommand> sender, EventArgs e);
public delegate void DelegateTransitionRequest (StateMachineBase<TState, TCommand> sender, TransitionRequestEventArgs<TState, TCommand> e);
public delegate void DelegateTransitionComplete (StateMachineBase<TState, TCommand> sender, TransitionCompletedEventArgs<TState, TCommand> e);
public event DelegateStart OnStart = null;
public event DelegateStop OnStop = null;
public event DelegateTransitionRequest OnTransitionRequest = null;
public event DelegateTransitionComplete OnTransitionComplete = null;
private readonly object _SyncRoot = new object(); // Thread safety.
public bool Running { get; private set; }
public TState State { get; private set; }
private TCommand Command { get; set; }
private List<Transition<TState, TCommand>> AllowedTransitions { get; set; }
// Will not work if the machine is running.
protected void AddTransition (TState from, TState to, TCommand command) { ... }
public void Start () { ... }
public void Stop () { ... }
public bool CanTransit (TState state) { ... }
public bool CanTransit (TCommand command) { ... }
public bool Request (TState state) { ... }
public bool Request (TCommand command) { ... }
}
I do realize that in some cases, using code generation may defeat the purpose of writing unit tests but I am sure many developers do use it in specific scenarios. I figure, if I have written the library myself, I should be able to get the unit tests right whether by hand or reflection.
The question is, is it wise to use code generation in this case? If not, what unintended consequences could I be overlooking?
I believe very strongly that code generation makes sense for the creation of the test suite infrastructure, but not the business logic. The infrastructure (and you have an example of that in your example above) must be implemented a certain way, but it should be humans (preferably humans who know what they’re doing) who decide on the number of test cases, the purpose for each test case and finally the implementation of the test case logic.
I would talk to your colleugues to get their opinion on the split between non-negotiable infrastructure code and the stuff that’s let up to the test case author. You should be able to sell them on using code generation for the former and on hand-coding the latter.