Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236248

BDD 'Given' step description and implementation

How do you usually describe and implement 'Given' step for scenario?

  1. High level state description OR explicit data definitions?
  2. Fill database or stub repository?

High level state description

Given I have 4 products
When I look for best-selling products
Then I see top 3 products with maximum number of sales

PRO

CON

Explicit data definition

Given I have following products:
  | Name         | Sales number    |
  | Beer         | 20              |
  | Pizza        | 5               |
  | Socks        | 3               |      
  | Toilet paper | 100             |
When I look for best-selling products
Then I see following products:
  | Name         | Sales number    |
  | Toilet paper | 100             |
  | Beer         | 20              |
  | Pizza        | 5               |

PRO

CON


Fill database

using (var connection = new SqlConnection(connectionString))
{                
    using (var deleteCommand = new SqlCommand("DELETE FROM Products", connection))
    {
        connection.Open();
        deleteCommand.ExecuteNonQuery();
    }

    SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM Products", connection);              
    DataSet data = new DataSet();
    adapter.Fill(data);

    foreach (var specFlowRow in table.Rows)
    {
        DataRow dataRow = data.Tables[0].Rows.Add();
        dataRow["Name"] = specFlowRow["Name"];                   
    }

    adapter.Update(data);
}

PRO

CONS

Repository stub

// or get stubbed repository from DI framework
productsRepository = new InMemoryProductsRepository();            

// or use specflow assist helpers
foreach (var specFlowRow in table.Rows)            
    productsRepository.Save(new Product(specFlowRow["Name"]));

PRO

CONS


Thats my vision of possible ways :) What way do you define and implement 'Given' steps? Thanks!

Upvotes: 2

Views: 990

Answers (2)

Jon Kern
Jon Kern

Reputation: 3235

As a broad answer to the "How do we setup the Given for complex or related objects?" question -- like Products and Sales -- it all depends on what behavior you are specifying. There is no single, right way. BTW, you did not include the Feature and Scenario text to give us some context for what behavior this cuke is for, but, admittedly, it is not hard to guess.

Your "not brittle" first example shows a good way to drive out basic behavior that tells me a user can see a short list of top-selling products and their sales numbers.

If you wanted to prove out that the display somehow cares about showing things in a ranked fashion, your more explicit given and then makes it clear that you are "reverse sorting" by sales volume. Or, this explicit example can be thought of as making it really clear what you mean.

My usual rule of thumb is to limit the setup to just those parts of the "object graph" that you care to test in the current scenario. That helps draw attention to the most "narrow" bits of the system that are under test. Otherwise, if you continue to build everything from scratch for all scenarios, it is sometimes hard to see the purpose of the test. Sometimes you care about the details in the parent object, sometimes you want to test the sum of the parts.

Upvotes: 1

Wouter de Kort
Wouter de Kort

Reputation: 39898

We implement Given by a combination things you mention. By using DI and different configurations (made easy with this tool) we run our unit tests most of the time in memory and force them once on the CI server as integration tests against a real database. So you get both performance and thorough testing.

For setting up your data, I personally like your example 'Explicit data definition' best. Specifying which data the tests uses makes sure you can read the test as documentation. Running against an unknown data store makes the tests hard to read. But when building your test data in this case, the name of the product is not important, only the amount.

This is handled by using the Builder pattern. Only specify the data that is import for your test and let the Builder generate default values for all other fields.

NBuilder is a really nice tool. We are using it now for our tests and it looks very promising.

Your test would look like:

class Product
{
    public string Name { get; set; }
    public int Sales { get; set; }
}

[TestMethod]
public void SalesTest()
{
    var products = Builder<Product>.CreateListOfSize(4)
         .TheFirst(1)
         .With(x => x.Sales = 20)
         .AndTheNext(1)
         .With(x => x.Sales = 5)
         .AndTheNext(1)
         .With(x => x.Sales = 3)
         .AndTheNext(1)
         .With(x => x.Sales = 100).Persist();

    var result = SystemUnderTest.Execute();

    Assert.AreEqual(3, result.Count);

    Assert.AreEqual(100, result[0].Sales);
    Assert.AreEqual(20, result[0].Sales);
    Assert.AreEqual(5, result[0].Sales);
}

Upvotes: 1

Related Questions