Carna

Customization

Carna can customize attributes, steps, running of fixtures, and reporting of results, etc.

Customize FixtureAttributes

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

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

    public TestFixtureAttribute()
    {
    }

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

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

    public TestAttribute()
    {
    }

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

Customize Attributes

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

At first, define the IgnoreAttribute attribute.

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

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

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

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

Finally, specify them in the "carna-runner-settings.json" file 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()
    {
    }
}

Customize Steps

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

At first, define the ArrangeStep, ActStep, and AssertStep classes that inherits the FixtureStep class.

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 the IFixtureSteppable interface 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 the IFixtureStepRunner interface 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 is 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 is 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
        {
            if (Step.Assertion is not null) Step.ExecuteAssertion(Step.Assertion);
            Step.Action?.Invoke();

            if (HasAssertionWithException)
            {
                var exception = results.GetLatestExceptionAt<ActStep>();
                if (exception is not null) results.ClearException(exception);

                if (Step.ExceptionAssertion is not null) Step.ExecuteAssertion(Step.ExceptionAssertion, exception);
                Step.ExceptionAction?.Invoke(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 is not null || Step.Action is not null;
    private bool HasAssertionWithException => Step.ExceptionAssertion is not null || Step.ExceptionAction is not 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");
    }
}