Skip to content

Customization

Averrunci edited this page Mar 21, 2017 · 1 revision

Custom FixtureAttribute

If you prefer TestFixture and Test as an attribute of a fixture, you can use them by defining them that inherit FixtureAttribute as follows.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class TestFixtureAttribute : FixtureAttribute
{
    public override bool IsContainerFixture => true;

    public TestFixtureAttribute() : this(null)
    {
    }

    public TestFixtureAttribute(string description) : base(description)
    {
        IsRootFixture = true;
    }
}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class TestAttribute : FixtureAttribute
{
    public override bool IsContainerFixture => false;

    public TestAttribute() : this(null)
    {
    }

    public TestAttribute(string description) : base(description)
    {
    }
}
[TestFixture]
class SampleTest
{
    [Test]
    void Test1()
    {
    }
}

Custom Attribute

Carna does not have an attribute that indicates that a fixture should not to be run like IgnoreAttribute. If you need it, you can define as follows.

At first, define IgnoreAttribute.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class IgnoreAttribute : Attribute
{
}

Next, define a finder that implements IFixtureTypeFinder and a builder that implements IFixtureBuilder to exclude a fixture specified by IgnoreAttribute.

public class CustomFixtureTypeFinder : FixtureTypeFinder
{
    protected override Func<TypeInfo, bool> FixtureTypeFilter => typeInfo =>
        base.FixtureTypeFilter(typeInfo) && typeInfo.GetCustomAttribute<IgnoreAttribute>() == null;
}

public class CustomFixtureBuilder : FixtureBuilder
{
    protected override Func<MethodInfo, bool> MethodFilter =>
        m => base.MethodFilter(m) && m.GetCustomAttribute<IgnoreAttribute>() == null;
}

Finally, speficy them in carna-runner-settings.json so that they can be used.

{
  "finder": {
    "type": "SampleSpec.CustomFixtureTypeFinder,SampleSpec"
  },
  "builder": {
    "type": "SampleSpec.CustomFixtureBuilder,SampleSpec"
  }
}
[Specification]
class CustomerSpec
{
    [Example]
    [Ignore]
    void Ex01()
    {
    }

    [Example]
    void Ex02()
    {
    }
}

Custom Step

If you prefer Arrange, Act and Assert as a step, you can use them by defining them as follows.

At first, define ArranStep, ActStep, and AssertStep that inherits FixtureStep.

public class ArrangeStep : FixtureStep
{
    public Action Arrangement { get; }

    public ArrangeStep(string description, Type callerType, string callerMemberName, string callerFilePath, int callerLineNumber) : base(description, callerType, callerMemberName, callerFilePath, callerLineNumber)
    {
    }

    public ArrangeStep(string description, Action arrangement, Type callerType, string callerMemberName, string callerFilePath, int callerLineNumber) : base(description, callerType, callerMemberName, callerFilePath, callerLineNumber)
    {
        Arrangement = arrangement;
    }
}

public class ActStep : FixtureStep
{
    public Action Action { get; }

    public ActStep(string description, Type callerType, string callerMemberName, string callerFilePath, int callerLineNumber) : base(description, callerType, callerMemberName, callerFilePath, callerLineNumber)
    {
    }

    public ActStep(string description, Action action, Type callerType, string callerMemberName, string callerFilePath, int callerLineNumber) : base(description, callerType, callerMemberName, callerFilePath, callerLineNumber)
    {
        Action = action;
    }
}

public class AssertStep : FixtureStep
{
    public Expression<Func<bool>> Assertion { get; }
    public Expression<Func<Exception, bool>> ExceptionAssertion { get; }
    public Action Action { get; }
    public Action<Exception> ExceptionAction { get; }

    public AssertStep(string description, Type callerType, string callerMemberName, string callerFilePath, int callerLineNumber) : base(description, callerType, callerMemberName, callerFilePath, callerLineNumber)
    {
    }

    public AssertStep(string description, Expression<Func<bool>> assertion, Type callerType, string callerMemberName, string callerFilePath, int callerLineNumber) : base(description, callerType, callerMemberName, callerFilePath, callerLineNumber)
    {
        Assertion = assertion;
    }

    public AssertStep(string description, Expression<Func<Exception, bool>> exceptionAssertion, Type callerType, string callerMemberName, string callerFilePath, int callerLineNumber) : base(description, callerType, callerMemberName, callerFilePath, callerLineNumber)
    {
        ExceptionAssertion = exceptionAssertion;
    }

    public AssertStep(string description, Action action, Type callerType, string callerMemberName, string callerFilePath, int callerLineNumber) : base(description, callerType, callerMemberName, callerFilePath, callerLineNumber)
    {
        Action = action;
    }

    public AssertStep(string description, Action<Exception> exceptionAction, Type callerType, string callerMemberName, string callerFilePath, int callerLineNumber) : base(description, callerType, callerMemberName, callerFilePath, callerLineNumber)
    {
        ExceptionAction = exceptionAction;
    }
}

Next, define a class that implements IFixtureSteppable so that these steps can be used.

public class ArrangeActAssertSteppable : IFixtureSteppable
{
    protected virtual void Arrange(string description, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
    {
        (this as IFixtureSteppable).Stepper.Take(new ArrangeStep(description, GetType(), callerMemberName, callerFilePath, callerLineNumber));
    }

    protected virtual void Arrange(string description, Action arrangement, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
    {
        (this as IFixtureSteppable).Stepper.Take(new ArrangeStep(description, arrangement, GetType(), callerMemberName, callerFilePath, callerLineNumber));
    }

    protected virtual void Act(string description, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
    {
        (this as IFixtureSteppable).Stepper.Take(new ActStep(description, GetType(), callerMemberName, callerFilePath, callerLineNumber));
    }

    protected virtual void Act(string description, Action action, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
    {
        (this as IFixtureSteppable).Stepper.Take(new ActStep(description, action, GetType(), callerMemberName, callerFilePath, callerLineNumber));
    }

    protected virtual void Assert(string description, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
    {
        (this as IFixtureSteppable).Stepper.Take(new AssertStep(description, GetType(), callerMemberName, callerFilePath, callerLineNumber));
    }

    protected virtual void Assert(string description, Expression<Func<bool>> assertion, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
    {
        (this as IFixtureSteppable).Stepper.Take(new AssertStep(description, assertion, GetType(), callerMemberName, callerFilePath, callerLineNumber));
    }

    protected virtual void Assert(string description, Expression<Func<Exception, bool>> exceptionAssertion, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
    {
        (this as IFixtureSteppable).Stepper.Take(new AssertStep(description, exceptionAssertion, GetType(), callerMemberName, callerFilePath, callerLineNumber));
    }

    protected virtual void Assert(string description, Action assertion, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
    {
        (this as IFixtureSteppable).Stepper.Take(new AssertStep(description, assertion, GetType(), callerMemberName, callerFilePath, callerLineNumber));
    }

    protected virtual void Assert(string description, Action<Exception> exceptionAssertion, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
    {
        (this as IFixtureSteppable).Stepper.Take(new AssertStep(description, exceptionAssertion, GetType(), callerMemberName, callerFilePath, callerLineNumber));
    }

    IFixtureStepper IFixtureSteppable.Stepper { get; set; }
}

Finally, define runners that implement IFixtureStepRunner to run these steps.

public class ArrangeStepRunner : FixtureStepRunner<ArrangeStep>
{
    public ArrangeStepRunner(ArrangeStep step) : base(step)
    {
    }

    protected override FixtureStepResult.Builder Run(FixtureStepResultCollection results)
    {
        if (Step.Arrangement == null)
        {
            return FixtureStepResult.Of(Step).Pending();
        }

        try
        {
            Step.Arrangement?.Invoke();

            return FixtureStepResult.Of(Step).Passed();
        }
        catch (Exception exc)
        {
            return FixtureStepResult.Of(Step).Failed(exc);
        }
    }
}

public class ActStepRunner : FixtureStepRunner<ActStep>
{
    public ActStepRunner(ActStep step) : base(step)
    {
    }

    protected override FixtureStepResult.Builder Run(FixtureStepResultCollection results)
    {
        if (Step.Action == null)
        {
            return FixtureStepResult.Of(Step).Pending();
        }

        try
        {
            Step.Action?.Invoke();

            return FixtureStepResult.Of(Step).Passed();
        }
        catch (Exception exc)
        {
            return FixtureStepResult.Of(Step).Failed(exc);
        }
    }
}

public class AssertStepRunner : FixtureStepRunner<AssertStep>
{
    public AssertStepRunner(AssertStep step) : base(step)
    {
    }

    protected override FixtureStepResult.Builder Run(FixtureStepResultCollection results)
    {
        if (IsPending)
        {
            return FixtureStepResult.Of(Step).Pending();
        }

        try
        {
            Step.ExecuteAssertion(Step.Assertion);
            Step.Action?.Invoke();

            if (HasAssertionWithException)
            {
                var exception = results.GetLatestExceptionAt<ActStep>();
                Step.ExecuteAssertion(Step.ExceptionAssertion, exception);
                Step.ExceptionAction?.Invoke(exception);
                results.ClearException(exception);
            }

            return FixtureStepResult.Of(Step).Passed();
        }
        catch (Exception exc)
        {
            return FixtureStepResult.Of(Step).Failed(exc);
        }
    }

    private bool IsPending => !HasAssertionWithoutException && !HasAssertionWithException;
    private bool HasAssertionWithoutException => Step.Assertion != null || Step.Action != null;
    private bool HasAssertionWithException => Step.ExceptionAssertion != null || Step.ExceptionAction != null;
}
[Specification]
class LoginAuthenticationSpec : ArrangeActAssertSteppable
{
    [Example("Invalid user name or password is specified")]
    void Ex01()
    {
        Arrange("an invalid user name");
        Arrange("an invalid password");
        Act("the authentication");
        Assert("that the user should not be authenticated");
    }
}
Clone this wiki locally