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");
}
}