This project is read-only.

Suggestion: Step constructor overload with Delegate and params object

Nov 2, 2010 at 10:02 PM

Hy,

When you change Step to the following:

        public Step(string prefix, int indentLevel, string text, Action action)
            : this(prefix, indentLevel, text, action, new object[] { })
        {
        }

        public Step(string prefix, int indentLevel, string text, Delegate action, params object[] args)
        {
            Prefix = prefix;
            IndentLevel = indentLevel;
            Text = text;
            Action = action;
            ActionArgs = args;
        }

        /// <summary>
        /// Gets or sets the action.
        /// </summary>
        /// <value>The action.</value>
        public Delegate Action { get; private set; }

        /// <summary>
        /// Gets or sets the action arguments.
        /// </summary>
        /// <value>The action arguments.</value>
        public object[] ActionArgs { get; private set; }

        public Result Execute()
        {
            IEnumerable<string> t = tags ?? Enumerable.Empty<string>();

            if (!IsExecutable)
            {
                return Result.ForResultType(Prefix, IndentLevel, Text, t, ResultType.NotExecutable);
            }

            try
            {
                Action.DynamicInvoke(ActionArgs);
                return Result.ForResultType(Prefix, IndentLevel, Text, t, ResultType.Passed);
            }
You can support unlimited number of paramaters. This allows to generate a non generic overload of for example

Given(Delegate descriptiveAction, params object[] args)

Which then gives the possibility to invoke methods with any kind of arguments. Nonetheless you could have conditional build for .NET 3.5 and
.NET 4.0 with supports the number of Action<> overloads but still have the Delegate and params object[] stuff in it.

What do you think?

Daniel
Nov 3, 2010 at 10:21 PM

Hi Daniel

Sounds good in principle, although I personally try to keep my methods under 4 arguments, not everyone uses the tool exactly like me :)

What does the calling code look like when you're using the Delegate parameter?

Dec 15, 2010 at 1:41 AM

I made this change in the DataDrivenStories fork because I needed a similar interface for the parameter tracking I am working on (not to allow more parameters be passed into an Action).

I also made the .ctor private and exposed static methods that clearly illustrate what type of Step is being created. Consumers no longer need to know about the DoNothing delegate or how to convert an Action into text and they only see the parameters applicable to the type of Step they are creating.
 

        /// <summary>
        /// Initializes a new instance of the <see cref="Step"/> class.
        /// </summary>
        /// <param name="prefix">The prefix.</param>
        /// <param name="indentLevel">The indent level.</param>
        /// <param name="action">The action.</param>
        /// <param name="parameters">The parameters for the action</param>
        public static Step Executable(string prefix, int indentLevel, Delegate action, params object[] parameters)
        {
            return new Step(prefix, indentLevel)
                       {
                           Action = action,
                           Parameters = parameters,
                           Text = Formatter.FormatMethod(action, parameters)
                       };
        }

        /// <summary>
        /// Returns a new instance of the <see cref="Step"/> class. for a step that is not executable
        /// </summary>
        /// <param name="prefix">The prefix.</param>
        /// <param name="indentLevel">The indent level.</param>
        /// <param name="text">The text.</param>
        /// <returns>a new instance of the <see cref="Step"/> class</returns>
        public static Step NotExecutable(string prefix, int indentLevel, string text)
        {
            return new Step(prefix, indentLevel) { Text = text, Action = DoNothing };
        }

        /// <summary>
        /// Returns a new instance of the <see cref="Step"/> class. for a step that is pending
        /// </summary>
        /// <param name="prefix">The prefix.</param>
        /// <param name="indentLevel">The indent level.</param>
        /// <param name="text">The text.</param>
        /// <returns>a new instance of the <see cref="Step"/> class</returns>
        public static Step Pending(string prefix, int indentLevel, string text)
        {
            return new Step(prefix, indentLevel) {Text = text, Action = (Action)(() => { throw new StringBasedExecutableStepException(text); })};
        }

        private Step(string prefix, int indentLevel)
        {
            Prefix = prefix;
            IndentLevel = indentLevel;
            Tags = new List<string>();
        }
Here is an example of the calling code:
        [Description("Provide the initial context to the scenario. Try not to describe behaviour or actions, this step describes and sets up initial state")]
        public Condition Given<T1, T2, T3, T4>(Action<T1, T2, T3, T4> descriptiveAction, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
        {
            Step s = Step.Executable("Given", 4, descriptiveAction, arg1, arg2, arg3, arg4);
            return new Condition(s, this);
        }
How does this look to you guys?