diff --git a/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs b/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs index 6fd3730..a72203d 100644 --- a/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs +++ b/TestStack.ConventionTests.Tests/ConventionAssertionClassTests.cs @@ -1,6 +1,5 @@ namespace TestStack.ConventionTests.Tests { - using System.Collections.Generic; using ApprovalTests.Reporters; using NUnit.Framework; using TestStack.ConventionTests.Internal; @@ -34,10 +33,9 @@ public ConventionReportFailure Format(string failingData) public class FailingConvention : IConvention { - public string ConventionTitle { get { return "Header"; } } - public IEnumerable GetFailingData(FakeData data) + public void Execute(FakeData data, IConventionResult result) { - return new[] { "Different" }; + result.Is("Header", new[] {"Different"}); } } } diff --git a/TestStack.ConventionTests/Convention.cs b/TestStack.ConventionTests/Convention.cs index 606d671..f4439af 100644 --- a/TestStack.ConventionTests/Convention.cs +++ b/TestStack.ConventionTests/Convention.cs @@ -3,11 +3,9 @@ using System; using System.Collections.Generic; using System.IO; - using System.Linq; using System.Reflection; using ApprovalTests; using ApprovalTests.Core.Exceptions; - using TestStack.ConventionTests.Conventions; using TestStack.ConventionTests.Internal; using TestStack.ConventionTests.Reporting; @@ -42,8 +40,8 @@ public static void Is(IConvention convention, TDataSou { try { - var conventionResult = Executor.GetConventionReport(convention.ConventionTitle, convention.GetFailingData(data).ToArray(), data); - Reports.Add(conventionResult); + var conventionResult = Executor.GetConventionResults(convention, data); + Reports.AddRange(conventionResult); new ConventionReportTraceRenderer().Render(conventionResult); reporter.Render(conventionResult); @@ -57,8 +55,8 @@ public static void Is(IConvention convention, TDataSou public static void IsWithApprovedExeptions(IConvention convention, TDataSource data) where TDataSource : IConventionData { - var conventionResult = Executor.GetConventionReportWithApprovedExeptions(convention.ConventionTitle, convention.GetFailingData(data).ToArray(), data); - Reports.Add(conventionResult); + var conventionResult = Executor.GetConventionResultsWithApprovedExeptions(convention, data); + Reports.AddRange(conventionResult); try { @@ -78,60 +76,6 @@ public static void IsWithApprovedExeptions(IConvention } } - public static void Is(ISymmetricConvention convention, TDataSource data) - where TDataSource : IConventionData - { - Is(convention, data, new ConventionResultExceptionReporter()); - } - - public static void Is(ISymmetricConvention convention, TDataSource data, IConventionReportRenderer reporter) - where TDataSource : IConventionData - { - try - { - var conventionResult = Executor.GetConventionReport(convention.ConventionTitle, convention.GetFailingData(data).ToArray(), data); - var inverseConventionResult = Executor.GetConventionReport(convention.InverseTitle, convention.GetFailingInverseData(data).ToArray(), data); - - Reports.Add(conventionResult); - Reports.Add(inverseConventionResult); - - new ConventionReportTraceRenderer().Render(conventionResult, inverseConventionResult); - reporter.Render(conventionResult, inverseConventionResult); - } - finally - { - HtmlRenderer.Render(Reports.ToArray()); - } - } - - public static void IsWithApprovedExeptions(ISymmetricConvention convention, TDataSource data) - where TDataSource : IConventionData - { - var conventionResult = Executor.GetConventionReportWithApprovedExeptions(convention.ConventionTitle, convention.GetFailingData(data).ToArray(), data); - var inverseConventionResult = Executor.GetConventionReportWithApprovedExeptions(convention.InverseTitle, convention.GetFailingInverseData(data).ToArray(), data); - Reports.Add(conventionResult); - Reports.Add(inverseConventionResult); - - try - { - //Render both, with approved exceptions included - var conventionReportTextRenderer = new ConventionReportTextRenderer(); - conventionReportTextRenderer.Render(conventionResult, inverseConventionResult); - Approvals.Verify(conventionReportTextRenderer.Output); - - // Trace on success - new ConventionReportTraceRenderer().Render(conventionResult, inverseConventionResult); - } - catch (ApprovalException ex) - { - throw new ConventionFailedException("Approved exceptions for convention differs\r\n\r\n" + ex.Message, ex); - } - finally - { - HtmlRenderer.Render(Reports.ToArray()); - } - } - // http://stackoverflow.com/questions/52797/c-how-do-i-get-the-path-of-the-assembly-the-code-is-in#answer-283917 static string AssemblyDirectory { diff --git a/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs b/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs index 63ee4f8..ed8647f 100644 --- a/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs +++ b/TestStack.ConventionTests/Conventions/AllClassesHaveDefaultConstructor.cs @@ -1,17 +1,15 @@ namespace TestStack.ConventionTests.Conventions { - using System.Collections.Generic; using System.Linq; using TestStack.ConventionTests.ConventionData; using TestStack.ConventionTests.Internal; public class AllClassesHaveDefaultConstructor : IConvention { - public string ConventionTitle { get { return "Types must have a default constructor"; } } - - public IEnumerable GetFailingData(Types data) + public void Execute(Types data, IConventionResult result) { - return data.TypesToVerify.Where(t => t.HasDefaultConstructor() == false); + result.Is("Types must have a default constructor", + data.TypesToVerify.Where(t => t.HasDefaultConstructor() == false)); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs b/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs index dcf659e..b6d7d1e 100644 --- a/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs +++ b/TestStack.ConventionTests/Conventions/AllMethodsAreVirtual.cs @@ -1,18 +1,14 @@ namespace TestStack.ConventionTests.Conventions { - using System.Collections.Generic; using System.Linq; - using System.Reflection; using TestStack.ConventionTests.ConventionData; using TestStack.ConventionTests.Internal; public class AllMethodsAreVirtual : IConvention { - public string ConventionTitle { get { return "Methods must be virtual"; } } - - public IEnumerable GetFailingData(Types data) + public void Execute(Types data, IConventionResult result) { - return data.TypesToVerify.SelectMany(t => t.NonVirtualMethods()); + result.Is("Methods must be virtual", data.TypesToVerify.SelectMany(t => t.NonVirtualMethods())); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs b/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs index d109d37..25da3af 100644 --- a/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs +++ b/TestStack.ConventionTests/Conventions/ClassTypeHasSpecificNamespace.cs @@ -1,8 +1,6 @@ namespace TestStack.ConventionTests.Conventions { using System; - using System.Collections.Generic; - using System.Linq; using TestStack.ConventionTests.ConventionData; /// @@ -14,7 +12,7 @@ /// /// This is a Symmetric convention, and will verify all of a Class Type lives in the namespace, but also that only that class type is in that namespace /// - public class ClassTypeHasSpecificNamespace : ISymmetricConvention + public class ClassTypeHasSpecificNamespace : IConvention { readonly Func classIsApplicable; readonly string namespaceToCheck; @@ -33,34 +31,19 @@ public ClassTypeHasSpecificNamespace(Func classIsApplicable, string this.classType = classType; } - public string ConventionTitle + public void Execute(Types data, IConventionResult result) { - get - { - return string.Format("{0}s must be under the '{1}' namespace", classType, namespaceToCheck); - } + result.IsSymmetric( + string.Format("{0}s must be under the '{1}' namespace", classType, namespaceToCheck), + string.Format("Non-{0}s must not be under the '{1}' namespace", classType, namespaceToCheck), + classIsApplicable, + TypeLivesInSpecifiedNamespace, + data.TypesToVerify); } - public string InverseTitle + bool TypeLivesInSpecifiedNamespace(Type t) { - get - { - return string.Format("Non-{0}s must not be under the '{1}' namespace", classType, namespaceToCheck); - } - } - - public IEnumerable GetFailingData(Types data) - { - return data.TypesToVerify - .Where(classIsApplicable) - .Where(t => t.Namespace == null || !t.Namespace.StartsWith(namespaceToCheck)); - } - - public IEnumerable GetFailingInverseData(Types data) - { - return data.TypesToVerify - .Where(t => !classIsApplicable(t)) - .Where(t => t.Namespace != null && t.Namespace.StartsWith(namespaceToCheck)); + return t.Namespace == null || t.Namespace.StartsWith(namespaceToCheck); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs b/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs index 88fc915..53f83af 100644 --- a/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs +++ b/TestStack.ConventionTests/Conventions/FilesAreEmbeddedResources.cs @@ -1,6 +1,5 @@ namespace TestStack.ConventionTests.Conventions { - using System.Collections.Generic; using System.Linq; using TestStack.ConventionTests.ConventionData; @@ -11,19 +10,13 @@ public FilesAreEmbeddedResources(string fileExtension) FileExtension = fileExtension; } - public string ConventionTitle - { - get - { - return string.Format("{0} Files must be embedded resources", FileExtension); - } - } - - public string FileExtension { get; set; } + public string FileExtension { get; private set; } - public IEnumerable GetFailingData(ProjectFiles data) + public void Execute(ProjectFiles data, IConventionResult result) { - return data.Files.Where(s => s.FilePath.EndsWith(FileExtension) && s.ReferenceType != "EmbeddedResource"); + result.Is( + string.Format("{0} Files must be embedded resources", FileExtension), + data.Files.Where(s => s.FilePath.EndsWith(FileExtension) && s.ReferenceType != "EmbeddedResource")); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/ISymmetricConvention.cs b/TestStack.ConventionTests/Conventions/ISymmetricConvention.cs deleted file mode 100644 index e7454bf..0000000 --- a/TestStack.ConventionTests/Conventions/ISymmetricConvention.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace TestStack.ConventionTests.Conventions -{ - using System.Collections.Generic; - - public interface ISymmetricConvention where T : IConventionData - { - string ConventionTitle { get; } - string InverseTitle { get; } - IEnumerable GetFailingData(T data); - IEnumerable GetFailingInverseData(T data); - } -} \ No newline at end of file diff --git a/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs b/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs index 04d9fdb..29eab34 100644 --- a/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs +++ b/TestStack.ConventionTests/Conventions/ProjectDoesNotReferenceDllsFromBinOrObjDirectories.cs @@ -1,6 +1,5 @@ namespace TestStack.ConventionTests.Conventions { - using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using TestStack.ConventionTests.ConventionData; @@ -9,16 +8,15 @@ public class ProjectDoesNotReferenceDllsFromBinOrObjDirectories : IConvention.*?(obj|bin).*?)$"; - static bool IsBinOrObjReference(ProjectReference reference) + public void Execute(ProjectReferences data, IConventionResult result) { - return Regex.IsMatch(reference.ReferencedPath, AssemblyReferencingObjRegex, RegexOptions.IgnoreCase); + result.Is("Project must not reference dlls from bin or obj directories", + data.References.Where(IsBinOrObjReference)); } - public string ConventionTitle { get { return "Project must not reference dlls from bin or obj directories"; } } - - public IEnumerable GetFailingData(ProjectReferences data) + static bool IsBinOrObjReference(ProjectReference reference) { - return data.References.Where(IsBinOrObjReference); + return Regex.IsMatch(reference.ReferencedPath, AssemblyReferencingObjRegex, RegexOptions.IgnoreCase); } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/IConvention.cs b/TestStack.ConventionTests/IConvention.cs index 468fdf6..641dc53 100644 --- a/TestStack.ConventionTests/IConvention.cs +++ b/TestStack.ConventionTests/IConvention.cs @@ -1,10 +1,7 @@ namespace TestStack.ConventionTests { - using System.Collections.Generic; - public interface IConvention where T : IConventionData { - string ConventionTitle { get; } - IEnumerable GetFailingData(T data); + void Execute(T data, IConventionResult result); } } \ No newline at end of file diff --git a/TestStack.ConventionTests/IConventionResult.cs b/TestStack.ConventionTests/IConventionResult.cs new file mode 100644 index 0000000..9442528 --- /dev/null +++ b/TestStack.ConventionTests/IConventionResult.cs @@ -0,0 +1,46 @@ +namespace TestStack.ConventionTests +{ + using System; + using System.Collections.Generic; + + public interface IConventionResult + { + void Is(string resultTitle, IEnumerable failingData); + + /// + /// A symmetric convention is a convention which also can be applied in reverse. For example + /// All dto's live in Project.Dto namespace AND Only dto's live in Project.Dto + /// This means if a DTO is outside of Project.Dto, the test will fail, + /// and if a non-dto is in Project.Dto the test will also fail + /// + /// The data type the convention is applied to + /// Title of the convention, i.e Dto's must live in Project.Dto namespace + /// Data failing to conform to the convention + /// The inverse scenario title, i.e Non-dtos must not live inside Project.Dto namespace + /// Data failing to conform to the inverse of the convention + void IsSymmetric( + string conventionResultTitle, IEnumerable conventionFailingData, + string inverseResultTitle, IEnumerable inverseFailingData); + + /// + /// A symmetric convention is a convention which also can be applied in reverse. For example + /// All dto's live in Project.Dto namespace AND Only dto's live in Project.Dto + /// This means if a DTO is outside of Project.Dto, the test will fail, + /// and if a non-dto is in Project.Dto the test will also fail + /// + /// This overload allows you to work with sets, see .... + /// + /// The data type the convention is applied to + /// Title of the convention, i.e Dto's must live in Project.Dto namespace + /// The inverse scenario title, i.e Non-dtos must not live inside Project.Dto namespace + /// All data, for dto example, all types in the project, not just dto's + /// Predicate defining data which is in the first set + /// Predicate defining data which is in the second set + void IsSymmetric( + string firstSetFailureTitle, + string secondSetFailureTitle, + Func isPartOfFirstSet, + Func isPartOfSecondSet, + IEnumerable allData); + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests/Internal/ConventionResult.cs b/TestStack.ConventionTests/Internal/ConventionResult.cs new file mode 100644 index 0000000..53d1112 --- /dev/null +++ b/TestStack.ConventionTests/Internal/ConventionResult.cs @@ -0,0 +1,79 @@ +namespace TestStack.ConventionTests.Internal +{ + using System; + using System.Collections.Generic; + using System.Linq; + using TestStack.ConventionTests.Reporting; + + public class ConventionResult : IConventionResult + { + readonly List conventionResults; + readonly string dataDescription; + + public ConventionResult(string dataDescription) + { + this.dataDescription = dataDescription; + conventionResults = new List(); + } + + public ResultInfo[] ConventionResults + { + get { return conventionResults.ToArray(); } + } + + public void Is(string resultTitle, IEnumerable failingData) + { + // ReSharper disable PossibleMultipleEnumeration + conventionResults.Add(new ResultInfo( + failingData.None() ? TestResult.Passed : TestResult.Failed, + resultTitle, + dataDescription, + failingData.Select(FormatData).ToArray())); + } + + public void IsSymmetric( + string conventionResultTitle, IEnumerable conventionFailingData, + string inverseResultTitle, IEnumerable inverseFailingData) + { + conventionResults.Add(new ResultInfo( + conventionFailingData.None() ? TestResult.Passed : TestResult.Failed, + conventionResultTitle, + dataDescription, + conventionFailingData.Select(FormatData).ToArray())); + conventionResults.Add(new ResultInfo( + inverseFailingData.None() ? TestResult.Passed : TestResult.Failed, + inverseResultTitle, + dataDescription, + inverseFailingData.Select(FormatData).ToArray())); + } + + public void IsSymmetric( + string firstSetFailureTitle, + string secondSetFailureTitle, + Func isPartOfFirstSet, + Func isPartOfSecondSet, + IEnumerable allData) + { + var firstSetFailingData = allData.Where(isPartOfFirstSet).Where(d => !isPartOfSecondSet(d)); + var secondSetFailingData = allData.Where(d => !isPartOfFirstSet(d)).Where(isPartOfSecondSet); + + IsSymmetric( + firstSetFailureTitle, firstSetFailingData, + secondSetFailureTitle, secondSetFailingData); + } + + static ConventionReportFailure FormatData(T failingData) + { + var formatter = Convention.Formatters.FirstOrDefault(f => f.CanFormat(failingData)); + + if (formatter == null) + { + throw new NoDataFormatterFoundException( + typeof (T).Name + + " has no formatter, add one with `Convention.Formatters.Add(new MyDataFormatter());`"); + } + + return formatter.Format(failingData); + } + } +} \ No newline at end of file diff --git a/TestStack.ConventionTests/Internal/Executor.cs b/TestStack.ConventionTests/Internal/Executor.cs index c054e29..a39c3cf 100644 --- a/TestStack.ConventionTests/Internal/Executor.cs +++ b/TestStack.ConventionTests/Internal/Executor.cs @@ -1,45 +1,41 @@ namespace TestStack.ConventionTests.Internal { - using System.Linq; + using System; using TestStack.ConventionTests.Conventions; using TestStack.ConventionTests.Reporting; public static class Executor { - public static ResultInfo GetConventionReport(string conventionTitle, object[] failingData, IConventionData data) + public static ResultInfo[] GetConventionResults(IConvention convention, TDataSource data) + where TDataSource : IConventionData { if (!data.HasData) throw new ConventionSourceInvalidException(string.Format("{0} has no data", data.Description)); - var passed = failingData.None(); + var resultGatherer = new ConventionResult(data.Description); + convention.Execute(data, resultGatherer); - var conventionResult = new ResultInfo( - passed ? TestResult.Passed : TestResult.Failed, - conventionTitle, - data.Description, - failingData.Select(FormatData).ToArray()); - return conventionResult; + return resultGatherer.ConventionResults; } - public static ResultInfo GetConventionReportWithApprovedExeptions(string conventionTitle, object[] failingData, IConventionData data) + public static ResultInfo[] GetConventionResultsWithApprovedExeptions( + IConvention convention, TDataSource data) + where TDataSource : IConventionData { - var conventionResult = Executor.GetConventionReport(conventionTitle, failingData, data); var conventionReportTextRenderer = new ConventionReportTextRenderer(); // Add approved exceptions to report - conventionReportTextRenderer.RenderItems(conventionResult); - conventionResult.WithApprovedException(conventionReportTextRenderer.Output); - - return conventionResult; - } - - static ConventionReportFailure FormatData(T failingData) - { - var formatter = Convention.Formatters.FirstOrDefault(f => f.CanFormat(failingData)); + if (!data.HasData) + throw new ConventionSourceInvalidException(string.Format("{0} has no data", data.Description)); - if (formatter == null) - throw new NoDataFormatterFoundException(typeof(T).Name + " has no formatter, add one with `Convention.Formatters.Add(new MyDataFormatter());`"); + var resultGatherer = new ConventionResult(data.Description); + convention.Execute(data, resultGatherer); + foreach (var conventionResult in resultGatherer.ConventionResults) + { + conventionReportTextRenderer.RenderItems(conventionResult); + conventionResult.WithApprovedException(conventionReportTextRenderer.Output); + } - return formatter.Format(failingData); + return resultGatherer.ConventionResults; } } } \ No newline at end of file diff --git a/TestStack.ConventionTests/TestStack.ConventionTests.csproj b/TestStack.ConventionTests/TestStack.ConventionTests.csproj index 8235b14..3e8b083 100644 --- a/TestStack.ConventionTests/TestStack.ConventionTests.csproj +++ b/TestStack.ConventionTests/TestStack.ConventionTests.csproj @@ -61,7 +61,9 @@ + + @@ -75,7 +77,6 @@ -