如何使 Specflow 很好地处理日期/时间?

发布于 2024-10-20 08:31:49 字数 493 浏览 2 评论 0原文

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

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

如何进行测试的规范流方面?

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

I wish to be able to write tests like this:

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

How do I do the specflow side of the tests?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

∞觅青森が 2024-10-27 08:31:49

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

1:调试测试,测试将因不确定而失败。查看测试运行结果规范流可以帮助您添加此测试的模板。错误消息将类似于

Assert.Inconclusive failed。未找到一个或多个步骤的匹配步骤定义。

    [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属性的单元测试类。现在您可以采取一些技巧来帮助您。在 GiveAUserSignsUpForA30DayAccount 方法中,我将创建一个将在测试中使用的用户,该用户拥有 30 天的试用帐户。私有成员在这里可以正常工作,因此您可以在方法之间访问它们,但这仅在所有方法都在同一个类中时才有效。如果您尝试在多个功能/类之间重用方法,则需要考虑将对象保存到 ScenarioContext

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

(.*) 表示您正在向该方法传递一个字符串
(\d+) 表示您正在向该方法传递一个 int 。

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

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

4:最后将断言添加到 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);

    }
}

Building this .feature file will create a code behind for the tests. You then need to wire up each step to a method. The easiest way to do this is,

1: debug the tests, the test will fail as inconclusive. Looking at the Test run results specflow helps you by adding a template for this test. the error message will look something like this

Assert.Inconclusive failed. No matching step definition found for one or more steps.

    [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: Copy this into a new specflow step definition file, which is basically just unit test class populated with specflow attributes. Now there are some tricks you can do to help you out. in the GivenAUserSignsUpForA30DayAccount method i would create a user that will be used in the test that has a 30 day trial account. A private member would work fine here so you can access them between methods, but this only works if all the methods are in the same class. if you try to reuse methods between multiple features/classes you will need to look into saving your object into the ScenarioContext

3: When the specflow test runs it looks for a method that has an matching attribute with the same string. A trick here is that you can u pass parameters to the method using wild cards in the Method Attribute. There are 2 different file cards

(.*) means you are passing a string to that method
(\d+) means you are passing an int to that method.

Because your When method is common you could reuse it with parameters like this.

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

4: finally add your Asserts into the Then method so the end result looks something like this. (note that personally i would restructure the wording of the feature a bit and pass it expected results that way the test logic isn't as nasty as my example, look at scenario outlines for data driven tests)

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

    }
}
稀香 2024-10-27 08:31:49

我在如何处理 SpecFlow 中的相对日期和时间方面遇到了类似的问题,并通过支持规范内的模糊日期来解决这个问题。我使用了这个答案中的代码: 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 代码中的正则表达式以适应。

I faced a similar issue with how to cope with relative dates and times in SpecFlow, and approached it by supporting fuzzy dates within specifications. I used the code from this answer: Fuzzy Date Time Picker Control in C# .NET?, which would let you express what you want as follows:

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

With a step definition such as:

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

    // TODO: Use loginDateTime
}

If you don't like the syntax for the fuzzy dates, you can modify the regular expressions in the FuzzyDateTime code to suit.

入怼 2024-10-27 08:31:49

我想您可能正在寻找 StepArgumentTransformation

为了应对“31 天内”的情况,文档为您提供了:

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

对于“2 年内”的情况,您可以看到模式......

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

I think you may be looking for StepArgumentTransformation.

To cope with 'in 31 days', the docs have it for you:

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

And for 'in 2 years', you can see the pattern...

    [StepArgumentTransformation(@"in (\d+) years?")]
    public DateTime InXYearsTransform(int years)
   {
      return DateTime.Today.AddYears(years);
   }
泪之魂 2024-10-27 08:31:49
> 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
> how can the same step definitions cope with both "31 days" and "2 years time"

If your rules need no special handling for workingday, xmas, weekend, ... you can modify @Nitro52-s answer to:

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

Maybe you can also think about reformulating the scenarios like this

Scenario: access before expiry
  When they login on '2010-01-01'
    And TodayIs '2010-01-29'
  Then they will be let in
内心激荡 2024-10-27 08:31:49

我知道我为此挣扎了很长时间。我花了一段时间来更改我现有的代码,但是删除了对 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);
}

I know I struggled with this for a long time. It took a while to change my existing code, but killing all references to DateTime.Now and replacing them with an interface that I could Mock has made everything a million times easier to test (and change later). I have created an IDateTimeService that has one method "GetCurrent()". Now all my Given steps can say:

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

Then I can do range checking a lot easier.

The step for the current date looks like:

[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);
}
柠檬 2024-10-27 08:31:49

尝试使用 Moles 并存根 DateTime.Now 每次都返回相同的日期。 Moles 的最佳功能之一是能够将任何东西附近的 dang 转变为可以隔离的运行时委托。唯一的缺点是,它可能运行得更慢,具体取决于您选择的实现(存根与摩尔)。我刚刚开始深入研究它,所以请对我的建议持保留态度。

Try using Moles and stub the DateTime.Now to return the same date everytime. One of the best features of Moles is the ability to turn dang near anything into a runtime delegate that you can isolate. The only drawback is that it may run slower depending on the implementation you pick (stubbed vs moled). I'm just beginning to dive into it so take my suggestion with a grain of salt.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文