Category Archives: C#

c# 7.0: Parametry wyjściowe

Od dawna (chyba od początku) w C# do dyspozycji mieliśmy słowo kluczowe out, służące do przekazywania parametrów na wyjście. Standardem stało się pisanie metod typu TryParse, które zwracają bool oraz wartość za pomocą parametru out, np.:

         int number;
         bool result = Int32.TryParse(text, out number);
         if (result)
         {
            Console.WriteLine("Converted '{0}' to {1}.", text, number);         
         }
         else
         {
           // error
         }

Składnia bardzo niewygodna. Trzeba było osobno deklarować zmienną przekazywaną jako out. Ponadto, funkcje takie często akceptowały kilka parametrów out. W takich sytuacjach, musieliśmy deklarować puste “kontenery” ponieważ nie było możliwości przekazania NULL albo jakieś innej wartości. W skrajny sytuacjach wyglądało to tak:

int value;
// unnecessary variables
int container1,container2, container3;
DoSomething(out value, out container1, out container2, out container3);

// do something with value

W C# 7.0 nie musimy osobno deklarować takich zmiennych i powyższy kod można przepisać do:

            if (Int32.TryParse("4343", out int number))
            {
                Console.WriteLine(number);
            }
            else
            {
                // error
            }

Jak widzimy, deklaracja odbywa się w wewnątrz wywołania. Po kompilacji uzyskamy klasyczny kod:

    private static void Main(/*Parameter with token 08000001*/string[] args)
    {
      int result;
      if (!int.TryParse("4343", out result))
        return;
      Console.WriteLine(result);
    }

Ze względu, że sygnatura jest z góry znana, można skorzystać ze słowa “var” zamiast deklarować jawnie typ integer:

               
            if (Int32.TryParse("4343", out var number))
            {
                Console.WriteLine(number);
            }
            else
            {
                // error
            }

Możliwe, że będzie można korzystać z wildcards czyli (nie zostało to jeszcze zatwierdzone do finalnej wersji 7.0):

        static void Main(string[] args)
        {            
            if (Int32.TryParse("4343", out *))
            {
                Console.WriteLine("OK");
            }
            else
            {
                // error
            }
        }

Byłoby to bardzo przydatne w sytuacjach, gdzie zainteresowani jesteśmy tylko jedną wartością, a sygnatura wymaga od nas podania np. 5 innych zmiennych. Niestety na dzień dzisiejszy nie jest to zaimplementowane w najnowszej wersji VS (Visual Studio 15′ Preview 4).

NUnit 3 – testy wykonywane równolegle

Wersja nUnit 3.0 wspiera wykonywanie testów równolegle.  Jeśli mamy testy integracyjne albo akceptacyjne, wtedy wykonywanie ich równolegle, w znaczący sposób może przyśpieszyć  CI. W przypadku testów jednostkowych nie ma już to takiego znaczenia, bo i tak powinny one wykonywać się szybko.

Zaczynamy oczywiście od instalacji pakietu nuget NUnit 3. Załóżmy, że mamy następujące, czasochłonne do wykonania testy:

   [TestFixture]
    public class Tests1
    {
        [Test]
        public void Test1()
        {
            Console.WriteLine("Test 1 - start");
            Thread.Sleep(1000);
            Console.WriteLine("Test 1 - end");
        }
    }

    [TestFixture]
    public class Tests2
    {
        [Test]
        public void Test2()
        {
            Console.WriteLine("Test 2 - start");
            Thread.Sleep(1000);
            Console.WriteLine("Test 2 - end");
        }
    }

    [TestFixture]
    public class Tests3
    {
        [Test]
        public void Test3()
        {
            Console.WriteLine("Test 3 - start");
            Thread.Sleep(1000);
            Console.WriteLine("Test 3 - end");
        }
    }

    [TestFixture]
    public class Tests4
    {
        [Test]
        public void Test4()
        {
            Console.WriteLine("Test 4 - start");
            Thread.Sleep(1000);
            Console.WriteLine("Test 4 - end");
        }
    }

Jeśli wykonamy je teraz, zostaną one odpalone w sposób sekwencyjny:

2

Czas wykonania to około 5 sekund, ponieważ domyślnie nie są one uruchamiane równoległe.
W Assembly.cs możemy umieścić globalny atrybut, który umożliwi ich zrównoleglenie, tzn.:

[assembly: Parallelizable(ParallelScope.Fixtures)]

Wynik:

1

Widzimy, że czas ich wykonania zmalał do 2 sekund.

Jeśli jakiś test chcemy wyłączyć z zrównoleglenia, wtedy wystarczy umieścić następujący atrybut:

[Parallelizable(ParallelScope.None)]

Jak widać, atrybut możemy stosować zarówno globalnie jak i selektywnie na konkretnych testach. Dobrze napisane testy nie powinny zależeć od kolejności ich wykonywania, zatem umieszczenie atrybutu Parallelizable globalnie jest rozsądnym podejściem.
Domyślna liczba wątków służąca do odpalenia testów zależy od procesora, a konkretnie liczby rdzeni. Możliwie jest jednak ręczne sterowanie liczbą wątków za pomocą następującego atrybutu:

[assembly:LevelOfParallelism(5)]

Warto również wspomnieć, że na dzień dzisiejszy nie istnieje możliwość wykonania konkretnych testów (metod) równolegle. Innymi słowy, możliwe jest zrównoleglenie wyłącznie klas (TestFixture).

C# 7.0 – Tuples

Klasa (immutable) Tuple istniała aż od .NET Framework 4.0. W C# 7.0 mamy jednak wsparcie dla tuple od strony języka. Jednym z problemów klasy Tuple było, że każda właściwość nazywała się kolejno Item1, Item2 itp.

Przykład:

    class Program
    {
        private static void Main(string[] args)
        {
            var tuple = GetFirstAndLastName();

            Console.WriteLine(tuple.Item1);
            Console.WriteLine(tuple.Item2);
        }

        private static Tuple<string,string> GetFirstAndLastName()
        {
            var tuple = new Tuple<string, string>("Piotr", "Zielinski");

            return tuple;       
        }
    }

W wersji 7.0, powyższy kod możemy przepisać do:

    class Program
    {
        private static void Main(string[] args)
        {
            var tuple = GetFirstAndLastName();

            Console.WriteLine(tuple.firstName);
            Console.WriteLine(tuple.lastName);
        }

        private static (string firstName, string lastName) GetFirstAndLastName()
        {
            var fullName = (firstName: "Piotr", lastName: "Zielinski");

            return fullName;
        }
    }

Składnia dużo czytelniejsza ponieważ nie operujemy już na Item1 i Item2. Zobaczmy co się kryje za tym nowym “Tuple” po dekompilacji:

  internal class Program
  {
    // Method .ctor with token 06000003
    public Program()
    {
      base.\u002Ector();
    }

    // Method Main with token 06000001
    private static void Main(/*Parameter with token 08000001*/string[] args)
    {
      ValueTuple<string, string> firstAndLastName = Program.GetFirstAndLastName();
      Console.WriteLine(firstAndLastName.Item1);
      Console.WriteLine(firstAndLastName.Item2);
    }

    // Method GetFirstAndLastName with token 06000002
    private static ValueTuple<string, string> GetFirstAndLastName()
    {
      return new ValueTuple<string, string>("Piotr", "Zielinski");
    }
  }

Całość opiera się na zwykłej strukturze ValueTuple<string, string>. Wygląda to bardzo analogicznie do starego Tuple<T1,T2>. Zaglądając do źródeł ValueTuple, przekonamy się, że klasa jest immutable i  przeładowuje metody typu Equals oraz GetHashCode. Oznacza to, że typ doskonale nadaję się jako klucz do słowników.

Powyższy kod można przetestować używając Visual Studio ’15’ preview 3,  który jest dostępny tutaj.

Unity 3d – wprowadzenie do skryptów

Dzisiaj napiszemy nasz pierwszy skrypt w Unity 3d.

Najpierw dodajmy jakiś element na scene, który potem będziemy mogli obracać za pomocą skryptu. W tym celu z menu głównego wybieramy  GameObject->3d Object-> Cube:

1

Następnie w celu dodania skryptu klikamy na “Assets” w panelu “Project” i wybieramy  Create->C# Script:

2

Zanim przejdziemy do edycji skryptu, warto upewnić się, że Visual Studio jest skonfigurowany jako narzędzie do pisania kodu. W tym celu wchodzimy do Edit->Preferences. External Script Editor powinien być ustawiony na VS:

3

Teraz możemy dwa razy kliknąć na skrypcie i Unity 3d odpali instancję Visual Studio w raz z szablonem nowo wygenerowanego skryptu:

public class SpinCube : MonoBehaviour {

	// Use this for initialization
	void Start () {
	
	}
	
	// Update is called once per frame
	void Update () {
	
	}
}

Nas interesuje wyłącznie metoda Update, która jest odpalana w każdej ramce renderowania. To właśnie tam będą znajdować się wszelkie transformacje. Jeśli chcemy obracać obiektem wtedy należy skorzystać z pola “transform”:

public class SpinCube : MonoBehaviour
{
	// Use this for initialization
	void Start () {
	
	}
	
	// Update is called once per frame
	void Update ()
	{
             transform.Rotate(0, 1, 0);	
	}
}

Właśnie napisaliśmy nasz pierwszy skrypt. Musimy jednak go doczepić do jakiegoś obiektu. W naszym przypadku będzie to dodany wcześniej sześcian. Wystarczy na niego kliknąć i po prostu przeciągnąć skrypt:

4

Od tego momentu klasa SpinCube będzie wykonana w każdej ramce renderowania. Oczywiście ten sam skrypt możemy doczepiać do różnych obiektów. Po naciśnięci przycisku “Play”, sześcian zacznie obracać się.

Zwykle skrypty przyjmują jakieś parametry wejściowe. W naszym przypadku może to być prędkość obracania. Z punktu widzenia C#, jest to zwykłe pole publiczne:

public class SpinCube : MonoBehaviour
{
        public int Speed;

	void Start () {
	
	}
	
	// Update is called once per frame
	void Update ()
	{
        transform.Rotate(0, Speed, 0);	
	}
}

Po ponownym kliknięciu obiektu, zobaczymy, że “Speed” jest teraz konfigurowalny:

5

Typ Geography w Dapper

W poprzednim wpisie zajęliśmy się typem Geography, który jest przydatny na operacjach związanych z współrzędnymi geograficznymi. Jak wiemy, zapytania SQL wyglądają dość skomplikowanie, tzn.:

INSERT INTO Locations (Location)  
VALUES (geography::STGeomFromText('POLYGON((-122.358 47.653 , -122.348 47.649, -122.348 47.658, -122.358 47.658, -122.358 47.653))', 4326)); 

Na szczęście Dapper ma wsparcie dla Geography i nie trzeba samemu tworzyć powyższych zapytań. Przede wszystkim należy skorzystać z klasy SqlGeography, która znajduje się w Microsoft.SqlServer.Types.dll. Jeśli ta biblioteka nie jest dołączona do projektu, należy dodać odpowiednią referencję:
1

Następnie, dodanie nowej wartości wygląda następująco:

            using (var connection = new SqlConnection(connectionString))
            {
                var geography = SqlGeography.STLineFromText(
                    new SqlChars(new SqlString("LINESTRING(-122.360 47.656, -122.343 47.656 )")), 4326);

                var pars = new { geo = geography };

                connection.Execute("INSERT INTO [Locations] (Location) values (@geo)", pars);
            }

Jak widać, Dapper rozpozna typ SqlGeography i wygeneruje odpowiedni kod SQL. Nie musimy ręcznie wstrzykiwać żadnego zapytania i korzystamy z parametrów tak jak to ma miejsce z innymi typami.

Odczytywanie współrzędnych geograficznych

Ostatnio musiałem odczytać współrzędne geograficzne na podstawie nazwy lokalizacji.

Pierwszą opcją, którą sprawdziłem był pakiet “GoogleMaps.LocationServices”. Po instalacji NuGet, odczytanie współrzędnych było bardzo proste:

            var gls = new GoogleLocationService();
            var location = gls.GetLatLongFromAddress("Warsaw, Poland");

            Console.WriteLine("{0}, {1}", location.Latitude,location.Longitude);

Wynik:
1

Rozwiązanie bardzo proste. Biblioteka korzysta z Google Maps, ale niestety ma limity na liczbę zapytań w ciągu dnia. Co gorsza, nie istnieje możliwość zwiększenia tego limitu.

Z tego względu zdecydowałem się na bezpośrednie połączenie z Google API, a konkretnie z Geocoding API. Co prawda, rozwiązanie też ma limity dzienne, ale po darmowej rejestracji uzyskamy limit 100k dziennie, co w moim w wypadku było wystarczające. API jest w formie REST:
“https://maps.googleapis.com/maps/api/geocode/json?address=Warsaw,Poland&key=YOUR_API_KEY”

Oczywiście należy najpierw uzyskać klucz, rejestrując konto na Console Google. Przykładowa odpowiedź przychodzi w następującym formacie:

{
   "results" : [
      {
         "address_components" : [
            {
               "long_name" : "Warsaw",
               "short_name" : "Warsaw",
               "types" : [ "locality", "political" ]
            },
            {
               "long_name" : "Warszawa",
               "short_name" : "Warszawa",
               "types" : [ "administrative_area_level_3", "political" ]
            },
            {
               "long_name" : "Warszawa",
               "short_name" : "Warszawa",
               "types" : [ "administrative_area_level_2", "political" ]
            },
            {
               "long_name" : "Masovian Voivodeship",
               "short_name" : "Masovian Voivodeship",
               "types" : [ "administrative_area_level_1", "political" ]
            },
            {
               "long_name" : "Poland",
               "short_name" : "PL",
               "types" : [ "country", "political" ]
            }
         ],
         "formatted_address" : "Warsaw, Poland",
         "geometry" : {
            "bounds" : {
               "northeast" : {
                  "lat" : 52.3679992,
                  "lng" : 21.2710983
               },
               "southwest" : {
                  "lat" : 52.0978767,
                  "lng" : 20.8512898
               }
            },
            "location" : {
               "lat" : 52.2296756,
               "lng" : 21.0122287
            },
            "location_type" : "APPROXIMATE",
            "viewport" : {
               "northeast" : {
                  "lat" : 52.3679992,
                  "lng" : 21.2710983
               },
               "southwest" : {
                  "lat" : 52.0978767,
                  "lng" : 20.8512898
               }
            }
         },
         "place_id" : "ChIJAZ-GmmbMHkcR_NPqiCq-8HI",
         "types" : [ "locality", "political" ]
      }
   ],
   "status" : "OK"
}

Generując automatycznie klasy C# w Visual Studio na podstawie JSON uzyskamy:

  public class Rootobject
    {
        public Result[] results { get; set; }
        public string status { get; set; }
    }

    public class Result
    {
        public Address_Components[] address_components { get; set; }
        public string formatted_address { get; set; }
        public Geometry geometry { get; set; }
        public string place_id { get; set; }
        public string[] types { get; set; }
    }

    public class Geometry
    {
        public Bounds bounds { get; set; }
        public Location location { get; set; }
        public string location_type { get; set; }
        public Viewport viewport { get; set; }
    }

    public class Bounds
    {
        public Northeast northeast { get; set; }
        public Southwest southwest { get; set; }
    }

    public class Northeast
    {
        public float lat { get; set; }
        public float lng { get; set; }
    }

    public class Southwest
    {
        public float lat { get; set; }
        public float lng { get; set; }
    }

    public class Location
    {
        public float lat { get; set; }
        public float lng { get; set; }
    }

    public class Viewport
    {
        public Northeast1 northeast { get; set; }
        public Southwest1 southwest { get; set; }
    }

    public class Northeast1
    {
        public float lat { get; set; }
        public float lng { get; set; }
    }

    public class Southwest1
    {
        public float lat { get; set; }
        public float lng { get; set; }
    }

    public class Address_Components
    {
        public string long_name { get; set; }
        public string short_name { get; set; }
        public string[] types { get; set; }
    }

W przypadku usług REST, standardowo używam RestSharp+JSON.NET:

           var query = "https://maps.googleapis.com/maps/api/geocode/json?address=Warsaw,Poland&key=API_KEY";

            var client = new RestClient(query);
            var response = client.Get<dynamic>(new RestRequest());

            Rootobject data = JsonConvert.DeserializeObject<Rootobject>(response.Content);
            
            Console.WriteLine("{0}, {1}", data.results[0].geometry.location.lat, data.results[0].geometry.location.lng);

W praktyce, automatycznie wygenerowane klasy zostałyby zastąpione czytelniejszą strukturą. Tyle jednak wystarczy na szybkie sprawdzenie możliwości API.

Wydajność: HashSet + struktury

Struktury mogą być doskonałym rozwiązaniem w połączeniu ze słownikiem czy HashSet. Zwykle projektuje je się w sposób immutable, więc stanowią naturalny wybór na klucze w HashSet.

Niestety bardzo łatwo popełnić błąd, który poskutkuje gwałtownym spadkiem wydajności.  Załóżmy, że mamy dwie struktury, jedna zawierająca wyłącznie typy proste (value type), a druga również typ referencyjny czyli string:

    public struct WithValueType
    {
        public int ValueType1;
        public int ValueType2;
    }

    public struct WithReferenceType
    {
        public int ValueType;
        public string RefType;
   
    }

Napiszmy teraz prosty benchmark, sprawdzający ile trwa dodanie obiektów do HashSet:

        int times = 1000000;
        
        var withValueType = new HashSet<WithValueType>();
        var withReferenceType = new HashSet<WithReferenceType>();

        // withValueType
        Stopwatch timer = Stopwatch.StartNew();

        for (int i = 0; i < times; i++)
            withValueType.Add(new WithValueType {ValueType1 = i, ValueType2 = i});

        timer.Stop();
        Console.WriteLine($"WithValueType: {timer.ElapsedMilliseconds}");

        // withReferenceType
        timer = Stopwatch.StartNew();

        for (int i = 0; i < times; i++)
            withReferenceType.Add(new WithReferenceType { ValueType = i, RefType = i.ToString() });

        timer.Stop();
        Console.WriteLine($"withReferenceType: {timer.ElapsedMilliseconds}");
      

Na ekranie zobaczymy następujące wyniki:

1

Skąd ta różnica? To już nie jest mikro-optymalizacja, ale bardzo poważny problem wydajnościowy, który ostatnio zmarnował mi sporo czasu na profilowaniu aplikacji. Wiemy, że struktury możemy porównywać poprzez wartość. Jeśli w kodzie mamy warunek np. if(a != b), wtedy konkretne wartości zostaną porównane, a nie jak w przypadku klas, adres instancji. Problem w tym, że wykorzystywany jest mechanizm reflekcji, który zawsze jest wolniejszy. Z dokumentacji MSDN wiemy, że Equals dla struktur działa następująco:

1. Jeśli struktura zawiera przynajmniej jeden typ referencyjny, wtedy jest używany mechanizm reflekcji.
2. Jeśli struktura zawiera wyłącznie typy proste (value types), wtedy dane porównywane są bajt po bajcie.

Zobaczmy implementację ValueType.Equals:

public override bool Equals (Object obj) { 
            BCLDebug.Perf(false, "ValueType::Equals is not fast.  "+this.GetType().FullName+" should override Equals(Object)"); 
            if (null==obj) {
                return false; 
            }
            RuntimeType thisType = (RuntimeType)this.GetType();
            RuntimeType thatType = (RuntimeType)obj.GetType();
 
            if (thatType!=thisType) {
                return false; 
            } 

            Object thisObj = (Object)this; 
            Object thisResult, thatResult;

            // if there are no GC references in this object we can avoid reflection
            // and do a fast memcmp 
            if (CanCompareBits(this))
                return FastEqualsCheck(thisObj, obj); 
 
            FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
 
            for (int i=0; i<thisFields.Length; i++) {
                thisResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(thisObj,false);
                thatResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(obj, false);
 
                if (thisResult == null) {
                    if (thatResult != null) 
                        return false; 
                }
                else 
                if (!thisResult.Equals(thatResult)) {
                    return false;
                }
            } 

            return true; 
        }

Po metodzie CanCompareBits widzimy, że szybkie sprawdzanie jest wyłącznie możliwe, gdy nie ma typów referencyjnych. Wtedy po prostu są sprawdzane wartości bajtów (szybka operacja). Problemy zaczynają się, gdy dane są przechowywane na stercie, szczególnie w różnych miejscach.

W praktyce, gdy mamy typy referencyjne, wtedy obowiązkowo implementujemy Equals. Powyższe rozważania jednak nie usprawiedliwiają faktu, że to struktura z typami prostymi, a nie referencyjnymi, stanowi największy problem. HashSet zawsze wywołuje GetHashCode. W dokumentacji przeczytamy, że:

        /*=================================GetHashCode==================================
        **Action: Our algorithm for returning the hashcode is a little bit complex.  We look
        **        for the first non-static field and get it's hashcode.  If the type has no
        **        non-static fields, we return the hashcode of the type.  We can't take the 
        **        hashcode of a static member because if that member is of the same type as
        **        the original type, we'll end up in an infinite loop. 
        **Returns: The hashcode for the type. 
        **Arguments: None.
        **Exceptions: None. 
        ==============================================================================*/
        [System.Security.SecuritySafeCritical]  // auto-generated
        [ResourceExposure(ResourceScope.None)]
        [MethodImplAttribute(MethodImplOptions.InternalCall)] 
        public extern override int GetHashCode();

Rozwiązaniem jest własna implementacja GetHashCode:

    public struct WithValueType
    {
        public int ValueType1;
        public int ValueType2;

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public bool Equals(WithValueType other)
        {
            return ValueType1 == other.ValueType1 && ValueType2 == other.ValueType2;
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return (ValueType1*397) ^ ValueType2;
            }
        }
    }

    public struct WithReferenceType
    {
        public int ValueType;
        public string RefType;

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public bool Equals(WithReferenceType other)
        {
            return ValueType == other.ValueType && string.Equals(RefType, other.RefType);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return (ValueType*397) ^ (RefType != null ? RefType.GetHashCode() : 0);
            }
        }
    }

Wyniki prezentują się teraz dużo lepiej:
2

Warto również zaimplementować interfejs IEquatable, jeśli chcemy uniknąć boxingu. Więcej informacji w moim poprzednim wpisie.

CONSUMER DRIVEN CONTRACTS w PACT-NET – implementacja testów dostawcy

W poprzednim wpisie przedstawiłem framework PACT.NET. Zdołaliśmy zaimplementować testy po stronie konsumentów. Wynikiem tego, był wygenerowany plik JSON zawierający nagrane testy. Dla przypomnienia, test po stronie konsumenta wyglądał następująco:

[TestFixture]
public class ConsumerTests
{
    private IMockProviderService _mockProviderService;
    private IPactBuilder _pactProvider;
 
    [OneTimeSetUp]
    public void OneTimeSetUp()
    {
        _pactProvider = new PactBuilder()
            .ServiceConsumer("ConsumerName")
            .HasPactWith("ProviderName");
 
        _mockProviderService = _pactProvider.MockService(3423,false);
    }
 
    [OneTimeTearDown]
    public void OneTimeTearDown()
    {
        _pactProvider.Build();
    }
 
    [Test]
    public void Should_Return_FirstName()
    {
        //Arrange
        _mockProviderService.Given("there are some persons data")
            .UponReceiving("a request to retrieve all persons")
            .With(new ProviderServiceRequest
            {
                Method = HttpVerb.Get,
                Path = "/api/persons"     
            })
            .WillRespondWith(new ProviderServiceResponse
            {
                Status = 200,
                Headers = new Dictionary<string, string>
                {
                    { "Content-Type", "application/json; charset=utf-8" }
                },
                Body = new[]
                {
                    new
                    {
                        FirstName = "Piotr",
                    }
                }
            });
 
        var consumer = new PersonsApiClient("http://localhost:3423");
 
        //Act
        var persons = consumer.GetAllPersons();
 
        //Assert
        CollectionAssert.IsNotEmpty(persons);
        CollectionAssert.AllItemsAreNotNull(persons.Select(x=>x.FirstName));
 
        _mockProviderService.VerifyInteractions();
    }
}

Z kolei wygenerowany JSON:

{
  "provider": {
    "name": "ProviderName"
  },
  "consumer": {
    "name": "ConsumerName"
  },
  "interactions": [
    {
      "description": "a request to retrieve all persons",
      "provider_state": "there are some persons data",
      "request": {
        "method": "get",
        "path": "/api/persons"
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json; charset=utf-8"
        },
        "body": [
          {
            "FirstName": "Piotr"
          }
        ]
      }
    }
  ],
  "metadata": {
    "pactSpecificationVersion": "1.1.0"
  }
}

Przejdźmy zatem do testu po stronie dostawcy:

    [TestFixture]
    public class ProducerTests
    {
        [Test]
        public void Ensure_That_Consumer_Requirements_Are_Met()
        {
            var config = new PactVerifierConfig();
            IPactVerifier pactVerifier = new PactVerifier(() => { }, () => { }, config);

            pactVerifier
                .ProviderState(
                    "there are some persons data",
                    setUp: DataSetup,tearDown:DataTearDown);
       
            using (var testServer = TestServer.Create<Startup>())
            {
                pactVerifier
                   .ServiceProvider("Persons API", testServer.HttpClient)
                   .HonoursPactWith("Consumer")
                   .PactUri(@"consumername-providername.json")
                   .Verify();
            }
        }

        private void DataTearDown()
        {
        }


        private void DataSetup()
        {
        }

    }

W celu uruchomienia serwera, używamy Microsoft.Owin.Testing czyli klasy TestServer. W momencie odpalenia testu, zostanie zatem stworzony serwer z naszą aplikacją. Następnie “odgrywany” będzie test wygenerowany przez konsumenta. Operujemy na prawdziwych danych, a nie na mock’ach, stąd metody DataSetup oraz DataTearDown w praktyce zawierają odpowiedni kod, dodający oraz usuwający dane testowe.

Po wykonaniu testu, zostanie wygenerowany plik persons_api_verifier.log:

2016-06-01 19:54:01.894 +01:00 [Debug] Verifying a Pact between ConsumerName and ProviderName
  Given there are some persons data
    a request to retrieve all persons
      with GET /api/persons
        returns a response which
          has status code 200
          includes headers
            'Content-Type' with value application/json; charset=utf-8
          has a matching body (FAILED - 1)

Failures:

1) Expected: [
  {
    "FirstName": "Piotr"
  }
], Actual: [
  {
    "FirstName": "Piotr",
    "LastName": "Zielinski"
  },
  {
    "FirstName": "first name",
    "LastName": "last name"
  }
]

Widzimy, że test zakończył się niepowodzeniem, ponieważ oczekiwana zawartość Content jest inna niż ta zwrócona przez prawdziwy serwis.

Za pomocą PathVerifier definiujemy, które testy chcemy wykonać. W naszym przypadku jest tylko jeden. W praktyce definiuje się wiele stanów i kilka plików JSON.

Consumer driven contracts – pact-net

O Consumer-Driven-Contracts pisałem już tutaj. Sam koncept jest dosyć prosty, ale bez odpowiednich narzędzi może być mozolny w wdrożeniu.
Pack-net, jak nie trudno się domyślić jest implementacją biblioteki pact w C#. Kod źródłowy możną znaleźć na GitHub.

Wiemy, aby zaimplementować Consumer-Driven-Contracts (CDC) musimy napisać testy zarówno po stronie konsumenta jak i dostawcy. Zwykle, konsumenci nie testują prawdziwej usługi, a jedynie operują na mock’ach. Następnie dostawca, wykona wszystkie testy konsumenckie na prawdziwej usłudzę. Sztuczka polega na tym, aby w jakiś sposób przekazać do dostawcy testy zdefiniowane przez konsumenta. Nie chcemy generować biblioteki DLL i jej przekazywać. Pact wygeneruje za nas plik JSON, ze wszystkimi testami. Podsumowując za pomocą Pact:

1. Konsumenci definiują klasyczne testy jednostkowe. Wykonywane są one w izolacji na mock’u usługi.
2. Testy jednostkowe są “nagrywane” i  zapisywane w pliku JSON.
3. Plik (lub pliki w przypadku wielu konsumentów) JSON  są odczytywane przez producenta. Nagrane testy są odtwarzane, ale będą już wykonywane na prawdziwej usłudze.

Jeśli tworzymy usługę, wtedy krok trzeci gwarantuje nam, że spełniamy wszystkie wymogi konsumentów. Jeśli konsument A potrzebuje np. pole FirstName w odpowiedzi, wtedy zdefiniuje to za pomocą testu jednostkowego, a dostawca następnie zweryfikuje to.
Pact dostarcza łatwy interfejs i nie musimy się martwić np. odpaleniem serwer’a web w testach dostawcy, które zawsze wykonywane są na prawdziwej usłudze. Podobnie, dzięki Pact łatwo tworzyć mock’a usługi. Po prostu wystarczy zdefiniować oczekiwane nagłówki czy zawartość ciała.

Myślę, że najłatwiej będzie to zrozumieć na przykładzie. Załóżmy, że tworzymy usługę zwracającą listę osób. Nasza solucja zatem będzie składać się z:
1. Provider.Web – usługa
2. Provider.Web.Tests – testy dostawcy (wykonywane na prawdziwej usłudze)
3. Consumer – prosty klient łączący się z usługą
4. Consumer.Tests – testy konsumenckie (wykonywane na mock’u)

W dzisiejszym wpisie zajmiemy się punktami 1,3 oraz 4, a następnym razem zajmiemy się testami dostawcy.

Zacznijmy zatem od dostawcy (punkt 1):

    public class PersonsController : ApiController
    {
        public PersonInfo[] GetAll()
        {
            var person1 = new PersonInfo {FirstName = "Piotr", LastName = "Zielinski"};
            var person2 = new PersonInfo { FirstName = "first name", LastName = "last name" };

            return new PersonInfo[] {person1, person2};
        }
    }

    public class PersonInfo
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

Zwykłe WebApi, którego odpowiedź wygląda następująco:

<ArrayOfPersonInfo xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Provider.Web.Controllers">
<PersonInfo>
<FirstName>Piotr</FirstName>
<LastName>Zielinski</LastName>
</PersonInfo>
<PersonInfo>
<FirstName>first name</FirstName>
<LastName>last name</LastName>
</PersonInfo>
</ArrayOfPersonInfo>

Następnie zdefiniujemy klienta (konsumenta – punkt 3):

   public class PersonsApiClient
    {
        public string BaseUri { get; set; }

        public PersonsApiClient(string baseUri)
        {
            BaseUri = baseUri;
        }

        public PersonInfo[] GetAllPersons()
        {
            var client = new RestClient(BaseUri);
            var persons = client.Get<List<PersonInfo>>(new RestRequest("api/persons")).Data;

            return persons.ToArray();
        }
    }

Ostatni etap w dzisiejszym wpisie to zdefiniowanie testu konsumenta (punkt 4):

    [TestFixture]
    public class ConsumerTests
    {
        private IMockProviderService _mockProviderService;
        private IPactBuilder _pactProvider;

        [OneTimeSetUp]
        public void OneTimeSetUp()
        {
            _pactProvider = new PactBuilder()
                .ServiceConsumer("ConsumerName")
                .HasPactWith("ProviderName");

            _mockProviderService = _pactProvider.MockService(3423,false);
        }

        [OneTimeTearDown]
        public void OneTimeTearDown()
        {
            _pactProvider.Build();
        }

        [Test]
        public void Should_Return_FirstName()
        {
            //Arrange
            _mockProviderService.Given("there are some persons data")
                .UponReceiving("a request to retrieve all persons")
                .With(new ProviderServiceRequest
                {
                    Method = HttpVerb.Get,
                    Path = "/api/persons"      
                })
                .WillRespondWith(new ProviderServiceResponse
                {
                    Status = 200,
                    Headers = new Dictionary<string, string>
                    {
                        { "Content-Type", "application/json; charset=utf-8" }
                    },
                    Body = new[]
                    {
                        new
                        {
                            FirstName = "Piotr",
                        }
                    }
                });

            var consumer = new PersonsApiClient("http://localhost:3423");

            //Act
            var persons = consumer.GetAllPersons();

            //Assert
            CollectionAssert.IsNotEmpty(persons);
            CollectionAssert.AllItemsAreNotNull(persons.Select(x=>x.FirstName));

            _mockProviderService.VerifyInteractions();
        }
    }

Za pomocą klasy PactBuilder budujemy mock usługi czyli IMockProviderService. Następnie definiujemy wejścia i wyjścia mocka. Widzimy, że jeśli zapytanie GET przyjdzie na /api/persons wtedy zwracamy jeden obiekt, zawierający imię. Za pomocą MockProvider możemy zasymulować dowolne zachowanie usługi. W naszym przypadku jest to po prostu GET i odpowiedź. W bardziej zaawansowanych przypadkach dochodzą do tego parametry query, nagłówki, metody POST, DELETE itp.

Mock zwróci zatem tylko jeden obiekt, zawierający imię Za pomocą następnych asercji, zweryfikujemy, że faktycznie imię musi zawierać jakąś treść. Jeśli pole FirstName na tym etapie jest puste, oznacza to, że implementacja klienta jest błędną – np. deserialziacja nie działa tak jak należy. Widzimy, że port 3423 został dobrany losowo, ponieważ nie ma to znaczenia dla mock’a.
VerifyInteractions sprawdzi również tzw. interakcje. W powyższym przypadku oznacza to, że dokładnie jedno zapytanie GET “api/persons” zostało wysłane do usługi. Jeśli nasz klient zawierałby błąd, np. dwukrotne wysłanie zapytania, wtedy test również zakończyłby się błędem.

Po wykonaniu testu, zostanie on nagrany i zapisany w pliku “consumername-providername.json”:

{
  "provider": {
    "name": "ProviderName"
  },
  "consumer": {
    "name": "ConsumerName"
  },
  "interactions": [
    {
      "description": "a request to retrieve all persons",
      "provider_state": "there are some persons data",
      "request": {
        "method": "get",
        "path": "/api/persons"
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json; charset=utf-8"
        },
        "body": [
          {
            "FirstName": "Piotr"
          }
        ]
      }
    }
  ],
  "metadata": {
    "pactSpecificationVersion": "1.1.0"
  }
}

W kolejnym wpisie zajmiemy się implementacją testów dostawcy, które będą wykorzystywać właśnie wygenerowany plik JSON, w celu wykonania takiego samego testu na prawdziwej usłudze.