BDD'给定'步骤描述和实施

时间:2011-11-25 10:36:44

标签: c# tdd integration-testing bdd specflow

您通常如何描述和实施情景的“给定”步骤?

  1. 高级状态描述或显式数据定义?
  2. 填充数据库或存根库?

  3. 高级别状态说明

    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

    • 不清楚我们需要哪些数据

    明确的数据定义

    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

    • 难以阅读和理解业务目标
    • 更脆弱

    填充数据库

    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

    • 规范作为集成测试(我们使用系统结束2-end)

    CONS

    • 我们需要在代码之前创建数据库表(数据驱动方法)
    • 难以实施

    存储库存根

    // 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

    • 我们没有证明功能已实施

    这就是我对可能方式的看法:) 您以什么方式定义和实施“给定”步骤? 谢谢!

2 个答案:

答案 0 :(得分:1)

我们通过你提到的组合实现给定。通过使用DI和不同的配置(使用this tool变得容易),我们在内存中大部分时间运行单元测试,并在CI服务器上强制它们作为针对真实数据库的集成测试。因此,您可以获得性能和全面测试。

为了设置您的数据,我个人最喜欢您的示例'显式数据定义'。指定测试使用的数据可确保您可以将测试作为文档阅读。针对未知数据存储运行会使测试难以阅读。但是在这种情况下构建测试数据时,产品的名称并不重要,只有金额。

这是使用Builder模式处理的。仅指定为测试导入的数据,并让Builder为所有其他字段生成默认值。

NBuilder是一个非常好的工具。我们现在正在使用它来进行测试,看起来很有希望。

您的测试看起来像:

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);
}

答案 1 :(得分:1)

作为“我们如何设置复杂或相关对象的给定?”的广泛答案。问题 - 如产品和销售 - 所有取决于您指定的行为。没有一种正确的方法。顺便说一句,你没有包含功能和场景文本,以便为我们提供一些关于这个问题的行为的背景,但是,不可否认,这并不难猜测。

你的“不易碎”的第一个例子显示了一个很好的方法来驱逐基本行为,告诉我用户可以查看最畅销产品及其销售数字的简短列表。

如果你想证明显示器在某种程度上关注以排名的方式展示事物,那么你会更明确地给出,然后明确表示你按销售量进行“反向排序”。或者,这个明确的例子可以被认为是非常清楚你的意思。

我通常的经验法则是将设置限制为您在当前场景中要测试的“对象图”的那些部分。这有助于引起人们对正在测试的系统中最“狭窄”的部分的注意。否则,如果您继续从头开始为所有场景构建所有内容,有时很难看到测试的目的。有时您关心父对象中的细节,有时您想要测试各部分的总和。