Created
December 20, 2012 17:39
-
-
Save JohannesRudolph/4347105 to your computer and use it in GitHub Desktop.
Simple spec. A minimalisitic specification framework for xunit.net. MS-PL License.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ) ); | |
} ); | |
} ); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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