Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reporting on code execution and design patterns?

First of all I wanted to thank all of you for your continuous contributions to the Stack Overflow community! I've been a member of Stack Overflow for years and have come to rely on your input more so than any other source online. Though I try to participate and answer members' questions whenever I can, every once in a while I find myself stuck and in need of help.

Speaking of which I have an unusual code problem. I am writing an API library in C# that needs to be able to be called from WPF/Windows Forms application, but also from within Unit Test code.

The issue is that I need to be able to report (in Excel) on whether each method of the library executed properly when the API is called from within a WPF/windows forms application, along some other metadata and optionally a return type.

When the code is consumed within Unit Tests I don't really care about the reporting, but I do need to be able to produce an Assert on whether the API call executed properly or not.

For instance, if in a Unit Test we have an Test Initialize portion, one of the API calls may be to create a Domain User for the test method to use. Another one may also create a Domain Group, so that the user has proper group membership.

To accomodate the consumption of the API from WPF/WinForms, I've been rewriting every function in the API to return a OperationStep type, with the hopes that when all API calls have executed I would have an IEnumerable<OperationStep> which I can write to a CSV file.

So the question is is there an easier way of achieving what I have done so far? The reporting is extremely tedious and time consuming to code, considering that the API library consists of hundreds of similar methods. Samples are described bellow:

OperationStep<PrincipalContext> createDomainConnectionStep = DomainContext.Current.GetPrincipalContext(settings.DomainInfo);
OperationStep<UserPrincipal> createDomainUserStep = DomainContext.Current.CreateUser(createDomainConnectionStep.Context, settings.TestAccountInfo.Username, settings.TestAccountInfo.Password);
OperationStep<GroupPrincipal> createDomainGroupStep = DomainContext.Current.CreateGroup(createDomainConnectionStep.Context, settings.TestAccountInfo.UserGrupName);

Where the DomainContext is a singleton object whose functionality is to connect to the domain controller and create a user, group, and associate the user to a group.

Note that both the second and the third method call require the output of the first, and therefore warranting the need for having the public T Context within the OperationResult object as described bellow.

The OperationStep object consists of the following properties which are inherited by the IOperation interface with the exception of the public T Context.

public class OperationStep<T> : IOperation
{
    /// <summary>
    /// Denotes the Logical Name of the current operation
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Denotes the stage of execution of the current operation: Setup, Execution, Validation, Cleanup 
    /// </summary>
    public OperationStage Stage { get; set; }

    /// <summary>
    /// Denotes whether the test step completed properly or failed.
    /// </summary>
    public OperationResult Result { get; set; }

    /// <summary>
    /// Denotes the return type of the test method.
    /// </summary>
    public T Context { get; set; }

    /// <summary>
    /// Denotes any other relevant information about the test step
    /// </summary>
    public string Description { get; set; }

    /// <summary>
    /// If the test step result is failed, this should have the stack trace and the error message.
    /// </summary>
    public string Error { get; set; }
}

The method calls themselves are a bit bloated and tedious but here is a sample.

public class DomainContext
{
    private static volatile DomainContext currentContext;
    private static object synchronizationToken = new object();

    /// <summary>
    /// default ctor.
    /// </summary>
    private DomainContext() { }

    /// <summary>
    /// Retrieves the Current DomainContext instance.
    /// </summary>
    public static DomainContext Current
    {
        get
        {
            if (currentContext == null)
            {
                lock (synchronizationToken)
                {
                    if (currentContext == null)
                    {
                        currentContext = new DomainContext();
                    }
                }
            }
            return currentContext;
        }
    }

    /// <summary>
    /// Establishes a connection to the domain.
    /// </summary>
    /// <param name="domainInfo"></param>
    /// <returns></returns>
    public OperationStep<PrincipalContext> GetPrincipalContext(DomainInfo domainInfo)
    {
        OperationStep<PrincipalContext> result = new OperationStep<PrincipalContext>();
        result.Name = "Establish Connection to Active Directory";
        result.Result = OperationResult.Success;
        result.Stage = OperationStage.Setup;
        result.Description = string.Format("Domain Name: {0}, Default Containter: {1}", domainInfo.FQDN, domainInfo.Container);

        try
        {
            ContextType contextType = this.GetContextType(domainInfo.DomainType);
            PrincipalContext principalContext;

            try
            {
                principalContext = new PrincipalContext(contextType, domainInfo.FQDN, domainInfo.Container);
            }
            catch
            {
                throw new Exception("Unable to establish connection to Active Directory with the specified connection options.");
            }

            if (principalContext != null)
            {
                bool authenticationResult = principalContext.ValidateCredentials(domainInfo.Username, domainInfo.Password);

                if (!authenticationResult)
                {
                    throw new Exception("Unable to authenticate domain admin user to Active Directory.");
                }

                result.Context = principalContext;
                result.Result = OperationResult.Success;
            }
        }
        catch(Exception ex)
        {
            result.Error = ex.Message;
            result.Result = OperationResult.Failure;
        }

        return result;
    }
}

When all method calls have executed theoreticaly I should have an IEnumerable<IOperation> which in the case of a win form I can write in a csv file (to be viewed in MS Excel) or in the case of a unit test I can simply omit the extra info and ignore (other than the method executed successively and the T Context property).

like image 379
bleepzter Avatar asked Aug 10 '26 10:08

bleepzter


1 Answers

If I understood you correctly - all that OperationSteps are here only for logging. Then why not enable simple .NET logging? Log needed info where it is convenient for you. You can use TraceSource with DelimetedTraceListener to write to .csv file. More than that. You can move logging logic to Strategy class and override its logging methods in your unit test so that instead of logging you call Assert methods.

like image 150
nikita Avatar answered Aug 13 '26 01:08

nikita



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!