diff --git a/UnitTestProject1/UnitTestProject1.csproj b/NSpec.Runner.Tests/NSpec.Runner.Tests.csproj similarity index 95% rename from UnitTestProject1/UnitTestProject1.csproj rename to NSpec.Runner.Tests/NSpec.Runner.Tests.csproj index 16fd2fc..11644dd 100644 --- a/UnitTestProject1/UnitTestProject1.csproj +++ b/NSpec.Runner.Tests/NSpec.Runner.Tests.csproj @@ -6,8 +6,8 @@ {69395747-4A18-4A09-92F9-EED4032F7677} Library Properties - UnitTestProject1 - UnitTestProject1 + NSpec.Runner.Tests + NSpec.Runner.Tests v4.5 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -59,8 +59,8 @@ - - + + @@ -70,7 +70,9 @@ - + + Designer + diff --git a/NSpec.Runner.Tests/NSpecRunnerTest.cs b/NSpec.Runner.Tests/NSpecRunnerTest.cs new file mode 100644 index 0000000..dd4480b --- /dev/null +++ b/NSpec.Runner.Tests/NSpecRunnerTest.cs @@ -0,0 +1,87 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSpec; +using NSpec.Runner; + +namespace NSpec.Runner.Tests +{ + [TestClass] + public class NSpecRunnerTest : nspec + { + + [TestMethod, TestSpecification] + public void When_Writing_A_Test_Spec() + { + bool theTruth = false; + + context["and when dealing with other people,"] = () => + { + before = () => theTruth = true; + it["it will be honest."] = () => theTruth.should_be(true); + it["it will open doors for old ladies."] = () => theTruth.should_be(true); + + context["and when dealing with old dudes that are grumpy,"] = () => + { + before = () => theTruth = false; + + it["it will tell a knock knock joke."] = () => theTruth.should_be(false); + }; + + context["and when dealing with old dudes that are not grumpy,"] = () => + { + it["it will give them a high five."] = () => theTruth.should_be(true); + }; + }; + } + + [TestMethod, TestSpecification] + public void When_Writing_A_Test_Spec_With_Failues() + { + bool theTruth = false; + + context["and when dealing with other people,"] = () => + { + before = () => theTruth = false; + it["it will be honest."] = () => theTruth.should_be(true); + it["it will open doors for old ladies."] = () => theTruth.should_be(true); + + context["and when dealing with old dudes that are grumpy,"] = () => + { + before = () => theTruth = true; + + it["it will tell a knock knock joke."] = () => theTruth.should_be(false); + }; + + context["and when dealing with old dudes that are not grumpy,"] = () => + { + it["it will give them a high five."] = () => theTruth.should_be(true); + }; + }; + } + + [TestMethod, TestSpecification] + public void When_Writing_A_Test_Spec_With_Pendings() + { + context["and you haven't finished the specs"] = () => + { + it["you should declare them as TODO"] = todo; + it["and the test result should be inconclusiv"] = () => { }; + it["and the output should contain Pending = 1"] = () => { }; + }; + } + + [TestMethod, TestSpecification, Ignore] + public void When_Writing_A_Test_Spec_With_Ingnore_Attribute() + { + it["it should be ignored"] = () => { Assert.Fail("it should be ignored"); }; + } + + [TestMethod, TestSpecification(FailFast=true)] + public void When_Writing_A_Test_Spec_With_FailFast() + { + it["it should fail on first assertation"] = () => { 1.is_greater_than(2); }; + it["and not here"] = () => { 2.is_less_than(1); Assert.Fail("this should not be called"); }; + } + + } +} diff --git a/UnitTestProject1/Properties/AssemblyInfo.cs b/NSpec.Runner.Tests/Properties/AssemblyInfo.cs similarity index 93% rename from UnitTestProject1/Properties/AssemblyInfo.cs rename to NSpec.Runner.Tests/Properties/AssemblyInfo.cs index 7de63fb..51488d6 100644 --- a/UnitTestProject1/Properties/AssemblyInfo.cs +++ b/NSpec.Runner.Tests/Properties/AssemblyInfo.cs @@ -5,11 +5,11 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("UnitTestProject1")] +[assembly: AssemblyTitle("NSpec.Runner.Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("UnitTestProject1")] +[assembly: AssemblyProduct("NSpec.Runner.Tests")] [assembly: AssemblyCopyright("Copyright © 2014")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/NSpec.Runner.Tests/TestSpecification.cs b/NSpec.Runner.Tests/TestSpecification.cs new file mode 100644 index 0000000..5760bf9 --- /dev/null +++ b/NSpec.Runner.Tests/TestSpecification.cs @@ -0,0 +1,49 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Reflection; + +namespace NSpec.Runner.Tests +{ + [Serializable, TestClass] + public class TestSpecification : global::NSpec.Runner.TestSpecification + { + public static TestContext TestContext { get; protected set; } + + [AssemblyInitialize] + public static void AssemblyInit(TestContext context) + { + // by marking this class as TestClass and adding an AssemblyInitialize attribut + // we can reliably determine if run via Test Explorer (or hopefully any other MsTest compatible runner) + // + // if TestContext != null Enabled will be false + // and test will be processed by console runner + TestContext = context; + } + + public override void ProcessResults(NSpecResults results) + { + if (results.HasFailures) + Assert.Fail(results.Message); + else if (results.HasPendings) + Assert.Inconclusive(results.Message); + } + + public override bool ShouldIgnore(MethodBase method) + { + return method.GetCustomAttribute() != null; + } + + public override bool Enabled + { + get + { + return TestContext != null; + } + } + + } +} diff --git a/UnitTestProject1/packages.config b/NSpec.Runner.Tests/packages.config similarity index 100% rename from UnitTestProject1/packages.config rename to NSpec.Runner.Tests/packages.config diff --git a/NSpec.Runner.sln b/NSpec.Runner.sln index a589fc7..2a4822d 100644 --- a/NSpec.Runner.sln +++ b/NSpec.Runner.sln @@ -1,11 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NSpec.Runner", "NSpec.Runner\NSpec.Runner.csproj", "{4FD94255-9A5E-4B63-8F82-337F4FF0FC24}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestProject1", "UnitTestProject1\UnitTestProject1.csproj", "{69395747-4A18-4A09-92F9-EED4032F7677}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NSpec.Runner.Tests", "NSpec.Runner.Tests\NSpec.Runner.Tests.csproj", "{69395747-4A18-4A09-92F9-EED4032F7677}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -25,4 +25,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(CodealikeProperties) = postSolution + SolutionGuid = 651d86ea-12f9-4d34-920b-903e59654049 + EndGlobalSection EndGlobal diff --git a/NSpec.Runner/NSpec.Runner.csproj b/NSpec.Runner/NSpec.Runner.csproj index 935d5af..4ba0053 100644 --- a/NSpec.Runner/NSpec.Runner.csproj +++ b/NSpec.Runner/NSpec.Runner.csproj @@ -51,6 +51,7 @@ + @@ -60,6 +61,11 @@ + + + nunit.framework.dll + + diff --git a/NSpec.Runner/NSpecResult.cs b/NSpec.Runner/NSpecResult.cs new file mode 100644 index 0000000..f98d168 --- /dev/null +++ b/NSpec.Runner/NSpecResult.cs @@ -0,0 +1,37 @@ +using NSpec.Domain; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NSpec.Runner +{ + [Serializable] + public class NSpecResults + { + private readonly string message; + private readonly ContextCollection results; + + internal NSpecResults(ContextCollection results) + { + this.results = results; + this.message = String.Format("{0} Examples, {1} Failed, {2} Pending", + results.Examples().Count(), + results.Failures().Count(), + results.Pendings().Count() + ); + } + + + public bool HasFailures { get { return results.Failures().Any(); } } + + public bool HasPendings { get { return results.Pendings().Any(); } } + + public bool HasExamples { get { return results.Examples().Any(); } } + + public string Message { get { return message; } } + + } + +} diff --git a/NSpec.Runner/TestFailedException.cs b/NSpec.Runner/TestFailedException.cs index 975b206..884d6d7 100644 --- a/NSpec.Runner/TestFailedException.cs +++ b/NSpec.Runner/TestFailedException.cs @@ -4,5 +4,7 @@ namespace NSpec.Runner { public class TestFailedException : Exception { + public TestFailedException(string message) : base(message) { } + public TestFailedException(string message, Exception innerException) : base(message, innerException) { } } } diff --git a/NSpec.Runner/TestSpecification.cs b/NSpec.Runner/TestSpecification.cs index 87846fe..45d98b4 100644 --- a/NSpec.Runner/TestSpecification.cs +++ b/NSpec.Runner/TestSpecification.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Xml.Serialization; @@ -19,11 +20,38 @@ public class TestSpecification : MethodInterceptionAspect [XmlArrayItem(Type = typeof(MethodBase))] [XmlArray] private static List methodsThatHaveBeenPrepared = new List(); + + static TestSpecification() + { + // NSpec.AssertionExtensions methods are wrappers for nunit assertations but NSpec has no direct dependency on nunit.framework itself. + // That's why we include nunit.framework.dll from packages\nspec.1.0.5\tools as an embedded resource + // and use AssemblyResolve event to dynamically load it at runtime + // otherwise specs would wile with a FileNotFoundException + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + } + + private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + // https://blogs.msdn.microsoft.com/microsoft_press/2010/02/03/jeffrey-richter-excerpt-2-from-clr-via-c-third-edition/ + var assemblyName = new AssemblyName(args.Name); + String resourceName = "NSpec.Runner." + assemblyName.Name + ".dll"; + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) + { + if (stream == null) return null; + Byte[] assemblyData = new Byte[stream.Length]; + stream.Read(assemblyData, 0, assemblyData.Length); + return Assembly.Load(assemblyData); + } + } public override void OnInvoke(MethodInterceptionArgs args) { + + // skip ignored specs + if (ShouldIgnore(args.Method)) return; + // if the method has been prepared, we need to actually execute the function - if (methodsThatHaveBeenPrepared.Contains(args.Method)) + if (methodsThatHaveBeenPrepared.Contains(args.Method) || !Enabled) { base.OnInvoke(args); return; @@ -35,7 +63,8 @@ public override void OnInvoke(MethodInterceptionArgs args) // prepare the npsec content of the function var finder = new SingleClassGetter(args.Instance.GetType()); var builder = new MethodContextBuilder(finder, new DefaultConventions()); - var runner = new MethodContextRunner(builder, new ConsoleFormatter(), false); + var formatter = new ConsoleFormatter(); // { WriteLineDelegate = value => Console.WriteLine(Regex.Replace(value, @"\r(?!\n)", Environment.NewLine)) }; // custom formatter to replace \r with newline + var runner = new MethodContextRunner(builder, formatter, this.FailFast); // set up the contexts for the method var methodInfo = args.Method as MethodInfo; @@ -45,13 +74,50 @@ public override void OnInvoke(MethodInterceptionArgs args) var builtContexts = contexts.Build(); var results = runner.Run(builtContexts); + // wrap result and handle + var nspecResults = new NSpecResults(results); + ProcessResults(nspecResults); + + } + + /// + /// Override to add Test Framework specific result handling. + /// + /// + public virtual void ProcessResults(NSpecResults results) + { // if there were any failures, raise the exception so that the test framework has an error - if (results.Failures().Any()) - { - throw new TestFailedException(); - } + if (results.HasFailures) + throw new TestFailedException(results.Message); // tests all passed } + + /// + /// Determine if a certain test should be ignored + /// + /// + /// + public virtual bool ShouldIgnore(MethodBase method) + { + return false; + } + + /// + /// If false specs are not intercepted. Defaults to true, unless running from NSpecRunner.exe + /// + public virtual bool Enabled + { + get + { + return !Process.GetCurrentProcess().ProcessName.Equals("NSpecRunner", StringComparison.OrdinalIgnoreCase); + } + } + + /// + /// Configue if test should fail fast or not. Only applies if run if Enabled + /// + public virtual bool FailFast { get; set; } + } } diff --git a/UnitTestProject1/UnitTest1.cs b/UnitTestProject1/UnitTest1.cs deleted file mode 100644 index c5348ea..0000000 --- a/UnitTestProject1/UnitTest1.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NSpec; -using NSpec.Runner; - -namespace UnitTestProject1 -{ - [TestClass] - public class MSTest1 : nspec - { - private string something = String.Empty; - - [TestMethod, TestSpecification] - public void When_Writing_A_Test_Spec() - { - string theTruth = String.Empty; - - context["and when dealing with other people,"] = () => - { - before = () => theTruth = "true"; - it["it will be honest."] = () => Assert.AreEqual(theTruth, "true"); - it["it will open doors for old ladies."] = () => Assert.AreEqual(theTruth, "true"); - - context["and when dealing with old dudes that are grumpy,"] = () => - { - before = () => theTruth = "false"; - - it["it will tell a knock knock joke."] = () => Assert.AreEqual(theTruth, "false"); - }; - - context["and when dealing with old dudes that are not grumpy,"] = () => - { - it["it will give them a high five."] = () => Assert.AreEqual(theTruth, "true"); - }; - }; - } - } -}