Skip to content

Instantly share code, notes, and snippets.

@JohannesRudolph
Created December 20, 2012 17:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JohannesRudolph/4347105 to your computer and use it in GitHub Desktop.
Save JohannesRudolph/4347105 to your computer and use it in GitHub Desktop.
Simple spec. A minimalisitic specification framework for xunit.net. MS-PL License.
using SimpleSpec;
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace SimpleSpec.Tests
{
public class Demo
{
[SimpleSpecification]
public void NestedSpecificationSyntax()
{
var sut = default( Stack<int> );
"Given a new stack with an element on it"
.Context( () =>
{
sut = new Stack<int>();
sut.Push( 1 );
"when we peek"
.Do( () =>
{
var peeked = sut.Peek( );
"we expect the stack is not empty"
.Observation( () =>
Assert.NotEmpty( sut ) );
"we expect peek gives us the top element"
.Observation( () =>
Assert.Equal( 1, sut.Peek() ) );
} );
"when we ask for its size"
.Do( () =>
{
var size = sut.Count;
"we expect the stack is not empty"
.Observation( () =>
Assert.NotEmpty( sut ) );
"we expect it contained one element"
.Observation( () =>
Assert.Equal( 1, size ) );
} );
} );
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using Xunit.Sdk;
namespace SimpleSpec
{
internal class Core
{
internal static class SpecificationContext
{
[ThreadStatic]
private static bool _threadStaticInitialized;
[ThreadStatic]
private static SpecificationPrimitive _context;
[ThreadStatic]
private static List<SpecificationPrimitive> _dos;
[ThreadStatic]
private static List<SpecificationPrimitive> _observations;
[ThreadStatic]
private static List<Action> _exceptions;
private static void Reset()
{
_exceptions = new List<Action>();
_context = null;
_dos = new List<SpecificationPrimitive>();
_observations = new List<SpecificationPrimitive>();
}
private static void EnsureThreadStaticInitialized()
{
if (_threadStaticInitialized)
return;
Reset();
_threadStaticInitialized = true;
}
public static void Context( string message, Action arrange )
{
EnsureThreadStaticInitialized();
if (_context == null)
_context = new SpecificationPrimitive( message, arrange );
else
_exceptions.Add( () => { throw new InvalidOperationException( "Cannot have more than one Context statement in a specification" ); } );
}
public static void Do( string message, Action doAction )
{
EnsureThreadStaticInitialized();
var @do = new SpecificationPrimitive( message, doAction );
_dos.Add( @do );
}
public static void Observation( string message, Action observationAction )
{
EnsureThreadStaticInitialized();
var observation = new SpecificationPrimitive( message, observationAction );
_observations.Add( observation );
}
public static IEnumerable<ITestCommand> EnumerateTestCommands( IMethodInfo method )
{
yield return new Core.ActionTestCommand( method, _context.Message, _context.ActionDelegate );
foreach (var @do in _dos)
{
// we now have a set of registered do blokcs (first level of context)
yield return new Core.ActionTestCommand( method, "\t" + @do.Message, @do.ActionDelegate );
// for this do, we now have a set of observations
foreach (var observation in _observations)
yield return new Core.ActionTestCommand( method, "\t\t" + observation.Message, observation.ActionDelegate );
_observations.Clear();
}
}
}
private class SpecificationPrimitive
{
private readonly string _message;
private readonly Action _action;
public SpecificationPrimitive( string message, Action action )
{
if (message == null)
throw new ArgumentNullException( "message" );
if (action == null)
throw new ArgumentNullException( "action" );
_message = message;
_action = action;
}
public string Message
{
get { return _message; }
}
public Action ActionDelegate
{
get { return _action; }
}
}
private class ActionTestCommand : TestCommand, ITestCommand
{
private readonly Action _action;
public ActionTestCommand( IMethodInfo method, string name, Action action )
: base( method, name, 0 )
{
_action = action;
}
public override MethodResult Execute( object testClass )
{
try
{
_action();
return new PassedResult( testMethod, DisplayName );
}
catch (Exception ex)
{
return new FailedResult( testMethod, ex, DisplayName );
}
}
}
}
internal static class SpecificationExtensions
{
/// <summary>
/// Records a disposable context for this specification. The context lifecycle will be managed by SubSpec.
/// </summary>
/// <param name="message">A message describing the established context.</param>
/// <param name="arrange">The action that will establish and return the context for this test.</param>
public static void Context( this string message, Action arrange )
{
Core.SpecificationContext.Context( message, arrange );
}
/// <summary>
/// Records an action to be performed on the context for this specification.
/// </summary>
/// <param name="message">A message describing the action.</param>
/// <param name="arrange">The action to perform.</param>
public static void Do( this string message, Action act )
{
Core.SpecificationContext.Do( message, act );
}
/// <summary>
/// Records an observation for this specification.
/// All observations are executed on the same context.
/// </summary>
/// <param name="message">A message describing the expected result.</param>
/// <param name="observation">The action that will verify the expectation.</param>
public static void Observation( this string message, Action observation )
{
Core.SpecificationContext.Observation( message, observation );
}
}
[AttributeUsage( AttributeTargets.Method, AllowMultiple = false, Inherited = true )]
internal class SimpleSpecificationAttribute : FactAttribute
{
protected override IEnumerable<ITestCommand> EnumerateTestCommands( IMethodInfo method )
{
RegisterLazyContextprimitives( method );
return Core.SpecificationContext.EnumerateTestCommands( method );
}
private static void RegisterLazyContextprimitives( IMethodInfo method )
{
if (method.IsStatic)
method.Invoke( null, null );
else
{
ConstructorInfo defaultConstructor = method.MethodInfo.ReflectedType.GetConstructor( Type.EmptyTypes );
if (defaultConstructor == null)
throw new InvalidOperationException( "Specification class does not have a default constructor" );
object instance = defaultConstructor.Invoke( null );
method.Invoke( instance, null );
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment