Package: manyworlds
ManyworldsOrganize BDD scenarios as directed graphs for more concise and expressive feature files.BDD feature files can be verbose and repetitive. Consider the following four scenarios, represented as a series of actions (A) resulting in an observed outcome (O):S¹  A¹ → A² → A³: O¹
S²  A¹ → A² → A³ → A⁴: O²
S³  A¹ → A² → A³ → A⁵: O³
S⁴  A¹ → A² → A³ → A⁵ → A⁶: O⁴All four scenarios (S¹–S⁴) share the first three actions (A¹, A², A³). Scenarios S³ and S⁴ share one additional action (A⁵). This is very repetitive and makes it hard to understand how the scenarios are organized.Now consider the same scenarios represented as a directed graph:S¹  A¹ → A² → A³: O¹
S²             ↳ A⁴: O²
S³             ↳ A⁵: O³
S⁴                ↳ A⁶: O⁴The graph format has a few benefits:Many actions are now implied by their scenario's position in the graph and no longer need to be stated which eliminates repetition and noise.We can now see how scenarios relate to each other. Specifically, we can start thinking in terms of parent and child scenarios.We now have what amounts to a decision tree of possible paths that a user can take through the app. This makes it easier to notice missing scenarios.So how could we start experimenting with scenario graphs using existing tools? What if we could simply useindentationin feature files? This would limit us to one type of graph (trees/forests), but maybe that would cover the vast majority of use cases? That is the idea behind Manyworlds. With Manyworlds we can:Use indentation in feature files to organize scenarios as scenario trees.Expand indented feature files into standard, flat feature files which can be run using any testing tool that understands Gherkin.UsageHere is an example of an indented feature file:# indented.featureFeature:User DeactivationAs an administratorI want to deactivate users who leave the companySo that only authorized users have access to the systemScenario:View usersGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|# inactiveWhen I go to "Users"Then I see the following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active|Scenario:Deactivate userWhenI click "Deactivate" for user "Ben"AndI click "OK"ThenI see the following users: # I no longer see Ben|Name|Status||Alice|Active||Connie|Active|Scenario:Bulk operations # on multiple usersScenario: Select userWhenI select user "Ben"ThenI see "1 user selected"Scenario:Deselect userWhenI deselect user "Ben"ThenI see "0 users selected"Scenario:Select multiple usersWhenI select user "Alice"ThenI see "2 users selected"Scenario:Deselect all usersWhenI click "Deselect all"ThenI see "0 users selected"Scenario:Bulk deactivate usersWhenI click "Deactivate all"ThenI see a confirmation dialogScenario:Confirm bulk deactivation of users # by clicking "OK"WhenI click "OK"ThenI see "0 users selected"AndI see the following users: # I no longer see Ben or Alice|Name|Status||Connie|Active|Scenario:Cancel out of bulk deactivation of usersWhenI click "Cancel"ThenI see "2 users selected"AndI see the following users:|Name|Status||Ben|Active|# still there|Alice|Active|# still there|Connie|Active|# TODO: add a scenario for bulk activation of usersNow let's use Manyworlds to flatten it:python-mmanyworlds--inputindented.feature--outputflat.featureThis will print the structure of the scenario tree(s) to the terminal …Viewusers
├──Deactivateuser
└──Bulkoperations:└──Selectuser├──Deselectuser└──Selectmultipleusers├──Deselectallusers└──Bulkdeactivateusers├──Confirmbulkdeactivationofusers└──Canceloutofbulkdeactivationofusers… and write the following flat feature file:# flat.featureScenario:View usersGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"ThenI see the following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active|Scenario:Deactivate userGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"AndI click "Deactivate" for user "Ben"AndI click "OK"ThenI see the following users:|Name|Status||Alice|Active||Connie|Active|Scenario:[Bulk operations] Select userGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"AndI select user "Ben"ThenI see "1 user selected"Scenario:[Bulk operations] Deselect userGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"AndI select user "Ben"AndI deselect user "Ben"ThenI see "0 users selected"Scenario:[Bulk operations] Select multiple usersGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"AndI select user "Ben"AndI select user "Alice"ThenI see "2 users selected"Scenario:[Bulk operations] Deselect all usersGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"AndI select user "Ben"AndI select user "Alice"AndI click "Deselect all"ThenI see "0 users selected"Scenario:[Bulk operations] Bulk deactivate usersGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"AndI select user "Ben"AndI select user "Alice"AndI click "Deactivate all"ThenI see a confirmation dialogScenario:[Bulk operations] Confirm bulk deactivation of usersGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"AndI select user "Ben"AndI select user "Alice"AndI click "Deactivate all"AndI click "OK"ThenI see "0 users selected"AndI see the following users:|Name|Status||Connie|Active|Scenario:[Bulk operations] Cancel out of bulk deactivation of usersGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"AndI select user "Ben"AndI select user "Alice"AndI click "Deactivate all"AndI click "Cancel"ThenI see "2 users selected"AndI see the following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active|Flattening ModesBy default, Manyworlds creates one scenario pernodein the scenario tree, resulting in scenarios with one set of "whens" followed by one set of "thens" which is generally considered best practice. This is the "strict" mode.Manyworlds also supports a "relaxed" mode that creates one scenario perleaf nodein the scenario tree, resulting in fewer scenarios that may have multiple consecutive "when/then" pairs which is widely considered an anti-pattern. For starters, it makes it hard to name scenarios well. However, it does reduce repetition and will run faster:python-mmanyworlds--inputindented.feature--outputflat_relaxed.feature--moderelaxedThis will write the following "relaxed" flat feature file:# flat_relaxed.featureScenario:View users > Deactivate userGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"ThenI see the following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active|WhenI click "Deactivate" for user "Ben"AndI click "OK"ThenI see the following users:|Name|Status||Alice|Active||Connie|Active|Scenario:[Bulk operations] Select user > Deselect userGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"AndI select user "Ben"ThenI see "1 user selected"WhenI deselect user "Ben"ThenI see "0 users selected"Scenario:[Bulk operations] Select multiple users > Deselect all usersGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"AndI select user "Ben"AndI select user "Alice"ThenI see "2 users selected"WhenI click "Deselect all"ThenI see "0 users selected"Scenario:[Bulk operations] Bulk deactivate users > Confirm bulk deactivation of usersGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"AndI select user "Ben"AndI select user "Alice"AndI click "Deactivate all"ThenI see a confirmation dialogWhenI click "OK"ThenI see "0 users selected"AndI see the following users:|Name|Status||Connie|Active|Scenario:[Bulk operations] Cancel out of bulk deactivation of usersGiventhe following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active||Dan|Deactivated|WhenI go to "Users"AndI select user "Ben"AndI select user "Alice"AndI click "Deactivate all"AndI click "Cancel"ThenI see "2 users selected"AndI see the following users:|Name|Status||Ben|Active||Alice|Active||Connie|Active|File SizeManyworlds feature files are significantly shorter than the conventional output feature files, which is another reason I why find them easier to maintain. The exact factor is a function mostly of the depth of the scenario tree. A factor of around 3 is not uncommon.Organizational ScenariosScenarios without assertions are considered "organizational" and are used to group child scenarios only. In output feature files, organizationasl scenarios will not appear as their own scenarios, but their names are used as a "breadcrumb" in the names of their child scenarios. The "Bulk Operations" scenario in the above example is organizational.CommentsYou can add inline comments to just about anything in Manyworlds input feature files: Steps, scenarios and even data table rows! This is in contrast to theGherkin specificationwhich only allows comments on separate lines. By default, comment output is turned off when writing Manyworlds output files so they validate as Gherkin. You can turn it on by using the '--write-comments' flag.Using the Feature Class DirectlyIf you want to use Manyworlds in your code rather than using the cli, here's how to do that:importmanyworldsasmwmw.Feature.from_file('hierarchical.feature').flatten('flat.feature')InstallationpipinstallmanyworldsWhat If Test Runners Could Run Scenario Graphs Directly?I believe this is where it could get really interesting. A few examples:If a scenario fails in an action, the runner could mark all descendent scenarios as failing without even running them!A runner could use the 'relaxed' mode under the hood to run scenarios optimized for speed, then display test results using the 'strict' mode optimized for informational clarity.A runner could use network analysis tools to decide how to cleave apart the tree for optimal parallelization.I would think that these might result in significantly faster running (and faster failing) test suites. The display of test results might also be significantly more informative compared to what we have today.