Carna can customize attributes, steps, running of fixtures, and reporting of results, etc.
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() { } }
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() { } }
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"); } }