Skip cases #24

Merged
merged 3 commits into from Nov 30, 2013
+251 −9
View
4 src/Fixie.Samples/Fixie.Samples.csproj
@@ -64,6 +64,10 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Shuffle\CustomConvention.cs" />
<Compile Include="Shuffle\OrderTests.cs" />
+ <Compile Include="Skipped\SkipAttribute.cs" />
+ <Compile Include="Skipped\SkipClassTests.cs" />
+ <Compile Include="Skipped\CustomConvention.cs" />
+ <Compile Include="Skipped\SkipMethodTests.cs" />
<Compile Include="StringBuilderExtensions.cs" />
<Compile Include="xUnitStyle\CalculatorTests.cs" />
<Compile Include="xUnitStyle\CustomConvention.cs" />
View
25 src/Fixie.Samples/Skipped/CustomConvention.cs
@@ -0,0 +1,25 @@
+using System;
+using Fixie.Conventions;
+
+namespace Fixie.Samples.Skipped
+{
+ public class CustomConvention : Convention
+ {
+ public CustomConvention()
+ {
+ Classes
+ .Where(type => type.IsInNamespace(GetType().Namespace))
+ .NameEndsWith("Tests");
+
+ Methods
+ .Where(method => method.IsVoid());
+
+ CaseExecution
+ .Skip(@case => @case.Method.HasOrInherits<SkipAttribute>() || @case.Method.DeclaringType.HasOrInherits<SkipAttribute>());
+
+ ClassExecution
+ .CreateInstancePerTestClass()
+ .SortCases((caseA, caseB) => String.Compare(caseA.Name, caseB.Name, StringComparison.Ordinal));
+ }
+ }
+}
View
7 src/Fixie.Samples/Skipped/SkipAttribute.cs
@@ -0,0 +1,7 @@
+using System;
+
+namespace Fixie.Samples.Skipped
+{
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
+ public class SkipAttribute : Attribute { }
+}
View
13 src/Fixie.Samples/Skipped/SkipClassTests.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Fixie.Samples.Skipped
+{
+ [Skip]
+ public class SkipClassTests
+ {
+ public void ShouldNotBeCalled()
+ {
+ throw new Exception("This test should be skipped.");
+ }
+ }
+}
View
47 src/Fixie.Samples/Skipped/SkipMethodTests.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Text;
+using Should;
+
+namespace Fixie.Samples.Skipped
+{
+ public class SkipMethodTests : IDisposable
+ {
+ Calculator calculator;
+ readonly StringBuilder log;
+
+ public SkipMethodTests()
+ {
+ calculator = new Calculator();
+ log = new StringBuilder();
+ log.WhereAmI();
+ }
+
+ public void ShouldAdd()
+ {
+ log.WhereAmI();
+ calculator.Add(2, 3).ShouldEqual(5);
+ }
+
+ [Skip]
+ public void ShouldNotBeCalled()
+ {
+ throw new Exception("This test should be skipped.");
+ }
+
+ public void ShouldSubtract()
+ {
+ log.WhereAmI();
+ calculator.Subtract(5, 3).ShouldEqual(2);
+ }
+
+ public void Dispose()
+ {
+ log.WhereAmI();
+ log.ShouldHaveLines(
+ ".ctor",
+ "ShouldAdd",
+ "ShouldSubtract",
+ "Dispose");
+ }
+ }
+}
View
9 src/Fixie.TestDriven/TestDrivenListener.cs
@@ -16,6 +16,15 @@ public void AssemblyStarted(Assembly assembly)
{
}
+ public void CaseSkipped(Case @case)
+ {
+ tdnet.TestFinished(new TestResult
+ {
+ Name = @case.Name,
+ State = TestState.Ignored
+ });
+ }
+
public void CasePassed(PassResult result)
{
var @case = result.Case;
View
15 src/Fixie.Tests/Conventions/ConventionTests.cs
@@ -161,12 +161,13 @@ public void ShouldExecuteAllCasesInAllDiscoveredTestClasses()
var listener = new StubListener();
var convention = new SelfTestConvention();
- convention.Execute(listener, typeof(SampleIrrelevantClass), typeof(PassTestClass), typeof(int), typeof(PassFailTestClass));
+ convention.Execute(listener, typeof(SampleIrrelevantClass), typeof(PassTestClass), typeof(int), typeof(PassFailTestClass), typeof(SkipTestClass));
listener.Entries.ShouldEqual("Fixie.Tests.Conventions.ConventionTests+PassTestClass.PassA passed.",
"Fixie.Tests.Conventions.ConventionTests+PassTestClass.PassB passed.",
"Fixie.Tests.Conventions.ConventionTests+PassFailTestClass.Fail failed: 'Fail' failed!",
- "Fixie.Tests.Conventions.ConventionTests+PassFailTestClass.Pass passed.");
+ "Fixie.Tests.Conventions.ConventionTests+PassFailTestClass.Pass passed.",
+ "Fixie.Tests.Conventions.ConventionTests+SkipTestClass.Skip skipped.");
}
public void ShouldAllowRandomShufflingOfCaseExecutionOrder()
@@ -178,12 +179,13 @@ public void ShouldAllowRandomShufflingOfCaseExecutionOrder()
.CreateInstancePerTestClass()
.ShuffleCases(new Random(1));
- convention.Execute(listener, typeof(SampleIrrelevantClass), typeof(PassTestClass), typeof(int), typeof(PassFailTestClass));
+ convention.Execute(listener, typeof(SampleIrrelevantClass), typeof(PassTestClass), typeof(int), typeof(PassFailTestClass), typeof(SkipTestClass));
listener.Entries.ShouldEqual("Fixie.Tests.Conventions.ConventionTests+PassTestClass.PassB passed.",
"Fixie.Tests.Conventions.ConventionTests+PassTestClass.PassA passed.",
"Fixie.Tests.Conventions.ConventionTests+PassFailTestClass.Fail failed: 'Fail' failed!",
- "Fixie.Tests.Conventions.ConventionTests+PassFailTestClass.Pass passed.");
+ "Fixie.Tests.Conventions.ConventionTests+PassFailTestClass.Pass passed.",
+ "Fixie.Tests.Conventions.ConventionTests+SkipTestClass.Skip skipped.");
}
class SampleIrrelevantClass
@@ -203,5 +205,10 @@ class PassFailTestClass
public void Pass() { }
public void Fail() { throw new FailureException(); }
}
+
+ class SkipTestClass
+ {
+ public void Skip() { throw new ShouldBeUnreachableException(); }
+ }
}
}
View
68 src/Fixie.Tests/Lifecycle/ConstructionTests.cs
@@ -150,6 +150,74 @@ public void ShouldFailAllCasesWhenConstructingPerTestClassAndCustomFactoryThrows
"Factory");
}
+ public void ShouldSkipConstructingPerCaseWhenAllCasesSkipped()
+ {
+ Convention.ClassExecution
+ .CreateInstancePerCase();
+
+ Convention.CaseExecution
+ .Skip(x => true);
+
+ var output = Run();
+
+ output.ShouldHaveResults(
+ "SampleTestClass.Pass skipped.",
+ "SampleTestClass.Fail skipped.");
+
+ output.ShouldHaveLifecycle();
+ }
+
+ public void ShouldSkipConstructingPerTestClassWhenAllCasesSkipped()
+ {
+ Convention.ClassExecution
+ .CreateInstancePerTestClass();
+
+ Convention.CaseExecution
+ .Skip(x => true);
+
+ var output = Run();
+
+ output.ShouldHaveResults(
+ "SampleTestClass.Pass skipped.",
+ "SampleTestClass.Fail skipped.");
+
+ output.ShouldHaveLifecycle();
+ }
+
+ public void ShouldSkipConstructingPerCaseUsingCustomFactoryWhenAllCasesSkipped()
+ {
+ Convention.ClassExecution
+ .CreateInstancePerCase(Factory);
+
+ Convention.CaseExecution
+ .Skip(x => true);
+
+ var output = Run();
+
+ output.ShouldHaveResults(
+ "SampleTestClass.Pass skipped.",
+ "SampleTestClass.Fail skipped.");
+
+ output.ShouldHaveLifecycle();
+ }
+
+ public void ShouldSkipConstructingPerTestClassUsingCustomFactoryWhenAllCasesSkipped()
+ {
+ Convention.ClassExecution
+ .CreateInstancePerTestClass(Factory);
+
+ Convention.CaseExecution
+ .Skip(x => true);
+
+ var output = Run();
+
+ output.ShouldHaveResults(
+ "SampleTestClass.Pass skipped.",
+ "SampleTestClass.Fail skipped.");
+
+ output.ShouldHaveLifecycle();
+ }
+
static object Factory(Type testClass)
{
WhereAmI();
View
3 src/Fixie.Tests/Listeners/ConsoleListenerTests.cs
@@ -22,6 +22,7 @@ public void ShouldReportResultsToTheConsole()
console.Lines()
.Select(x => Regex.Replace(x, @":line \d+", ":line #")) //Avoid brittle assertion introduced by stack trace line numbers.
.ShouldEqual(
+ "Test '" + testClass + ".SkipA' skipped",
"Console.Out: FailA",
"Console.Error: FailA",
"Console.Out: FailB",
@@ -70,6 +71,8 @@ public void FailB()
public void PassC() { WhereAmI(); }
+ public void SkipA() { throw new ShouldBeUnreachableException(); }
+
static void WhereAmI([CallerMemberName] string member = null)
{
Console.Out.WriteLine("Console.Out: " + member);
View
4 src/Fixie.Tests/Listeners/TeamCityListenerTests.cs
@@ -23,6 +23,8 @@ public void ShouldReportResultsToTheConsoleInTeamCityFormat()
.Select(x => Regex.Replace(x, @":line \d+", ":line #")) //Avoid brittle assertion introduced by stack trace line numbers.
.Select(x => Regex.Replace(x, @"duration='\d+'", "duration='#'")) //Avoid brittle assertion introduced by durations.
.ShouldEqual(
+ "##teamcity[testIgnored name='" + testClass + ".SkipA']",
+
"Console.Out: FailA",
"Console.Error: FailA",
"Console.Out: FailB",
@@ -83,6 +85,8 @@ public void FailB()
public void PassC() { WhereAmI(); }
+ public void SkipA() { throw new ShouldBeUnreachableException(); }
+
static void WhereAmI([CallerMemberName] string member = null)
{
Console.Out.WriteLine("Console.Out: " + member);
View
5 src/Fixie.Tests/StubListener.cs
@@ -14,6 +14,11 @@ public void AssemblyStarted(Assembly assembly)
{
}
+ public void CaseSkipped(Case @case)
+ {
+ log.Add(string.Format("{0} skipped.", @case.Name));
+ }
+
public void CasePassed(PassResult result)
{
var @case = result.Case;
View
8 src/Fixie/AssemblyResult.cs
@@ -7,20 +7,24 @@ public class AssemblyResult
{
readonly int passed;
readonly int failed;
+ readonly int skipped;
- public AssemblyResult(int passed, int failed)
+ public AssemblyResult(int passed, int skipped, int failed)
{
this.passed = passed;
this.failed = failed;
+ this.skipped = skipped;
}
public int Passed { get { return passed; } }
+ public int Skipped { get { return skipped; } }
+
public int Failed { get { return failed; } }
public int Total
{
- get { return Passed + Failed; }
+ get { return Passed + Skipped + Failed; }
}
}
}
View
9 src/Fixie/Conventions/CaseBehaviorBuilder.cs
@@ -11,10 +11,13 @@ public class CaseBehaviorBuilder
public CaseBehaviorBuilder()
{
Behavior = new Invoke();
+ SkipPredicate = @case => false;
}
public CaseBehavior Behavior { get; private set; }
+ public Func<Case, bool> SkipPredicate { get; private set; }
+
public CaseBehaviorBuilder Wrap(CaseBehaviorAction outer)
{
Behavior = new WrapBehavior(outer, Behavior);
@@ -31,6 +34,12 @@ public CaseBehaviorBuilder SetUpTearDown(CaseAction setUp, CaseAction tearDown)
});
}
+ public CaseBehaviorBuilder Skip(Func<Case, bool> skipPredicate)
+ {
+ SkipPredicate = skipPredicate;
+ return this;
+ }
+
class WrapBehavior : CaseBehavior
{
readonly CaseBehaviorAction outer;
View
11 src/Fixie/Conventions/Convention.cs
@@ -38,8 +38,17 @@ public void Execute(Listener listener, params Type[] candidateTypes)
var methods = Methods.Filter(testClass);
var cases = methods.SelectMany(method => CasesForMethod(testClass, method)).ToArray();
+ var casesBySkipState = cases.ToLookup(CaseExecution.SkipPredicate);
+ var casesToSkip = casesBySkipState[true];
+ var casesToExecute = casesBySkipState[false];
+ foreach (var @case in casesToSkip)
+ {
+ listener.CaseSkipped(@case);
+ }
- var caseExecutions = cases.Select(@case => new CaseExecution(@case)).ToArray();
+ var caseExecutions = casesToExecute.Select(@case => new CaseExecution(@case)).ToArray();
+ if (!caseExecutions.Any())
+ continue;
ClassExecution.Behavior.Execute(testClass, this, caseExecutions);
View
3 src/Fixie/Conventions/SelfTestConvention.cs
@@ -15,6 +15,9 @@ public SelfTestConvention()
ClassExecution
.SortCases((x, y) => String.Compare(x.Name, y.Name, StringComparison.Ordinal));
+
+ CaseExecution
+ .Skip(@case => @case.Method.Name.StartsWith("Skip"));
}
}
}
View
5 src/Fixie/Foreground.cs
@@ -22,6 +22,11 @@ public static Foreground Red
get { return new Foreground(ConsoleColor.Red); }
}
+ public static Foreground Yellow
+ {
+ get { return new Foreground(ConsoleColor.Yellow); }
+ }
+
public static Foreground DarkGray
{
get { return new Foreground(ConsoleColor.DarkGray); }
View
1 src/Fixie/Listener.cs
@@ -5,6 +5,7 @@ namespace Fixie
public interface Listener
{
void AssemblyStarted(Assembly assembly);
+ void CaseSkipped(Case @case);
void CasePassed(PassResult result);
void CaseFailed(FailResult result);
void AssemblyCompleted(Assembly assembly, AssemblyResult result);
View
8 src/Fixie/Listeners/ConsoleListener.cs
@@ -11,6 +11,12 @@ public void AssemblyStarted(Assembly assembly)
Console.WriteLine();
}
+ public void CaseSkipped(Case @case)
+ {
+ using (Foreground.Yellow)
+ Console.WriteLine("Test '{0}' skipped", @case.Name);
+ }
+
public void CasePassed(PassResult result)
{
}
@@ -32,7 +38,7 @@ public void AssemblyCompleted(Assembly assembly, AssemblyResult result)
var name = assemblyName.Name;
var version = assemblyName.Version;
- Console.WriteLine("{0} passed, {1} failed ({2} {3}).", result.Passed, result.Failed, name, version);
+ Console.WriteLine("{0} passed, {1} skipped, {2} failed ({3} {4}).", result.Passed, result.Skipped, result.Failed, name, version);
Console.WriteLine();
}
}
View
5 src/Fixie/Listeners/TeamCityListener.cs
@@ -12,6 +12,11 @@ public void AssemblyStarted(Assembly assembly)
Message("testSuiteStarted name='{0}'", assembly.FileName());
}
+ public void CaseSkipped(Case @case)
+ {
+ Message("testIgnored name='{0}'", @case.Name);
+ }
+
public void CasePassed(PassResult result)
{
var @case = result.Case;
View
10 src/Fixie/Runner.cs
@@ -122,6 +122,7 @@ class AssemblyResultListener : Listener
{
int passed;
int failed;
+ int skipped;
readonly Listener inner;
public AssemblyResultListener(Listener inner)
@@ -133,9 +134,16 @@ public void AssemblyStarted(Assembly assembly)
{
passed = 0;
failed = 0;
+ skipped = 0;
inner.AssemblyStarted(assembly);
}
+ public void CaseSkipped(Case @case)
+ {
+ skipped++;
+ inner.CaseSkipped(@case);
+ }
+
public void CasePassed(PassResult result)
{
passed++;
@@ -155,7 +163,7 @@ public void AssemblyCompleted(Assembly assembly, AssemblyResult result)
public AssemblyResult AssemblyResult
{
- get { return new AssemblyResult(passed, failed); }
+ get { return new AssemblyResult(passed, skipped, failed); }
}
}
}