6

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

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 年时间”

4

6 回答 6

3

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

1:调试测试,测试将因不确定而失败。查看测试运行结果规范流可以通过为此测试添加模板来帮助您。错误消息看起来像这样

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

    }
}
于 2011-03-04T02:10:47.450 回答
3

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

背景:
    假设用户注册了一个 30 天的帐户

场景:过期前访问
    当他们在接下来的 29 天内登录时
    然后他们将被允许进入

场景:过期后访问
    当他们在接下来的 31 天内登录时
    然后他们将被要求续订

场景:账号删除后访问
    当他们在未来 2 年内登录时
    然后他们将被要求注册一个新帐户

使用步骤定义,例如:

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

    // TODO: Use loginDateTime
}

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

于 2011-03-14T09:01:04.263 回答
2

我想您可能正在寻找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);
   }
于 2016-11-11T17:38:03.493 回答
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
于 2011-03-14T09:23:04.937 回答
1

我知道我为此苦苦挣扎了很长时间。更改我现有的代码花了一些时间,但是杀死所有对 DateTime.Now 的引用并用我可以模拟的接口替换它们已经使测试变得更容易一百万倍(并在以后更改)。我创建了一个具有一种方法“GetCurrent()”的 IDateTimeService。现在我所有的 Given 步骤都可以说:

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);
}
于 2012-01-16T15:30:08.823 回答
-2

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

于 2011-04-06T13:25:14.780 回答