-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(Unity) Attribute-based BDD-style tests (#160)
- Loading branch information
Showing
15 changed files
with
461 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
using NUnit.Framework; | ||
using Responsible.Bdd; | ||
using static Responsible.Bdd.Keywords; | ||
using static Responsible.Responsibly; | ||
|
||
namespace Responsible.UnityTests | ||
{ | ||
[Feature("BDD-style test")] | ||
public class BddTests : BddTest | ||
{ | ||
private bool setUpCalled; | ||
private bool givenExecuted; | ||
private bool whenExecuted; | ||
private bool tearDownCalled; | ||
|
||
[SetUp] | ||
public void SetUp() | ||
{ | ||
this.setUpCalled = true; | ||
this.givenExecuted = false; | ||
this.whenExecuted = false; | ||
} | ||
|
||
[TearDown] | ||
public void TearDown() | ||
{ | ||
this.tearDownCalled = true; | ||
} | ||
|
||
// It's really hard to know for sure if TearDown gets run with custom attributes, | ||
// However, if SetUp is called, we can be pretty sure TearDown is also called. | ||
// But lets do this just for the heck of it anyway. | ||
// If anyone happens to look at this code and has ideas, please hit me up! | ||
[OneTimeTearDown] | ||
public void OneTimeTearDown() | ||
{ | ||
Assert.IsTrue(this.tearDownCalled, "Tear down should have been called at least once"); | ||
} | ||
|
||
[Scenario("A basic BDD-style test runs without error")] | ||
public IBddStep[] BasicTest() => new[] | ||
{ | ||
Given( | ||
"the test is set up properly", | ||
Do("Execute Given", () => this.givenExecuted = true)), | ||
When( | ||
"we execute the when step", | ||
Do("Execute When", () => this.whenExecuted = true)), | ||
Then( | ||
"the state of the test class should be in the final expected state", | ||
Do( | ||
"Assert state", | ||
() => Assert.AreEqual( | ||
(true, true, true), | ||
(this.setUpCalled, this.givenExecuted, this.whenExecuted)))), | ||
}; | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
135 changes: 135 additions & 0 deletions
135
ResponsibleUnity/Assets/UnityTests/FeatureAttributeTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using JetBrains.Annotations; | ||
using NUnit.Framework; | ||
using NUnit.Framework.Interfaces; | ||
using NUnit.Framework.Internal; | ||
using Responsible.Bdd; | ||
|
||
namespace Responsible.UnityTests | ||
{ | ||
public class FeatureAttributeTests | ||
{ | ||
private class NonBddTest | ||
{ | ||
} | ||
|
||
private class InvalidReturnTypeBddTest : BddTest | ||
{ | ||
[Scenario("Invalid scenario")] | ||
public void InvalidScenario() | ||
{ | ||
} | ||
} | ||
|
||
private class ValidBddTest : BddTest | ||
{ | ||
[Scenario("Test scenario 1")] | ||
public IBddStep[] TestScenario1() => Array.Empty<IBddStep>(); | ||
|
||
[Scenario("Test scenario 2")] | ||
public IBddStep[] TestScenario2() => Array.Empty<IBddStep>(); | ||
} | ||
|
||
private class MixedBddTest : BddTest | ||
{ | ||
[Scenario("Test scenario")] | ||
public IBddStep[] TestScenario() => Array.Empty<IBddStep>(); | ||
|
||
[Ignore("Just used for attribute testing")] | ||
[Test] | ||
public void NormalTest() | ||
{ | ||
} | ||
} | ||
|
||
[Test] | ||
public void BuildingTest_ShouldMarkSuiteAsNotRunnable_WhenClassDoesNotInheritBddTest() | ||
{ | ||
var suite = BuildSuites<NonBddTest>().Single(); | ||
AssertTestNotRunnableWithReasonContaining(suite, "must inherit from BddTest"); | ||
} | ||
|
||
[Test] | ||
public void BuildingTest_ShouldMarkScenarioAsNotRunnable_WhenReturnTypeIsIncorrect() | ||
{ | ||
var test = BuildSuites<InvalidReturnTypeBddTest>() | ||
.Single() | ||
.Tests | ||
.Single(); | ||
AssertTestNotRunnableWithReasonContaining( | ||
test, | ||
"Scenario return type must be convertible to IEnumerable<IBddStep>, got System.Void"); | ||
} | ||
|
||
[Test] | ||
public void BuildingTest_ShouldHaveGivenName_WhenClassIsSetUpCorrectly() | ||
{ | ||
var suite = BuildSuites<ValidBddTest>().Single(); | ||
Assert.AreEqual("Feature: Test feature", suite.Name); | ||
} | ||
|
||
[Test] | ||
public void BuildingTest_ShouldContainAllScenarios_WhenClassIsSetUpCorrectly() | ||
{ | ||
var testNames = BuildSuites<ValidBddTest>() | ||
.Single() | ||
.Tests.Select(t => t.Name) | ||
.ToArray(); | ||
|
||
CollectionAssert.AreEquivalent( | ||
new[] { "Scenario: Test scenario 1", "Scenario: Test scenario 2" }, | ||
testNames); | ||
} | ||
|
||
[Test] | ||
public void BuildingTests_ShouldReturnTwoSuites_WhenMixingStyles() | ||
{ | ||
var suiteNames = BuildSuites<MixedBddTest>() | ||
.Select(s => s.Name) | ||
.ToArray(); | ||
|
||
CollectionAssert.AreEquivalent( | ||
new[] | ||
{ | ||
"Feature: Test feature", | ||
$"{nameof(FeatureAttributeTests)}+{nameof(MixedBddTest)}" | ||
}, | ||
suiteNames); | ||
} | ||
|
||
[Test] | ||
public void BuildingTests_ShouldReturnAllTests_WhenMixingStyles() | ||
{ | ||
var testNames = BuildSuites<MixedBddTest>() | ||
.SelectMany(suite => suite.Tests) | ||
.Select(test => test.Name) | ||
.ToArray(); | ||
|
||
CollectionAssert.AreEquivalent( | ||
new[] | ||
{ | ||
"Scenario: Test scenario", | ||
"NormalTest", | ||
}, | ||
testNames); | ||
} | ||
|
||
private static IEnumerable<TestSuite> BuildSuites<T>() | ||
{ | ||
var fakeAttribute = new FeatureAttribute("Test feature"); | ||
var fixtureBuilder = (IFixtureBuilder)fakeAttribute; | ||
return fixtureBuilder.BuildFrom(new TypeWrapper(typeof(T))); | ||
} | ||
|
||
[AssertionMethod] | ||
private static void AssertTestNotRunnableWithReasonContaining(ITest test, string reason) | ||
{ | ||
Assert.AreEqual(RunState.NotRunnable, test.RunState); | ||
StringAssert.Contains( | ||
reason, | ||
(string)test.Properties.Get(PropertyNames.SkipReason)); | ||
} | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
ResponsibleUnity/Assets/UnityTests/FeatureAttributeTests.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using JetBrains.Annotations; | ||
using NUnit.Framework; | ||
using NUnit.Framework.Interfaces; | ||
using NUnit.Framework.Internal; | ||
using Responsible.Unity; | ||
using UnityEngine.TestTools; | ||
using static Responsible.Bdd.Keywords; | ||
|
||
namespace Responsible.Bdd | ||
{ | ||
/// <summary> | ||
/// Base class for BDD-style tests using attributes. | ||
/// </summary> | ||
/// <remarks> | ||
/// This is Unity-only, because we depend on NUnit in the attributes, | ||
/// and don't depend on NUnit for the vanilla C# version of Responsible. | ||
/// A non-Unity version might be implemented later. | ||
/// </remarks> | ||
/// <seealso cref="FeatureAttribute"/> | ||
/// <seealso cref="ScenarioAttribute"/> | ||
public abstract class BddTest | ||
{ | ||
/// <summary> | ||
/// A test instruction executor that is automatically set up and torn down between tests. | ||
/// See <see cref="MakeExecutor"/> for customization options. | ||
/// </summary> | ||
protected TestInstructionExecutor Executor { get; private set; } | ||
|
||
/// <summary> | ||
/// Set-up method for NUnit, should not be used manually. | ||
/// </summary> | ||
[SetUp] | ||
public void BddTestSetUp() => this.Executor = this.MakeExecutor(); | ||
|
||
/// <summary> | ||
/// Tear-down method for NUnit, should not be used manually. | ||
/// </summary> | ||
[TearDown] | ||
public void BddTestTearDown() => this.Executor.Dispose(); | ||
|
||
/// <summary> | ||
/// Helper method for executing BDD steps, should not be used manually, | ||
/// but must be public to make NUnit happy. | ||
/// </summary> | ||
/// <param name="scenario">Name of the test scenario.</param> | ||
/// <param name="testMethod">Test method that returns the test steps.</param> | ||
/// <returns></returns> | ||
public IEnumerator ExecuteScenario(string scenario, IMethodInfo testMethod) | ||
{ | ||
var steps = (IEnumerable<IBddStep>)testMethod.Invoke(this); | ||
return Scenario(scenario).WithSteps(steps.ToArray()).ToYieldInstruction(this.Executor); | ||
} | ||
|
||
/// <summary> | ||
/// Creates a test instruction executor for a test run. May be customized in deriving classes. | ||
/// </summary> | ||
/// <returns>A new test instruction executor to be used for the next test.</returns> | ||
[PublicAPI] | ||
protected virtual TestInstructionExecutor MakeExecutor() => new UnityTestInstructionExecutor(); | ||
|
||
/// <summary> | ||
/// Gets the scenario execution method for a deriving type. | ||
/// The exact type matters, because NUnit will use it to resolve set-up and tear-down methods. | ||
/// </summary> | ||
internal static IMethodInfo GetExecuteScenarioMethod(ITypeInfo derivingType) => | ||
new MethodWrapper(derivingType.Type, nameof(ExecuteScenario)); | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.