SpecFlow: konwersja argumentów

O SpecFlow pisałem już kilkakrotnie. Zwykłe w pliku ze scenariuszami, podajemy argumenty. Na przykład:

Scenario: Add two numbers
    Given I have entered 50 into the calculator
    And I have entered 70 into the calculator
    When I press add
    Then the result should be 120 on the screen

Następnie w pliku c# możemy użyć regex:

[Given(@"I have entered (.*) into the calculator")]
public void GivenIHaveEnteredIntoTheCalculator(int p0)
{
  ScenarioContext.Current.Pending();
}

Czasami jednak, mamy bardziej skomplikowane wyrażenia i za każdym razem regex po prostu byłby niewygodny. Na szczęście możemy napisać własny konwertery.

Załóżmy, że mamy scenariusz typu “Given it happened 50 days ago.”.  Chcemy liczbę 50 skonwertować do DateTime .AddDays(-50). SpecFlow umożliwia własne transformacje:

[Binding]
public class SpecFlowFeature1Steps
{
   [StepArgumentTransformation(@"(\d+) days ago")]
   public DateTime ToDateTime(int days)
   {
       return DateTime.Now.AddDays(-days);
   }
   [Given(@"it happened (.*)")]
   public void GivenItHappenedDaysAgo_(DateTime dateTime)
   {
       ScenarioContext.Current.Pending();
   }
}

Wystarczy metodę konwertującą oznaczyć atrybutem StepArgummentTransformation.

Czasami w pliku scenariusza umieszczamy tabele danych np.:

Given I entered data about the following employees:
    | FirstName | LastName  | Title             |
    | Piotr     | Zielinski | Software Engineer |
    | A         | B         | C                 |

Klasyczny sposób dostępu do takich danych wyglądałby następująco:

[Given(@"I entered data about the following employees:")]
public void GivenIEnteredDataAboutTheFollowingEmployees(Table table)
{
  foreach (TableRow row in table.Rows)
  {
      string firstName = row["FirstName"];
      string lastName = row["LastName"];
  }
}

Podejście niezbyt eleganckie – zwykle chcemy operować na prawdziwych obiektach. Prawdopodobnie gdzieś w systemie mamy następujący obiekt:

public class Employee
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public string Title { get; set; }
}

Wtedy za pomocą CreateSet możemy:

[Given(@"I entered data about the following employees:")]
public void GivenIEnteredDataAboutTheFollowingEmployees(Table table)
{
  IEnumerable<Employee> employees = table.CreateSet<Employee>();
}

Konwersja jest dokonywana na podstawie nazw. Kolumna FirstName jest kojarzona z właściwością o takiej samej nazwie. Rozmiar liter jednak nie ma znaczenia i zarówno FIRSTNAME jak i firstname zostaną skojarzone z właściwością FirstName. Również nie musimy martwić się o spacje czyli “first name” w pliku ze scenariuszami zostanie prawidłowo zinterpretowane. Dobrze o tym wiedzieć ponieważ scenariusze powinny wyglądać jak normalne zdania po angielsku a nie kod.

W jednym z postów pisałem o podobnym framework’u, a mianowicie SpecsFor. Zawierał on m.in expectedObjects – bardzo przydatną bibliotekę w walidacji obiektów oraz kolekcji obiektów. Zamiast samemu porównywać właściwość po właściwości, wystarczyło, że wywołaliśmy jedną metodę. Analogiczną rzecz możemy zrobić z tabelami w SpecFlow:

var expectedEmployees=new Employee[2];
expectedEmployees[0] = new Employee() {FirstName = "Piotr"};

table.CompareToSet(expectedEmployees);

Powyższy kod wywoła następujący wyjątek:

An exception of type 'TechTalk.SpecFlow.Assist.ComparisonException' occurred in TechTalk.SpecFlow.dll but was not handled in user code

Additional information: 

The table and the set not match.

Istnieje również funkcja do porównywania pojedynczych instancji:

table.CompareToInstance(employee);

Leave a Reply

Your email address will not be published.