diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a0d641e9..2af3a2f3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -210,7 +210,7 @@ jobs: publish-nuget: runs-on: ubuntu-latest needs: build - if: needs.build.outputs.publishNuget == 'true' && (github.event.inputs.runPublish == 'true' || github.ref_name == github.event.repository.default_branch) + if: github.event.inputs.runPublish == 'true' || github.ref_name == github.event.repository.default_branch environment: name: Publish url: https://www.nuget.org/packages/TestStack.BDDfy/ diff --git a/.gitignore b/.gitignore index ad4aa3cb..ecb096af 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,8 @@ obj/ packages/* PackageBuild/* Build/* -TestResult.xml +TestResult* +CoverageReport TestStack.BDDfy.sln.ide/graph _NCrunch_TestStack.BDDfy/ TestStack.BDDfy.sln.ide/ diff --git a/GitVersion.yml b/GitVersion.yml index 29e4741b..07063aa5 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,9 +1,16 @@ -mode: ContinuousDelivery -next-version: 8.0.0 +assembly-versioning-scheme: MajorMinorPatch +strategies: +- Mainline branches: main: - increment: Patch + mode: ContinuousDeployment regex: ^main$ is-release-branch: true + increment: Patch + + other: + mode: ContinuousDelivery + regex: .* + increment: Patch ignore: - sha: [] + sha: [] \ No newline at end of file diff --git a/coverage-by-samples.ps1 b/coverage-by-samples.ps1 new file mode 100644 index 00000000..86c72c87 --- /dev/null +++ b/coverage-by-samples.ps1 @@ -0,0 +1,10 @@ +dotnet test ./src/Samples/TestStack.BDDfy.Samples/TestStack.BDDfy.Samples.csproj ` + --collect:"XPlat Code Coverage" ` + --results-directory ./TestResults + +reportgenerator ` + -reports:./TestResults/**/coverage.cobertura.xml ` + -targetdir:./CoverageReport ` + -reporttypes:Html + +Remove-Item -Recurse -Force ./TestResults \ No newline at end of file diff --git a/coverage-by-tests.ps1 b/coverage-by-tests.ps1 new file mode 100644 index 00000000..f77d4f9f --- /dev/null +++ b/coverage-by-tests.ps1 @@ -0,0 +1,10 @@ +dotnet test ./src/TestStack.BDDfy.Tests/TestStack.BDDfy.Tests.csproj ` + --collect:"XPlat Code Coverage" ` + --results-directory ./TestResults + +reportgenerator ` + -reports:./TestResults/**/coverage.cobertura.xml ` + -targetdir:./CoverageReport ` + -reporttypes:Html + +Remove-Item -Recurse -Force ./TestResults \ No newline at end of file diff --git a/coverage.ps1 b/coverage.ps1 new file mode 100644 index 00000000..9c7b651c --- /dev/null +++ b/coverage.ps1 @@ -0,0 +1,10 @@ +dotnet test ./src ` + --collect:"XPlat Code Coverage" ` + --results-directory ./TestResults + +reportgenerator ` + -reports:./TestResults/**/coverage.cobertura.xml ` + -targetdir:./CoverageReport ` + -reporttypes:Html + +Remove-Item -Recurse -Force ./TestResults \ No newline at end of file diff --git a/readme.md b/readme.md index 10ad5c22..8bfe45ed 100644 --- a/readme.md +++ b/readme.md @@ -1,3 +1,5 @@ +![Build Status](https://github.com/TestStack/TestStack.BDDfy/actions/workflows/build.yml/badge.svg) + BDDfy is the simplest BDD framework to use, customize and extend! A few quick facts about BDDfy: diff --git a/src/.editorconfig b/src/.editorconfig index dd2b5504..e3d80304 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -1,4 +1,5 @@ [*.cs] -# Default severity for analyzer diagnostics with category 'Style' -dotnet_analyzer_diagnostic.category-Style.severity = none + +# SYSLIB1045: Convert to 'GeneratedRegexAttribute'. +dotnet_diagnostic.SYSLIB1045.severity = none diff --git a/src/Samples/TestStack.BDDfy.Samples/Atm/AccountHasInsufficientFund.cs b/src/Samples/TestStack.BDDfy.Samples/Atm/AccountHasInsufficientFund.cs index e9e2a205..ae90dcb9 100644 --- a/src/Samples/TestStack.BDDfy.Samples/Atm/AccountHasInsufficientFund.cs +++ b/src/Samples/TestStack.BDDfy.Samples/Atm/AccountHasInsufficientFund.cs @@ -10,43 +10,43 @@ public class AccountHasInsufficientFund // You can override step text using executable attributes [Given("Given the Account Balance is $10")] - void GivenTheAccountBalanceIs10() + internal void GivenTheAccountBalanceIs10() { _card = new Card(true, 10); } - void And_given_the_Card_is_valid() + internal void And_given_the_Card_is_valid() { } - void AndGivenTheMachineContainsEnoughMoney() + internal void AndGivenTheMachineContainsEnoughMoney() { _atm = new Atm(100); } [When("When the Account Holder requests $20")] - void WhenTheAccountHolderRequests20() + internal void WhenTheAccountHolderRequests20() { _atm.RequestMoney(_card, 20); } - void Then_the_ATM_should_not_dispense_any_Money() + internal void Then_the_ATM_should_not_dispense_any_Money() { _atm.DispenseValue.ShouldBe(0); } - void And_the_ATM_should_say_there_are_Insufficient_Funds() + internal void And_the_ATM_should_say_there_are_Insufficient_Funds() { _atm.Message.ShouldBe(DisplayMessage.InsufficientFunds); } [AndThen("And the Account Balance should be $20")] - void AndTheAccountBalanceShouldBe20() + internal void AndTheAccountBalanceShouldBe20() { _card.AccountBalance.ShouldBe(10); } - void And_the_Card_should_be_returned() + internal void And_the_Card_should_be_returned() { _atm.CardIsRetained.ShouldBe(false); } @@ -56,5 +56,12 @@ public void Verify() { this.BDDfy(); } + + [Fact] + public void VerifyLazy() + { + var engine = this.LazyBDDfy(); + engine.Run(); + } } } \ No newline at end of file diff --git a/src/Samples/TestStack.BDDfy.Samples/CanRunAsyncVoidSteps.cs b/src/Samples/TestStack.BDDfy.Samples/CanRunAsyncVoidSteps.cs new file mode 100644 index 00000000..36afc1e5 --- /dev/null +++ b/src/Samples/TestStack.BDDfy.Samples/CanRunAsyncVoidSteps.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using TestStack.BDDfy.Configuration; +using Xunit; + +namespace TestStack.BDDfy.Samples +{ + public class CanRunAsyncVoidSteps + { + [Fact] + internal void Run() => this.BDDfy(); + internal void SetUp() => Configurator.AsyncVoidSupportEnabled = false; + internal void TearDown() => Configurator.AsyncVoidSupportEnabled = true; + + internal async void GivenNonAsyncStep() => await Task.CompletedTask; + internal async void WhenSomethingHappens() => await Task.CompletedTask; + internal async void ThenAssertSomething() => await Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Samples/TestStack.BDDfy.Samples/TicTacToe/TicTacToe.cs b/src/Samples/TestStack.BDDfy.Samples/TicTacToe/TicTacToe.cs index 726295a3..056ce792 100644 --- a/src/Samples/TestStack.BDDfy.Samples/TicTacToe/TicTacToe.cs +++ b/src/Samples/TestStack.BDDfy.Samples/TicTacToe/TicTacToe.cs @@ -1,6 +1,5 @@ using Shouldly; using Xunit; -using Xunit.Extensions; namespace TestStack.BDDfy.Samples.TicTacToe { diff --git a/src/Samples/TestStack.BDDfy.Samples/TicTacToe/WinnerGame.cs b/src/Samples/TestStack.BDDfy.Samples/TicTacToe/WinnerGame.cs index 43e363eb..ce50e23e 100644 --- a/src/Samples/TestStack.BDDfy.Samples/TicTacToe/WinnerGame.cs +++ b/src/Samples/TestStack.BDDfy.Samples/TicTacToe/WinnerGame.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using NUnit.Framework; using Shouldly; namespace TestStack.BDDfy.Samples.TicTacToe diff --git a/src/TestStack.BDDfy.Tests/Concurrency/TestCollectionName.cs b/src/TestStack.BDDfy.Tests/Concurrency/TestCollectionName.cs new file mode 100644 index 00000000..aaf5a2ea --- /dev/null +++ b/src/TestStack.BDDfy.Tests/Concurrency/TestCollectionName.cs @@ -0,0 +1,7 @@ +namespace TestStack.BDDfy.Tests.Concurrency +{ + internal static class TestCollectionName { + public const string ModifiesConfigurator = "ModifiesConfigurator"; + public const string Approvals = nameof(Approvals); + } +} diff --git a/src/TestStack.BDDfy.Tests/Configuration/BatchProcessorsTests.cs b/src/TestStack.BDDfy.Tests/Configuration/BatchProcessorsTests.cs index b6ac86f7..b253c45c 100644 --- a/src/TestStack.BDDfy.Tests/Configuration/BatchProcessorsTests.cs +++ b/src/TestStack.BDDfy.Tests/Configuration/BatchProcessorsTests.cs @@ -1,17 +1,21 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using Shouldly; using TestStack.BDDfy.Configuration; +using TestStack.BDDfy.Reporters.Diagnostics; using TestStack.BDDfy.Reporters.Html; using TestStack.BDDfy.Reporters.MarkDown; +using TestStack.BDDfy.Tests.Concurrency; using Xunit; namespace TestStack.BDDfy.Tests.Configuration { + [Collection(TestCollectionName.ModifiesConfigurator)] public class BatchProcessorsTests { static bool MetroReportProcessorIsActive(IBatchProcessor batchProcessor) { - return batchProcessor is HtmlReporter && ((HtmlReporter)batchProcessor).ReportBuilder is MetroReportBuilder; + return batchProcessor is HtmlReporter reporter && reporter.ReportBuilder is MetroReportBuilder; } [Fact] @@ -67,5 +71,35 @@ public void ReturnsHtmlMetroReporterWhenItIsActivated() Configurator.BatchProcessors.HtmlMetroReport.Disable(); } + + [Fact] + public void ReturnsDianosticsReporterWhenItIsActivated() + { + Configurator.BatchProcessors.DiagnosticsReport.Enable(); + + var processors = Configurator.BatchProcessors.GetProcessors().ToList(); + + processors.ShouldContain(p=> p is DiagnosticsReporter, 1); + + Configurator.BatchProcessors.DiagnosticsReport.Disable(); + } + + [Fact] + public void ReturnsAdditionalBatchProcessorsWhenAdded() + { + Configurator.BatchProcessors.Add(new FooBatchProcessor()); + + var processors = Configurator.BatchProcessors.GetProcessors().ToList(); + + processors.ShouldContain(p => p is FooBatchProcessor, 1); + } + + private class FooBatchProcessor : IBatchProcessor + { + public void Process(IEnumerable stories) + { + throw new System.NotImplementedException(); + } + } } } \ No newline at end of file diff --git a/src/TestStack.BDDfy.Tests/Configuration/ExceptionResolverTests.cs b/src/TestStack.BDDfy.Tests/Configuration/ExceptionResolverTests.cs new file mode 100644 index 00000000..9f1f8988 --- /dev/null +++ b/src/TestStack.BDDfy.Tests/Configuration/ExceptionResolverTests.cs @@ -0,0 +1,26 @@ +using System; +using System.Reflection; +using Shouldly; +using TestStack.BDDfy.Processors; +using Xunit; + +namespace TestStack.BDDfy.Tests.Configuration; + +public class ExceptionResolverTests +{ + [Fact] + public void WhenTargetInvocationException_ResolveRoot_WhenInnerNotAvailable() + { + var ex = new TargetInvocationException(null); + var resolved = ExceptionResolver.Resolve(ex); + resolved.ShouldBeOfType(); + } + + [Fact] + public void WhenNotTargetInvocationException_ResolveRoot_EvenWhenInnerIsAvailable() + { + var ex = new System.InvalidCastException("error", new AmbiguousMatchException()); + var resolved = ExceptionResolver.Resolve(ex); + resolved.ShouldBeOfType(); + } +} diff --git a/src/TestStack.BDDfy.Tests/Configuration/ProcessorPipelineTests.cs b/src/TestStack.BDDfy.Tests/Configuration/ProcessorPipelineTests.cs index 3ca8a1ab..1c042227 100644 --- a/src/TestStack.BDDfy.Tests/Configuration/ProcessorPipelineTests.cs +++ b/src/TestStack.BDDfy.Tests/Configuration/ProcessorPipelineTests.cs @@ -3,11 +3,12 @@ using TestStack.BDDfy.Configuration; using TestStack.BDDfy.Processors; using TestStack.BDDfy.Reporters; +using TestStack.BDDfy.Tests.Concurrency; using Xunit; namespace TestStack.BDDfy.Tests.Configuration { - [Collection("ExclusiveAccessToConfigurator")] + [Collection(TestCollectionName.ModifiesConfigurator)] public class ProcessorPipelineTests { [Fact] diff --git a/src/TestStack.BDDfy.Tests/Configuration/StepExecutorTests.cs b/src/TestStack.BDDfy.Tests/Configuration/StepExecutorTests.cs index 8d9e1916..8e78f3b2 100644 --- a/src/TestStack.BDDfy.Tests/Configuration/StepExecutorTests.cs +++ b/src/TestStack.BDDfy.Tests/Configuration/StepExecutorTests.cs @@ -1,11 +1,12 @@ using System.Text; using Shouldly; using TestStack.BDDfy.Configuration; +using TestStack.BDDfy.Tests.Concurrency; using Xunit; namespace TestStack.BDDfy.Tests.Configuration { - [Collection("ExclusiveAccessToConfigurator")] + [Collection(TestCollectionName.ModifiesConfigurator)] public class StepExecutorTests { private class TestStepExecutor : StepExecutor diff --git a/src/TestStack.BDDfy.Tests/Configuration/TestRunnerTests.cs b/src/TestStack.BDDfy.Tests/Configuration/TestRunnerTests.cs index f89aece7..1213e381 100644 --- a/src/TestStack.BDDfy.Tests/Configuration/TestRunnerTests.cs +++ b/src/TestStack.BDDfy.Tests/Configuration/TestRunnerTests.cs @@ -2,11 +2,11 @@ using System.Linq; using Shouldly; using TestStack.BDDfy.Configuration; +using TestStack.BDDfy.Tests.Concurrency; using Xunit; namespace TestStack.BDDfy.Tests.Configuration { - [Collection("ExclusiveAccessToConfigurator")] public class TestRunnerTests { public class ScenarioWithFailingThen @@ -33,6 +33,9 @@ public void PassingAndThen() } } + + [Collection(TestCollectionName.ModifiesConfigurator)] + public class When_StopExecutionOnFailingThen_IsSetToTrue { [Fact] diff --git a/src/TestStack.BDDfy.Tests/Disposer/DisposingScenarios.cs b/src/TestStack.BDDfy.Tests/Disposer/DisposingScenarios.cs index a47ad897..86b41b84 100644 --- a/src/TestStack.BDDfy.Tests/Disposer/DisposingScenarios.cs +++ b/src/TestStack.BDDfy.Tests/Disposer/DisposingScenarios.cs @@ -3,7 +3,6 @@ using Shouldly; using TestStack.BDDfy.Tests.Exceptions; using Xunit; -using Xunit.Extensions; namespace TestStack.BDDfy.Tests.Disposer { diff --git a/src/TestStack.BDDfy.Tests/Exceptions/OtherExceptions/WhenWhenThrowsException.cs b/src/TestStack.BDDfy.Tests/Exceptions/OtherExceptions/WhenWhenThrowsException.cs index 11741e33..1ea549b7 100644 --- a/src/TestStack.BDDfy.Tests/Exceptions/OtherExceptions/WhenWhenThrowsException.cs +++ b/src/TestStack.BDDfy.Tests/Exceptions/OtherExceptions/WhenWhenThrowsException.cs @@ -1,9 +1,11 @@ using System; using Shouldly; +using TestStack.BDDfy.Tests.Concurrency; using Xunit; namespace TestStack.BDDfy.Tests.Exceptions.OtherExceptions { + [Collection(TestCollectionName.ModifiesConfigurator)] public class WhenWhenThrowsException : OtherExceptionBase { private void ExecuteUsingFluentScanner() diff --git a/src/TestStack.BDDfy.Tests/ExclusiveAccessToConfiguratorFixture.cs b/src/TestStack.BDDfy.Tests/ExclusiveAccessToConfiguratorFixture.cs deleted file mode 100644 index 2c9f5f33..00000000 --- a/src/TestStack.BDDfy.Tests/ExclusiveAccessToConfiguratorFixture.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Xunit; - -namespace TestStack.BDDfy.Tests -{ - [CollectionDefinition("ExclusiveAccessToConfigurator", DisableParallelization = true)] - public class ExclusiveAccessToConfiguratorFixture - { - } -} \ No newline at end of file diff --git a/src/TestStack.BDDfy.Tests/DefaultHumanizerTests.cs b/src/TestStack.BDDfy.Tests/HumanizerTests.cs similarity index 83% rename from src/TestStack.BDDfy.Tests/DefaultHumanizerTests.cs rename to src/TestStack.BDDfy.Tests/HumanizerTests.cs index 7a5d7baf..079666a9 100644 --- a/src/TestStack.BDDfy.Tests/DefaultHumanizerTests.cs +++ b/src/TestStack.BDDfy.Tests/HumanizerTests.cs @@ -1,13 +1,12 @@ using System; using Shouldly; -using TestStack.BDDfy.Configuration; using Xunit; namespace TestStack.BDDfy.Tests { - public class DefaultHumanizerTests + public sealed class HumanizerTests { - private static DefaultHumanizer Humanizer => new(); + private static readonly DefaultHumanizer Humanizer = new(); [Fact] public void PascalCaseInputStringIsTurnedIntoSentence() @@ -34,6 +33,13 @@ public void WhenInputStringEndWithANumber_ThenNumberIsDealtWithLikeAWord() Humanizer.Humanize("NumberIsAtTheEnd100").ShouldBe("Number is at the end 100"); } + [Fact] + public void WhenInputStringHasANumberInTheMiddleAndEnd_ThenNumberIsDealtWithLikeAWord() + { + Humanizer.Humanize("Number15InTheMiddleAndEndingWith100") + .ShouldBe("Number 15 in the middle and ending with 100"); + } + [Fact] public void UnderscoredInputStringIsTurnedIntoSentence() { @@ -57,7 +63,7 @@ public void OneLetterWordInTheBeginningOfStringIsTurnedIntoAWord() [Theory] [InlineData("GivenThereAre__start__Cucumbers", "Given there are cucumbers")] [InlineData("Given_there_are__start__cucumbers", "Given there are cucumbers")] - [InlineData("GivenThereAre__count1__Cucumbers", "Given there are cucumbers")] + [InlineData("GivenThereAre__count1__Cucumbers", "Given there are cucumbers")] [InlineData("Given_there_are__count2__cucumbers", "Given there are cucumbers")] // The spacing rules for numbers are not consequential [InlineData("GivenMethodTaking__ExampleInt__", "Given method taking ")] [InlineData("Given_method_taking__ExampleInt__", "Given method taking ")] @@ -82,5 +88,14 @@ public void ReportsIllegalExampleStepNames(string stepName, string expectedStepT exception.ShouldNotBeNull(); exception.ShouldBeOfType(); } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + internal void HumanizeWithNullOrEmptyInput_ReturnsTheSame(string providedInput) + { + Humanizer.Humanize(providedInput).ShouldBe(providedInput); + } } } \ No newline at end of file diff --git a/src/TestStack.BDDfy.Tests/ModifiesConfiguratorFixture.cs b/src/TestStack.BDDfy.Tests/ModifiesConfiguratorFixture.cs new file mode 100644 index 00000000..c5fae0dc --- /dev/null +++ b/src/TestStack.BDDfy.Tests/ModifiesConfiguratorFixture.cs @@ -0,0 +1,10 @@ +using TestStack.BDDfy.Tests.Concurrency; +using Xunit; + +namespace TestStack.BDDfy.Tests +{ + [CollectionDefinition(TestCollectionName.ModifiesConfigurator, DisableParallelization = true)] + public class ModifiesConfiguratorFixture + { + } +} \ No newline at end of file diff --git a/src/TestStack.BDDfy.Tests/Reporters/Html/ClassicReportBuilderTests.ShouldProduceExpectedHtml.approved.txt b/src/TestStack.BDDfy.Tests/Reporters/Html/ClassicReportBuilderTests.ShouldProduceExpectedHtml.approved.txt index 436429e4..02482b18 100644 --- a/src/TestStack.BDDfy.Tests/Reporters/Html/ClassicReportBuilderTests.ShouldProduceExpectedHtml.approved.txt +++ b/src/TestStack.BDDfy.Tests/Reporters/Html/ClassicReportBuilderTests.ShouldProduceExpectedHtml.approved.txt @@ -159,7 +159,7 @@ body{margin:0;padding:0;padding-bottom:40px;max-width:100%;background-color:#fff - +