Skip to content

Commit 5843c80

Browse files
committed
another iteration of API experiments
This relates to issue #9, also see discussion under pull request #12 and #11
1 parent bb1f773 commit 5843c80

File tree

10 files changed

+196
-40
lines changed

10 files changed

+196
-40
lines changed
Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,40 @@
11
namespace TestStack.ConventionTests.Tests
22
{
3-
using System;
4-
using ApprovalTests;
53
using ApprovalTests.Reporters;
64
using NUnit.Framework;
75
using TestAssembly;
86
using TestStack.ConventionTests.Conventions;
97

108
[TestFixture]
11-
[UseReporter(typeof(DiffReporter))]
9+
[UseReporter(typeof (DiffReporter))]
1210
public class TypeBasedConventions
1311
{
14-
readonly Type[] itemsToVerify;
12+
readonly Types nhibernateEntities;
1513

1614
public TypeBasedConventions()
1715
{
18-
itemsToVerify = typeof(SampleDomainClass).Assembly.GetTypes();
16+
// TODO: This should go to some sort of autodiscovery mechanism so users don't have to see this shit
17+
Convention.Settings.AssertInclunclusive = Assert.Inconclusive;
18+
Convention.Settings.AssertZero = (v, m) => Assert.AreEqual(0, v, m);
19+
20+
var itemsToVerify = typeof (SampleDomainClass).Assembly.GetTypes();
21+
nhibernateEntities = new Types
22+
{
23+
ApplicableTypes = itemsToVerify,
24+
HasApprovedExceptions = false
25+
};
1926
}
2027

2128
[Test]
22-
public void all_methods_are_virtual()
29+
public void all_classes_have_default_constructor()
2330
{
24-
var exception = Assert.Throws<ConventionFailedException>(() => Convention.Is<AllMethodsAreVirtual>(itemsToVerify));
25-
26-
Approvals.Verify(exception.Message);
31+
Convention.Is(new AllClassesHaveDefaultConstructor(), nhibernateEntities);
2732
}
2833

2934
[Test]
30-
public void all_classes_have_default_constructor()
35+
public void all_methods_are_virtual()
3136
{
32-
var exception = Assert.Throws<ConventionFailedException>(() => Convention.Is<AllClassesHaveDefaultConstructor>(itemsToVerify));
33-
34-
Approvals.Verify(exception.Message);
37+
Convention.Is(new AllMethodsAreVirtual(), nhibernateEntities);
3538
}
3639
}
3740
}

TestStack.ConventionTests/Convention.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,33 @@
55
using System.Linq;
66
using System.Reflection;
77
using System.Text;
8+
using ApprovalTests;
89

910
public static class Convention
1011
{
12+
public static readonly ConventionSettings Settings = new ConventionSettings();
13+
14+
public static void Is<TData>(IConvention<TData> convention, TData data)
15+
{
16+
var result = convention.Execute(data);
17+
if (result.IsConclusive == false)
18+
{
19+
Settings.AssertInclunclusive(result.Message);
20+
}
21+
else
22+
{
23+
if (result.HasExceptions)
24+
{
25+
// should we encapsulate Approvals behind Settings?
26+
Approvals.Verify(result.Message);
27+
}
28+
else
29+
{
30+
Settings.AssertZero(result.InvalidResultsCount, result.Message);
31+
}
32+
}
33+
}
34+
1135
public static void Is<T, T2>(IEnumerable<T2> itemsToVerify) where T : ConventionData<T2>, new()
1236
{
1337
Is(new T(), itemsToVerify);
@@ -95,4 +119,16 @@ public static string Result<TConvention, T, TItem>(TConvention convention, IEnum
95119
return Result(convention, itemsToVerify);
96120
}
97121
}
122+
123+
public class ConventionSettings
124+
{
125+
public ConventionSettings()
126+
{
127+
// TODO: initialize the type;
128+
}
129+
130+
public Action<String> AssertInclunclusive;
131+
132+
public Action<int, string> AssertZero;
133+
}
98134
}

TestStack.ConventionTests/ConventionData.cs

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
11
namespace TestStack.ConventionTests.Conventions
22
{
3+
using System.Linq;
34
using TestStack.ConventionTests.Helpers;
5+
using TestStack.ConventionTests.Internal;
46

5-
public class AllClassesHaveDefaultConstructor : ConventionData
7+
public class AllClassesHaveDefaultConstructor : IConvention<Types>
68
{
9+
public string HeaderMessage { get; set; }
10+
711
public AllClassesHaveDefaultConstructor()
812
{
9-
Must = type => type.HasDefaultConstructor();
10-
ItemDescription = (type, builder) =>
11-
builder.AppendLine(string.Format("{0} does not have a default constructor", type.FullName));
13+
HeaderMessage = "The following types do not have default constructor";
14+
}
15+
16+
public ConventionResult Execute(Types data)
17+
{
18+
var types = data.ApplicableTypes;
19+
if (types.None())
20+
{
21+
return ConventionResult.Inconclusive("Put sensible 'inconclusive' message here");
22+
}
23+
// NOTE: thie bit above is duplicated in both conventions we have, and will likely be duplicated in any other.
24+
// we likely will want to pull that out, leaving perhaps a property like InconclusiveMessage
25+
26+
var invalid = types.Where(t => t.HasDefaultConstructor() == false);
27+
var result = ConventionResult.For(invalid, HeaderMessage, (t, m) => m.AppendLine("\t" + t));
28+
29+
//if we switch conventionData to have an interface and put HasApprovedExceptions on it, we'll be able to pull this out as well.
30+
result.HasExceptions = data.HasApprovedExceptions;
31+
return result;
1232
}
1333
}
1434
}
Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,46 @@
11
namespace TestStack.ConventionTests.Conventions
22
{
3+
using System;
4+
using System.Collections.Generic;
35
using System.Linq;
6+
using System.Reflection;
7+
using System.Text;
48
using TestStack.ConventionTests.Helpers;
9+
using TestStack.ConventionTests.Internal;
510

6-
public class AllMethodsAreVirtual : ConventionData
11+
public class AllMethodsAreVirtual : IConvention<Types>
712
{
813
public AllMethodsAreVirtual()
914
{
10-
Must = t => t.NonVirtualMethods().None();
11-
ItemDescription = (type, builder) =>
15+
HeaderMessage = "The following methods are not virtual.";
16+
}
17+
18+
public ConventionResult Execute(Types data)
19+
{
20+
var types = data.ApplicableTypes;
21+
if (types.None())
1222
{
13-
builder.Append(type.FullName);
14-
builder.AppendLine(" has non virtual method(s):");
15-
foreach (var nonVirtual in type.NonVirtualMethods())
16-
{
17-
builder.Append('\t');
18-
builder.AppendLine(nonVirtual.Name);
19-
}
20-
};
23+
return ConventionResult.Inconclusive("Put sensible 'inconclusive' message here");
24+
}
25+
26+
// do we want to encapsulate that in some way?
27+
// also notice how data gives us types, yet the convention acts upon methods.
28+
var invalid = types.ToLookup(t => t, t => t.NonVirtualMethods()).Where(l => l.Any());
29+
var result = ConventionResult.For(invalid, HeaderMessage, DescribeTypeAndMethods);
30+
result.HasExceptions = data.HasApprovedExceptions;
31+
return result;
2132
}
33+
34+
// I like how that's encapsulated in the reusable convention type, whereas previously it was part of the convention/test code
35+
void DescribeTypeAndMethods(IGrouping<Type, IEnumerable<MethodInfo>> item, StringBuilder message)
36+
{
37+
message.AppendLine("\t" + item.Key);
38+
foreach (var method in item)
39+
{
40+
message.AppendLine("\t\t" + method);
41+
}
42+
}
43+
44+
public string HeaderMessage { get; set; }
2245
}
2346
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace TestStack.ConventionTests
2+
{
3+
using TestStack.ConventionTests.Internal;
4+
5+
public interface IConvention<T>
6+
{
7+
ConventionResult Execute(T data);
8+
}
9+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
namespace TestStack.ConventionTests.Internal
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
8+
public class ConventionResult
9+
{
10+
public string Message { get; set; }
11+
public bool IsConclusive { get; set; }
12+
// TODO: perhaps name it better so that it doesn't get confused with System.Exception and related concepts
13+
public bool HasExceptions { get; set; }
14+
public int InvalidResultsCount { get; set; }
15+
16+
public static ConventionResult For<TResult>(IEnumerable<TResult> items,
17+
string header,
18+
Action<TResult, StringBuilder> itemDescriptor)
19+
{
20+
var array = items.ToArray();
21+
var result = new ConventionResult
22+
{
23+
InvalidResultsCount = array.Length,
24+
IsConclusive = true
25+
};
26+
if (array.None())
27+
{
28+
return result;
29+
}
30+
// NOTE: we might possibly want to abstract the StringBuilder to have more high level construct that would allow us to plug rich reports here...
31+
var message = new StringBuilder(header);
32+
Array.ForEach(array, r =>
33+
{
34+
message.AppendLine();
35+
itemDescriptor(r, message);
36+
});
37+
result.Message = message.ToString();
38+
return result;
39+
}
40+
41+
public static ConventionResult Inconclusive(string message)
42+
{
43+
return new ConventionResult
44+
{
45+
Message = message,
46+
IsConclusive = false
47+
};
48+
}
49+
}
50+
}

TestStack.ConventionTests/TestStack.ConventionTests.csproj

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@
3232
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
3333
</PropertyGroup>
3434
<ItemGroup>
35+
<Reference Include="ApprovalTests">
36+
<HintPath>..\packages\ApprovalTests.3.0.01\lib\net40\ApprovalTests.dll</HintPath>
37+
</Reference>
38+
<Reference Include="ApprovalUtilities">
39+
<HintPath>..\packages\ApprovalUtilities.3.0.01\lib\net35\ApprovalUtilities.dll</HintPath>
40+
</Reference>
3541
<Reference Include="Mono.Cecil">
3642
<HintPath>..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.dll</HintPath>
3743
</Reference>
@@ -53,13 +59,15 @@
5359
<Reference Include="System.Xml" />
5460
</ItemGroup>
5561
<ItemGroup>
62+
<Compile Include="Internal\ConventionResult.cs" />
5663
<Compile Include="Conventions\AllMethodsAreVirtual.cs" />
5764
<Compile Include="ConventionFailedException.cs" />
5865
<Compile Include="Conventions\AllClassesHaveDefaultConstructor.cs" />
5966
<Compile Include="Conventions\FilesAreEmbeddedResources.cs" />
6067
<Compile Include="Helpers\AssemblyProjectLocator.cs" />
6168
<Compile Include="Convention.cs" />
62-
<Compile Include="ConventionData.cs" />
69+
<Compile Include="IConvention.cs" />
70+
<Compile Include="Types.cs" />
6371
<Compile Include="ConventionData`1.cs" />
6472
<Compile Include="Helpers\IProjectLocator.cs" />
6573
<Compile Include="Helpers\IProjectProvider.cs" />

TestStack.ConventionTests/Types.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace TestStack.ConventionTests
2+
{
3+
using System;
4+
5+
/// <summary>
6+
/// This is where we set what our convention is all about.
7+
/// </summary>
8+
// NOTE: notice no base type, no interface. Just a POCO DTO
9+
public class Types
10+
{
11+
//NOTE: that's a terrible name
12+
public Type[] ApplicableTypes { get; set; }
13+
14+
public bool HasApprovedExceptions { get; set; }
15+
}
16+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
3+
<package id="ApprovalTests" version="3.0.01" targetFramework="net40" />
4+
<package id="ApprovalUtilities" version="3.0.01" targetFramework="net40" />
35
<package id="Mono.Cecil" version="0.9.5.4" targetFramework="net40" />
46
</packages>

0 commit comments

Comments
 (0)