/// <summary> /// Makes an assertion about an exception raised by the previous action. /// </summary> /// <param name="description">A string describing the assertion being made. This should be a short sentence /// that starts with 'it ...', but eliding the 'it'. For example, "puts the lotion in the bucket" would be an /// acceptable value.</param> /// <param name="assertion">The assertion to be made. It should throw an <see cref="AssertionFailedException"/> /// if it fails.</param> /// <returns>A spec under which additional actions and assertions can be made.</returns> /// <remarks>This assertion has the side effect of making the preceeding action no longer fail when an /// exception is thrown.</remarks> public XSpec TheException(string description, Func <Exception, bool> assertion) { if (description == null) { throw new ArgumentNullException("description"); } if (assertion == null) { throw new ArgumentNullException("assertion"); } XSpec parent = null; XSpec newNode = AddNode( description, () => { if (parent.exception == null) { throw new AssertFailedException("No exception was thrown."); } Assert.IsTrue(assertion(parent.exception)); }, NodeType.TheException, NodeType.It); parent = newNode.parent; parent.swallowExceptions = true; return(newNode); }
static void Run(XSpec root) { var stack = new List <XSpec>(); List <XSpec[]> tests = root.GatherTests(stack).ToList(); for (int i = 0; i < tests.Count; i++) { XSpec[] test = tests[i]; for (int j = 0; j < test.Length; j++) { if (!test[j].Exec()) { break; } } } var builder = new StringBuilder(); bool passed = root.Report(builder, 0); if (!passed) { throw new AssertFailedException("\r\n" + builder.ToString()); } }
XSpec(string description, Action action, XSpec parent, NodeType type) { this.action = action; this.description = description; this.parent = parent; this.type = type; }
/// <summary> /// Asserts that the previous action should throw an exception of the specified type. /// </summary> /// <typeparam name="TException">The type of exception that should have been thrown.</typeparam> /// <returns>A spec under which additional actions and assertions can be made.</returns> /// <remarks>This assertion has the side effect of making the preceeding action no longer fail when an /// exception is thrown; instead, the exception will be recorded and this assertion will make sure it was of /// the specified type.</remarks> /// <example> /// <code> ///[TestMethod] ///public void WhenIncrementingAnInteger() ///{ /// int x = 0; /// XSpec.Given( "an integer, set to zero", () => x = 0 ) /// .When( "the integer is incremented", () => x++ ) /// .It( "can be incremented again to two", () => { x++; Assert.AreEqual( x, 2 ); } ) /// .It( "should be 1", () => x == 1 ) /// .When( "the integer is incremented again", () => x++ ) /// .It( "should be 2", () => x == 2 ) /// .When( "the integer is divided by zero", () => x = x / ( x - 2 ) ) /// .ItShouldThrow<DivideByZeroException>() /// .Go(); ///} /// </code> /// </example> public XSpec ItShouldThrow <TException>() { XSpec parent = null; XSpec newNode = AddNode( "should throw " + typeof(TException).Name, () => { if (parent.exception == null) { throw new AssertFailedException("No exception was thrown."); } Exception exception = parent.exception; AggregateException aggregate = parent.exception as AggregateException; if (aggregate != null) { exception = aggregate.Flatten().InnerException; } Assert.IsInstanceOfType(exception, typeof(TException)); }, NodeType.It); parent = newNode.parent; parent.swallowExceptions = true; return(newNode); }
/// <summary> /// Executes the specification, in a less-isolated fashion. /// </summary> /// <exception cref="AssertionFailedException">The specification validation has failed.</exception> /// <remarks>A log of the execution is written to stdout. /// <para>This is a much faster way to run tests than <see cref="GoIsolated"/>; instead of isolating each /// assertion, this execution mode runs each step until the first failure; it then backs up and runs everything /// up to the first failure, skipping any assertions along the way. The upshot is that if an assertion fails, /// we re-run the <see cref="Given"/>s and <see cref="Where"/>s to re-establish context, and then continue on. /// </para> /// <para>This has the quadratic run time when all the assertions fail, but a linear run time when they all /// pass, which is most of the time.</para> /// </remarks> public void GoQuick() { XSpec root = this; while (root.parent != null) { root = root.parent; } RunQuick(root); }
/// <summary> /// Executes the specification. /// </summary> /// <exception cref="AssertionFailedException">The specification validation has failed.</exception> /// <remarks>A log of the execution is written to stdout.</remarks> /// <example> /// <code> ///[TestMethod] ///public void WhenIncrementingAnInteger() ///{ /// int x = 0; /// XSpec.Given( "an integer, set to zero", () => x = 0 ) /// .When( "the integer is incremented", () => x++ ) /// .It( "can be incremented again to two", () => { x++; Assert.AreEqual( x, 2 ); } ) /// .It( "should be 1", () => x == 1 ) /// .When( "the integer is incremented again", () => x++ ) /// .It( "should be 2", () => x == 2 ) /// .When( "the integer is divided by zero", () => x = x / ( x - 2 ) ) /// .ItShouldThrow<DivideByZeroException>() /// .Go(); ///} /// </code> /// </example> public void GoIsolated() { XSpec root = this; while (root.parent != null) { root = root.parent; } Run(root); }
/// <summary> /// Adds a new node to the this spec, with an explicit precedence override. The new node may or may not be a /// child of this one. /// </summary> /// <param name="description">The description associated with the node.</param> /// <param name="action">The action associated with the node.</param> /// <param name="type">The type of the node.</param> /// <param name="precedence">The type to use as the precedence of this node. This parameter exists specifically /// so that 'TheException' and 'It' nodes can be peers in the tree, rather than one being the child of the /// other.</param> /// <returns>The new node.</returns> XSpec AddNode(string description, Action action, NodeType type, NodeType precedence) { XSpec parent = this; while (parent.type >= precedence) { parent = parent.parent; } var node = new XSpec(description, action, parent, type); parent.children.Add(node); return(node); }
static void RunQuick(XSpec root) { XSpec[] tests = root.GatherTestsQuick().ToArray(); int end = 0; while (end < tests.Length) { // First, fast-forward, resetting state until we get up to just past our last failure. bool failedPrereqs = false; for (int i = 0; i < end; i++) { if (tests[i].type > NodeType.When) { continue; } if (!tests[i].Exec()) { failedPrereqs = true; break; } } if (failedPrereqs) { break; } for ( ; end < tests.Length; end++) { if (!tests[end].Exec()) { break; } } end++; // Skip forward, so we can make progress. } var builder = new StringBuilder(); bool passed = root.Report(builder, 0); if (!passed) { throw new AssertFailedException("\r\n" + builder.ToString()); } }