{"id":19407603,"url":"https://github.com/8t4/gwtdo","last_synced_at":"2025-10-12T09:06:51.412Z","repository":{"id":48114735,"uuid":"358657780","full_name":"8T4/gwtdo","owner":"8T4","description":"GWTDO defines a BDD-style test suite, with a clear separation of concerns between the test scenario definition and the implementation details.","archived":false,"fork":false,"pushed_at":"2023-05-12T17:05:12.000Z","size":1725,"stargazers_count":40,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-09-06T23:29:12.935Z","etag":null,"topics":["bdd","dotnet","dsl","gwt","nuget","test-fixture","test-fixtures","testing"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages/Gwtdo","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/8T4.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-04-16T16:24:28.000Z","updated_at":"2024-04-11T09:24:26.000Z","dependencies_parsed_at":"2024-07-29T21:46:19.589Z","dependency_job_id":"8a885809-ae85-4d4b-9ce4-7c0756cfc6d8","html_url":"https://github.com/8T4/gwtdo","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/8T4/gwtdo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/8T4%2Fgwtdo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/8T4%2Fgwtdo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/8T4%2Fgwtdo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/8T4%2Fgwtdo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/8T4","download_url":"https://codeload.github.com/8T4/gwtdo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/8T4%2Fgwtdo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279010958,"owners_count":26084836,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-12T02:00:06.719Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bdd","dotnet","dsl","gwt","nuget","test-fixture","test-fixtures","testing"],"created_at":"2024-11-10T12:03:08.026Z","updated_at":"2025-10-12T09:06:51.396Z","avatar_url":"https://github.com/8T4.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align='center'\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/8T4/gwtdo/main/doc/img/icon.png\"\u003e\n    \u003cbr/\u003e\n    \u003cbr/\u003e\n    \u003ca href='https://github.com/8T4/gwtdo/actions/workflows/dotnet.yml'\u003e\u003cimg src=\"https://github.com/8T4/gwtdo/actions/workflows/dotnet.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href='https://github.com/8T4/gwtdo/actions/workflows/codeql-analysis.yml'\u003e\u003cimg src=\"https://github.com/8T4/gwtdo/actions/workflows/codeql-analysis.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href='https://www.nuget.org/packages/Gwtdo'\u003e\u003cimg src=\"https://img.shields.io/nuget/v/Gwtdo.svg\"\u003e\u003c/a\u003e\n    \u003ca href='https://www.nuget.org/packages/Gwtdo'\u003e\u003cimg src=\"https://img.shields.io/nuget/dt/Gwtdo.svg\"\u003e\u003c/a\u003e\n    \u003ca href='https://www.codacy.com/gh/8T4/gwtdo/dashboard?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=8T4/gwtdo\u0026amp;utm_campaign=Badge_Grade'\u003e\u003cimg src=\"https://app.codacy.com/project/badge/Grade/51e1962835f24f65a3813d078061a9ef\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n# Using the GWTDO Library\nGiven-When-Then (GWT) is a format for writing executable specifications or automated acceptance tests in Behavior Driven Development (BDD). GWT is a way of structuring test scenarios that makes them easy to read and understand by all stakeholders including developers, QA engineers, and business analysts.\nThe Given-When-Then structure helps to ensure that the requirements of the system or feature being developed are clearly defined and that everyone involved in the development process has a shared understanding of what is expected.\n\n## What is GWTDO?\nGWTDO defines a BDD-style test suite, with a clear separation of concerns between the test scenario definition and the implementation details.\n\n## How to use GWTDO?\nTo use a GWTDO library, you need to follow these steps:\n\n1. Add the library to your project: You can either download the library and add it as a reference to your project or use NuGet, a package manager for .NET, to install the library.\n2. Import the library: In your code, you need to import the library namespace to access its classes, methods, and properties. You can do this by using the using statement in C#.\n3. Use the library: Once you have imported the library, you can use its classes, methods, and properties in your code.\n\n## Example\nIn this example, we will use the XUnit framework. The domain classes, as well as the code used below, can be accessed [here](src/Samples).\n\n### 1. Specification\nThis is a BDD-style scenario for a feature named \"User trades stocks\". The scenario describes the behavior of the system when a user requests to sell a certain number of shares of a particular stock before the close of trading.\n\n```sql\nFeature: User trades stocks\n  Scenario: User requests a sell before close of trading\n    Given I have 100 shares of MSFT stock\n       And I have 150 shares of APPL stock\n       And the time is before close of trading\n\n    When I ask to sell 20 shares of MSFT stock\n     \n     Then I should have 80 shares of MSFT stock\n      And I should have 150 shares of APPL stock\n      And a sell order for 20 shares of MSFT stock should have been executed\n```\n\nThe scenario starts with some preconditions, which are specified using the \"Given\" keyword. In this case, the preconditions are that the user has 100 shares of MSFT stock and 150 shares of APPL stock, and that the time is before the close of trading. The next step in the scenario is to perform an action using the \"When\" keyword. In this case, the action is that the user requests to sell 20 shares of MSFT stock.\nFinally, the expected outcome of the action is specified using the \"Then\" keyword. In this case, the expected outcomes are that the user should have 80 shares of MSFT stock and 150 shares of APPL stock, and that a sell order for 20 shares of MSFT stock should have been executed. The scenario provides a clear description of the intended behavior of the system for this specific use case, and can serve as a basis for designing, testing and implementing the necessary features in a BDD-oriented development process.\n\n### 2. BDD-style scenario\nThis code demonstrates how GWTDO can be used to write BDD-style tests that are readable and understandable by both technical and non-technical stakeholders.\n\n```c#\npublic class StocksTests : Feature\u003cTradingContext, TradingFixture\u003e, IClassFixture\u003cTradingContext\u003e\n{                                                                                                \n    public StocksTests(ITestOutputHelper output, TradingContext context) : base(context)         \n    {                                                                                            \n        SetOutputRedirect(new TestOutputRedirect(output));                                       \n    }   \n    \n    [Fact]\n    [Scenario(@\"User requests a sell before close of trading\")]\n    public void test_with_attribute_mapping()\n    {\n        Describe(\"When an asset is sold\",\n            GIVEN\n            | \"I have 100 shares of MSFT stock\" | AND\n            | \"I have 150 shares of APPL stock\" | AND\n            | \"The time is before close of trading\" |\n            WHEN\n            | \"I ask to sell 20 shares of MSFT stock\" |\n            THEN\n            | \"I should have 80 shares of MSFT stock\" | AND\n            | \"I should have 150 shares of APPL stock\" | AND\n            | \"A sell order for 20 shares of MSFT stock should have been executed\");\n    }\n}                                                                                             \n\n```\nOverall, this code demonstrates how GWTDO can be used to write BDD-style tests that are readable and understandable by both technical and non-technical stakeholders.\n\nThe Feature class takes two generic parameters: the [TradingContext](src/Samples/Gwtdo.Sample/TradingContext.cs) and [TradingFixture](src/Samples/Gwtdo.Sample/TradingFixture.cs) classes. \nIn addition, the StocksTests class implements the `IClassFixture` interface with `TradingContext` as the generic parameter. \nThis allows the `TradingContext` to be injected into the test class.\n\nThe constructor of `StocksTests` takes an `ITestOutputHelper` and a `TradingContext` object as parameters. It calls the base constructor of Feature with the `TradingContext` object, and sets up a test output redirect using the `ITestOutputHelper` object.\n\nThe `test_with_attribute_mapping()` method is a test method with an XUnit attribute [Fact]. The test method uses the Scenario attribute to provide a description of the scenario being tested. The **GIVEN**, **WHEN**, and **THEN** keywords are used to describe the steps of the scenario in a readable format.\n\n### 3. Mapping scenario\n\n```c#\npublic class TradingFixture : ScenarioFixture\u003cTradingContext\u003e\n{\n    [Given(\"I have 100 shares of MSFT stock\")]\n    public void Have100SharesOfMsftStock() =\u003e\n        Context?.Trading.Buy(new TradingOrder(\"MSFT\", 100,\n            new DateTime(2023, 1, 1, 10, 0, 0)));\n            \n    [Given(\"I have 150 shares of APPL stock\")]\n    public void Have150SharesOfApplStock() =\u003e\n        Context?.Trading.Buy(new TradingOrder(\"APPL\", 150,\n            new DateTime(2023, 1, 1, 10, 0, 0)));            \n\n    [Given(\"The time is before close of trading\")]\n    public void GivenTimeBeforeCloseOfTrading() =\u003e \n        Context.Clock.UpdateLimit(new DateTime(2023, 1, 1, 18, 0, 0));\n\n    [When(\"I ask to sell 20 shares of MSFT stock\")]\n    public void AskToSell20SharesOfMsftStock() =\u003e\n        Context?.Trading.Sell(new TradingOrder(\"MSFT\", 20,\n            new DateTime(2023, 1, 1, 10, 0, 0)));\n\n    [Then(\"I should have 80 shares of MSFT stock\")]\n    public void ShouldHave80SharesOfMsftStock() =\u003e\n        Context?.Trading.Shares[\"MSFT\"].Should().Be(80); \n\n    [Then(\"I should have 150 shares of APPL stock\")]\n    public void ShouldHave150SharesOfApplStock() =\u003e\n        Context?.Trading.Shares[\"APPL\"].Should().Be(150);\n\n    [Then(\"A sell order for 20 shares of MSFT stock should have been executed\")]\n    public void ASellOrderFor20SharesOfMSFT() =\u003e\n        Context?.Trading.Orders[\"MSFT\"].Should().Be(20);   \n}\n```\n\nThis code is defining a test fixture class called `TradingFixture`. It inherits from `ScenarioFixture\u003cTradingContext\u003e`, which is a class used for setting up and cleaning up test scenarios. It contains several methods that are decorated with attributes that map to the Given-When-Then steps of a BDD test.\n\nEach method represents a step in the scenario, and they are named according to the step they are performing. For example, the method `Have100SharesOfMsftStock` is decorated with the attribute `[Given(\"I have 100 shares of MSFT stock\")]`, which maps to the Given step \"I have 100 shares of MSFT stock\".\n\nThe methods use the `Context` object, which is an instance of the `TradingContext` class, to perform actions such as buying or selling stocks. The `Should()` method is used to assert that the expected result has occurred. For example, the method `ShouldHave80SharesOfMsftStock` asserts that the number of MSFT shares in the trading context is 80.\n\nThese methods can be called from a BDD-style test to execute the scenario defined in the `TradingFixture` class.\n\n### 4. Feature Context\n\n```c#\npublic record TradingContext : IFeatureContext, IFeatureContextLifeCycle\n{\n    public Trading Trading { get; set; }\n    public TradingClock? Clock { get; set; }\n    \n    public void Setup()\n    {\n        Clock = new TradingClock(new DateTime(2023, 1, 1, 18, 0, 0));\n        Trading = new Trading(Clock);\n    }\n\n    public void TearDown()\n    {\n    }\n}\n```\nThis code defines a record called `TradingContext` which implements the `IFeatureContext` and `IFeatureContextLifeCycle` interfaces. It contains two properties `Trading` and `Clock` of types `Trading` and `TradingClock?` respectively.\n\nThe `Trading` property is an instance of the `Trading` class and it is used to perform trading operations. The `Clock` property is an instance of the `TradingClock` class and it is used to keep track of the current time during trading.\n\nThe `Setup()` method initializes the `Clock` property to a specific date and time and then initializes the `Trading` property by passing the `Clock` instance to its constructor. This method is called before each test scenario.\n\nThe `TearDown()` method is empty and it is called after each test scenario. It can be used to clean up any resources that were used during the test scenario. In this case, since there is no cleanup needed, the method is empty.\n\n### 5. Results\n#### Success result\n![img.png](doc/img/img-result-success.png)\n\n#### Fail Result\n![img.png](doc/img/img-result-fail.png)\n\n#### Fail Result When Mapping is Wrong\n![img.png](doc/img/img-result-fail-mapping.png)\n\n\n## Using Extension Methods instead Attribute Mapping\n\n```c#\npublic class StocksTests : Feature\u003cTradingContext, TradingFixture\u003e, IClassFixture\u003cTradingContext\u003e\n{\n    public StocksTests(ITestOutputHelper output, TradingContext context) : base(context)\n    {                                                                                   \n        SetOutputRedirect(new TestOutputRedirect(output));                              \n    }                                                                                   \n\n    [Fact]                                                                        \n    [Scenario(\"User requests a sell before close of trading\")]                    \n    public void test_with_extension_methods()                                     \n    {                                                                             \n        FeatureContext.Setup();                                                   \n                                                                                  \n        GIVEN                                                                     \n            .I_have_100_shares_of_MSFT_stock().And                                \n            .I_have_150_shares_of_APPL_stock();                                   \n        WHEN                                                                      \n            .I_ask_to_sell_20_shares_of_MSFT_stock();                             \n        THEN                                                                      \n            .I_should_have_80_shares_of_MSFT_stock().And                          \n            .I_should_have_150_shares_of_APPL_stock().And                         \n            .A_sell_order_for_20_shares_of_MSFT_stock_should_have_been_executed();\n    } \n}    \n```\n\n```c#\nusing ARRANGE = Arrange\u003cTradingContext\u003e;\nusing ACT = Act\u003cTradingContext\u003e;\nusing ASSERT = Assert\u003cTradingContext\u003e;\n    \npublic static class TradingMethods\n{\n    public static ARRANGE I_have_100_shares_of_MSFT_stock(this ARRANGE fixtures) =\u003e\n        fixtures.Setup(f =\u003e f.Trading.Buy(new TradingOrder(\"MSFT\", 100, new DateTime(2023, 1, 1, 10, 0, 0))));\n\n    public static ARRANGE I_have_150_shares_of_APPL_stock(this ARRANGE fixtures) =\u003e\n        fixtures.Setup(f =\u003e f.Trading.Buy(new TradingOrder(\"APPL\", 150, new DateTime(2023, 1, 1, 10, 0, 0))));\n\n    public static ACT I_ask_to_sell_20_shares_of_MSFT_stock(this ACT fixtures) =\u003e\n        fixtures.It(f =\u003e f.Trading.Sell(new TradingOrder(\"MSFT\", 20, new DateTime(2023, 1, 1, 10, 0, 0))));\n\n    public static ASSERT I_should_have_80_shares_of_MSFT_stock(this ASSERT fixtures) =\u003e\n        fixtures.Expect(x =\u003e x.Trading.Shares[\"MSFT\"].Should().Be(80));\n\n    public static ASSERT I_should_have_150_shares_of_APPL_stock(this ASSERT fixtures) =\u003e\n        fixtures.Expect(x =\u003e x.Trading.Shares[\"APPL\"].Should().Be(150));\n\n    public static ASSERT A_sell_order_for_20_shares_of_MSFT_stock_should_have_been_executed(this ASSERT fixtures) =\u003e\n        fixtures.Expect(x =\u003e x.Trading.Orders[\"MSFT\"].Should().Be(20));\n}                                                                                \n```\n\nThe `TradingMethods` class defines extension methods for the `ARRANGE`, `ACT`, and `ASSERT` classes, which represent the setup, action, and verification phases of the test scenario. These extension methods encapsulate the details of the trading system's API and provide a simple, readable syntax for defining the test scenario steps.\n\nOverall, this code defines a BDD-style test suite for a trading system, with a clear separation of concerns between the test scenario definition and the implementation details of the trading system.\n\n# Using Variables\n```c#\npublic class StocksTests : Feature\u003cTradingContext, TradingFixture\u003e, IClassFixture\u003cTradingContext\u003e\n{\n    public StocksTests(ITestOutputHelper output, TradingContext context) : base(context)\n    {                                                                                   \n        SetOutputRedirect(new TestOutputRedirect(output));                              \n    } \n    \n    [Theory]\n    [InlineData(100, 20, 80, \"MSFT\")]\n    [InlineData(100, 50, 50, \"APPL\")]\n    [Scenario(@\"User requests a sell before close of trading\")]\n    public void test_theory_with_attribute_mapping(int share, int sells, int total, string asset)\n    {\n        Let.Load(new { x = share, y = asset, z = sells, w = total });\n        \n        Describe(\"User trades stocks\",\n            GIVEN\n            | \"I have :x shares of :y stock\" |\n            WHEN\n            | \"I ask to sell :z shares of :y stock\" |\n            THEN\n            | \"I should have :w shares of :y stock\");\n    }\n}\n```\nThe class defines a single test method called `test_theory_with_attribute_mapping`, which is marked with the `Theory` attribute. This attribute indicates that the test method is a data-driven test and will be executed multiple times with different sets of data. In this case, the test is executed with two sets of data, specified as the `InlineData` attributes.\n\nThe `test_theory_with_attribute_mapping` method takes four parameters, `share`, `sells`, `total`, and `asset`, which correspond to the data specified in the `InlineData` attributes. The method uses the `Let.Load` method to load the data into the test scenario and then describes the scenario using the `Describe` method.\n\n```c#\npublic class TradingFixture : ScenarioFixture\u003cTradingContext\u003e\n{\n    [Given(\"I have :x shares of :y stock\")]\n    public void HaveDynamicSharesOfMsftStock() =\u003e\n        Context?.Trading.Buy(new TradingOrder(Let[\"y\"].As\u003cstring\u003e(), Let[\"x\"].As\u003cint\u003e(),\n            new DateTime(2023, 1, 1, 10, 0, 0)));\n            \n    ...            \n}            \n```\nThe scenario involves the user trading stocks, and the `GIVEN`, `WHEN`, and `THEN` sections describe the initial state, the action taken by the user, and the expected result, respectively. The `:x`, `:y`, `:z`, and `:w` placeholders in the scenario string are replaced with the values of the corresponding parameters at runtime.\n\n# Guide to contributing to a GitHub project\nThis is a guide to contributing to this open source project that uses GitHub. It’s mostly based on how many open sorce projects operate. That’s all there is to it. The fundamentals are:\n\n- Fork the project \u0026 clone locally.  \n- Create an upstream remote and sync your local copy before you branch.  \n- Branch for each separate piece of work.  \n- Do the work, write good commit messages, and read the CONTRIBUTING file if there is one.  \n- Push to your origin repository.  \n- Create a new PR in GitHub.  \n- Respond to any code review feedback.  \n\nIf you want to contribute to an open source project, the best one to pick is one that you are using yourself. The maintainers will appreciate it!\n\n# References\n\n- [[1] - Given-When-Then](https://martinfowler.com/bliki/GivenWhenThen.html)\n- [[2] - Test fixture](https://en.wikipedia.org/wiki/Test_fixture)  \n- [[3] - 3A – Arrange, Act, Assert](https://xp123.com/articles/3a-arrange-act-assert/)  \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F8t4%2Fgwtdo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F8t4%2Fgwtdo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F8t4%2Fgwtdo/lists"}