如何使specflow能够很好地应对日期/时间?

时间:2011-03-03 22:49:36

标签: .net specflow gherkin

我希望能够编写这样的测试:

Background:
  Given a user signs up for a 30 day account

Scenario: access before expiry
  When they login in 29 days
  Then they will be let in

Scenario: access after expiry
  When they login in 31 days
  Then they will be asked to renew

Scenario: access after acounnt deleted
  When they login in 2 years time
  Then they will be asked to register for a new account

如何进行测试的specflow?

编辑:相同的步骤定义如何处理“31天”和“2年时间”

6 个答案:

答案 0 :(得分:3)

构建此.feature文件将为测试创建一个代码。然后,您需要将每个步骤连接到方法。最简单的方法是,

1:调试测试,测试将失败,但不确定。查看测试运行结果specflow可帮助您为此测试添加模板。错误消息看起来像这样

Assert.Inconclusive失败。找不到一个或多个步骤的匹配步骤定义。

    [Binding]
public class StepDefinition1
{
    [Given(@"a user signs up for a 30 day account")]
    public void GivenAUserSignsUpForA30DayAccount()
    {
    }

    [When(@"they login in 29 days")]
    public void WhenTheyLoginIn29Days()
    {
        ScenarioContext.Current.Pending();
    }

    [Then(@"they will be let in")]
    public void ThenTheyWillBeLetIn()
    {
        ScenarioContext.Current.Pending();
    }
}

2:将其复制到新的specflow步骤定义文件中,该文件基本上只是使用specflow属性填充的单元测试类。现在有一些技巧可以帮助你。在GivenAUserSignsUpForA30DayAccount方法中,我将创建一个用户,该用户将在具有30天试用帐户的测试中使用。私有成员在这里可以正常工作,因此您可以在方法之间访问它们,但这仅在所有方法都在同一个类中时才有效。如果您尝试在多个功能/类之间重用方法,则需要考虑将对象保存到ScenarioContext

3:当specflow测试运行时,它会查找具有相同字符串的匹配属性的方法。这里的一个技巧是你可以使用方法属性中的通配符将参数传递给方法。有2种不同的文件卡

(。*)表示您将字符串传递给该方法 (\ d +)表示您将int传递给该方法。

因为您的When方法很常见,所以可以使用这样的参数重复使用它。

    [When(@"they login in (\d+) days")]
    public void WhenTheyLoginInDays(int daysRemaining)
    {
        Account.DaysRemaining = daysRemaining;
    }

4:最后将你的Asserts添加到Then方法中,最终结果看起来像这样。 (请注意,我个人会重新调整该功能的措辞并传递预期结果,因为测试逻辑并不像我的示例那样令人讨厌,请查看数据驱动测试的场景大纲)

    [Binding]
public class StepDefinition1
{
    UserAccount user;

    [Given(@"a user signs up for a 30 day account")]
    public void GivenAUserSignsUpForA30DayAccount()
    {
        user = AccountController.CreateNewUser("bob", "password", AccountType.Trial);
    }

    [When(@"they login in (\d+) days")]
    public void WhenTheyLoginInDays(int daysRemaining)
    {
        Account.DaysRemaining = daysRemaining;
    }

    [Then(@"they will (.*)")]
    public void ThenTheyWillBeLetIn(string expected)
    {
        //check to see which test we are doing and then assert to see the expected result.
        if(string.Compare(expected, "be let in", true)
            Assert.AreEqual(LoginResult.Passed, LoginService.Login);
        if(string.Compare(expected, "be asked to renew", true)
            Assert.AreEqual(LoginResult.Passed, LoginService.Login);

    }
}

答案 1 :(得分:3)

我遇到了类似的问题,如何在SpecFlow中处理相对日期和时间,并通过在规范中支持模糊日期来解决它。我使用了这个答案的代码:Fuzzy Date Time Picker Control in C# .NET?,它可以让你表达你想要的内容如下:

Background:
    Given a user signs up for a 30 day account

Scenario: access before expiry
    When they login in the next 29 days
    Then they will be let in

Scenario: access after expiry
    When they login in the next 31 days
    Then they will be asked to renew

Scenario: access after account deleted
    When they login in the next 2 years
    Then they will be asked to register for a new account

使用步骤定义,例如:

[When(@"they login in the (.*)")]
public void WhenTheyLoginIn(string loginDateTimeString)
{
    DateTime loginDateTime = FuzzyDateTime.Parse(loginDateTimeString);

    // TODO: Use loginDateTime
}

如果您不喜欢模糊日期的语法,可以修改FuzzyDateTime代码中的正则表达式以适应。

答案 2 :(得分:1)

> how can the same step definitions cope with both "31 days" and "2 years time"

如果你的规则不需要特别处理工作日,圣诞节,周末......你可以修改@ Nitro52-s的答案:

[When(@"they login in (\d+) days")]
public void WhenTheyLoginInDays(int daysRemaining)
{
    Account.RegristrationDate = DateTime.ToDay().SubtractDays(daysRemaining);
    Account.VerificationDate = DateTime.ToDay();    
}

也许你也可以考虑重新制定像这样的场景

Scenario: access before expiry
  When they login on '2010-01-01'
    And TodayIs '2010-01-29'
  Then they will be let in

答案 3 :(得分:1)

我知道我很长时间都在努力。我需要一段时间来改变我现有的代码,但是杀死对DateTime.Now的所有引用并用一个我可以Mock的界面替换它们,这使得所有内容都变得容易测试(并且稍后更改)。我创建了一个IDateTimeService,它有一个方法“GetCurrent()”。现在我的所有给定步骤都可以说:

Given the current date is '2/4/12'
And the user's account was created on '1/24/12'

然后我可以更轻松地进行范围检查。

当前日期的步骤如下:

[Given(@"Given the current date is '(.*)'")]
public void GivenTheCurrentDateIs(string date)
{
     var dateServiceMock = new Mock<IDateTimeService>();
     dateServiceMock.Setup(ds => ds.GetCurrent()).Returns(DateTime.Parse(date));
     ScenarioContext.Current.Add("dateService", dateServiceMock);
}

答案 4 :(得分:1)

我想你可能正在寻找StepArgumentTransformation

为了在31天内处理这些问题,文档会为您提供:

[Binding]
public class Transforms
{
    [StepArgumentTransformation(@"in (\d+) days?")]
    public DateTime InXDaysTransform(int days)
   {
      return DateTime.Today.AddDays(days);
   }
}

对于&#39; 2年后,您可以看到模式......

    [StepArgumentTransformation(@"in (\d+) years?")]
    public DateTime InXYearsTransform(int years)
   {
      return DateTime.Today.AddYears(years);
   }

答案 5 :(得分:-2)

尝试使用Moles并将DateTime.Now存根,每次都返回相同的日期。 Moles的最佳功能之一是能够将任何东西转变为可以隔离的运行时委托。唯一的缺点是它可能会运行得慢,具体取决于你选择的实现(stubbed vs moled)。我刚刚开始潜入它,所以我的建议就是一点点盐。