Code Review: Kilka pułapek ze SpecFlow

Posted December 18th, 2014 by Piotr Zieliński
Categories: C#, Patterns & Practices, Testy

Specflow jest według mnie znakomitym framework’iem do pisania testów BDD. Bardzo często dodawanie nowych scenariuszy sprowadza się do przekopiowania przypadków użycia z user story. Oczywiście wymaga to trochę praktyki, ponieważ definiowanie za pomocą GWT na początku może wydawać się nienaturalne.

Na blogu o podstawach SpecFlow pisałem już wiele razy. Dzisiaj chciałbym pokazać pewną pułapkę, na którą w przypadku wielu scenariuszy trzeba uważać. Wiele osób narzeka, że praca ze SpecFlow jest trudna dla skomplikowanych projektów. Moim zdaniem również łatwo popełnić błędy i po pewnym czasie zestaw testów jest trudny w utrzymaniu.

Załóżmy, że tworzymy następujący plik ze scenariuszem (proszę nie zwracać uwagi na sens i opis testu Uśmiech):

Feature: NewPost Jakis opis tutaj... Scenario: Create a new post Given I have logged into CMS When I press the create a post button Then article should be created.

Wygenerowane kroki testu, będą wyglądać tak:

[Binding] public class NewPostSteps { [Given(@"I have logged into CMS")] public void GivenIHaveLoggedIntoCMS() { ScenarioContext.Current.Pending(); } [When(@"I press the create a post button")] public void WhenIPressTheCreateAPostButton() { ScenarioContext.Current.Pending(); } [Then(@"article should be created\.")] public void ThenArticleShouldBeCreated_() { ScenarioContext.Current.Pending(); } }

Wszystko fajnie, mamy trzy wygenerowane metody dla GWT. Następnie, być może po kilku miesiącach, dodajemy analogiczny scenariusz, ale dla edycji postów:

Feature: EditPost Jakis opis tutaj... Scenario: Edit a post Given I have logged into CMS When I press the edit a post button Then article should be updated.

Po wygenerowaniu kroków dostaniemy:

[Binding] public class EditPostSteps { [When(@"I press the edit a post button")] public void WhenIPressTheEditAPostButton() { ScenarioContext.Current.Pending(); } [Then(@"article should be updated\.")] public void ThenArticleShouldBeUpdated_() { ScenarioContext.Current.Pending(); } }

Na początku może wydawać się to dziwne. Nie został wygenerowany krok Given. W Specflow kroki są globalne i w naszym przypadku given będzie współdzielony. Zwróćmy uwagę, że “Given I have logged into CMS” jest takie same dla obydwu testów. Specflow automatycznie wykryje to i nie będzie generował duplikatu w EditPostSteps. Ma to sens, jeśli faktycznie krok jest taki sam. Często kroki o takiej samej nazwie, mają kompletnie inny kod, w zależności od kontekstu. Z tego względu należy być uważnym i mieć świadomość tego.

W naszym przypadku, ma sens współdzielenie given, bo inicjalizacja będzie taka sama. Niestety,  aktualny kod jest brzydki, ponieważ Given dla EditPostSteps, znajduje się w NewPostSteps. Z tego względu lepiej przenieść to do osobnej klasy, np. CmsSteps:

[Binding] public class CmsSteps { [Given(@"I have logged into CMS")] public void GivenIHaveLoggedIntoCMS() { } }

Rozwiązanie jest od razu czytelniejsze. CmsSteps może zawierać kroki współdzielone przez inne scenariusze. Wcześniejsza sytuacja, gdzie jeden plik scenariusza zawierał kroki dla innych był zdecydowanie nieelegancki.

Pozostaje jednak kolejna zagadka, jak współdzielić stan między różnymi klasami. W końcu Given coś inicjalizuje, co potem powinno być dostępne w EditPostSteps oraz NewPostSteps.

Najprostszym rozwiązaniem jest użycie ScenarioContext:

[Binding] public class CmsSteps { [Given(@"I have logged into CMS")] public void GivenIHaveLoggedIntoCMS() { ScenarioContext.Current["text"] = "logged in"; } } [Binding] public class EditPostSteps { [When(@"I press the edit a post button")] public void WhenIPressTheEditAPostButton() { string text = ScenarioContext.Current["text"].ToString(); } [Then(@"article should be updated\.")] public void ThenArticleShouldBeUpdated_() { ScenarioContext.Current.Pending(); } }

Niestety powstałe w taki sposób testy będą wcześniej czy później trudne w utrzymaniu. ScenarioContext to globalny kontener i na dodatek, dane przekazuje się za pomocą słownika (“text”). Zmiana w jednym miejscu, spowoduje problem w innym. Z tego względu zawsze lepiej tworzyć wrappery. Może mieć to formę następującej klasy:

public class CmsLoginPage { public string Login { get { return ScenarioContext.Current["Login"].ToString(); } set { ScenarioContext.Current["Login"] = value; } } public string Password { get { return ScenarioContext.Current["Password"].ToString(); } set { ScenarioContext.Current["Password"] = value; } } }

Odradzam bezpośrednie modyfikowanie kontekstu w krokach testu. Zawsze lepiej tworzyć właściwość, która opakowuje operacje na słowniku.

Mój preferowany sposób to dependency injection. Najpierw tworzę klasę z informacjami, które chce przekazać z jednego kroku (klasy) do drugiej:

public class CmsLoginPage { public string Login { get; set; } public string Password { get; set; } }

Następnie za pomocą konstruktora przekazuje referencję:

[Binding] public class CmsSteps { private readonly CmsLoginPage _loginPage; public CmsSteps(CmsLoginPage loginPage) { _loginPage = loginPage; } [Given(@"I have logged into CMS")] public void GivenIHaveLoggedIntoCMS() { _loginPage.Login = "piotr"; _loginPage.Password = "password"; } }

SpecFlow już o to zadba, aby została ona przekazana do jakichkolwiek innych klas:

[Binding] public class EditPostSteps { private readonly CmsLoginPage _loginPage; public EditPostSteps(CmsLoginPage loginPage) { _loginPage = loginPage; } [When(@"I press the edit a post button")] public void WhenIPressTheEditAPostButton() { string login = _loginPage.Login; string passwsord = _loginPage.Password; } [Then(@"article should be updated\.")] public void ThenArticleShouldBeUpdated_() { ScenarioContext.Current.Pending(); } }

W kolejnych postach mam zamiar również poruszać temat SpecFlow. Oczywiście jeśli wymagania nie są przygotowywane przez osoby z biznesu (np. przez PO), to nie docenimy prawdziwej przydatności framework’u. W takich przypadkach warto również rozważyć narzędzia typu SpecFor, które często są preferowane, gdy to programiści (bez udziału osób nietechnicznych) piszą scenariusze.

One Hacker Way – Erik Meijer

Posted December 16th, 2014 by Piotr Zieliński
Categories: Ogólne

http://vimeo.com/110554082

Twitter’a nie mam, to czasami blog służy mi za wrzucanie linków bez komentarza. Ciekawa prezentacja, aczkolwiek odnośnie TDD nie zgadzam się…

Visual Studio 2015 – debugowanie wyrażeń Lambda

Posted December 15th, 2014 by Piotr Zieliński
Categories: Visual Studio

Kolejne, drobne ulepszenie w VS 2015, to możliwość debugowania wyrażeń lambda.  Załóżmy, że mamy następujący kod:

int[] numbers = new int[] { 3, 5, 1, 10, 15, 2, 5, 10 }; var results = numbers.Where(x => x % 2 == 0);

Jeśli odpalimy powyższy kod w VS 2013 (lub w starszej wersji) niemożliwe byłoby debugowanie lambda. W okienku watch następująca wiadomość zostałby wyświetlona:

image

Analogicznie sprawa wygląda z Immediate Window:

image

Z kolei VS 2015, bez problemu ujrzyjmy końcowy wynik:

image

image

Bardzo drobna zmiana, ale wiele razy ta niedogodność utrudniała debugowanie. Mam na myśli sytuacje, kiedy warunek jest bardziej skomplikowany niż proste modulo 2. Wciąż są pewnie ograniczenia m.in. brak Mark Object ID czy debugowanie wywołań do kodu natywnego.

Microsoft Immutable Collections: Modyfikacja kolekcji

Posted December 12th, 2014 by Piotr Zieliński
Categories: C#

Tak samo, jak w .NET istnieje klasa StringBuilder za pomocą, której można modyfikować “string” (nie dosłownie), tak w MS immutable collections zaimplementowano analogiczny builder.

Przykład:

var immutableList = ImmutableList<int>.Empty; immutableList = immutableList.Add(1); Debug.Assert(immutableList.Count==1); ImmutableList<int>.Builder builder = immutableList.ToBuilder(); Debug.Assert(builder.Count==1);

Za pomocą ToBuilder() na liście tworzymy builder, który jest kopią danej kolekcji. Następnie jakiekolwiek operacje wykonywane na obiekcie builder, nie będą modyfikowały bazowej listy (tutaj zmienna o nazwie “immutableList”):

var immutableList = ImmutableList<int>.Empty; immutableList = immutableList.Add(1); Debug.Assert(immutableList.Count==1); ImmutableList<int>.Builder builder = immutableList.ToBuilder(); Debug.Assert(builder.Count==1); builder.Add(2); builder.Add(3); Debug.Assert(builder.Count == 3); var newImmutable = builder.ToImmutable(); Debug.Assert(newImmutable.Count == 3); Debug.Assert(immutableList.Count == 1);

Dodałem asercje dla pokazania co jest modyfikowane. Widać wyraźnie, że ToBuilder() tworzy nową wewnętrzna listę, tak jak i ToImmutable. Oczywiście wewnętrzna implementacja, z tego co mi wiadomo, jest dosyć sprytna i nie zawsze realokowane są obiekty. Utrzymywana jest lista albo drzewo i tyko w koniecznych przypadkach obiekty są kopiowane. Autorzy starali się ograniczyć te operacje, aby wydajność nie ucierpiała znacząco w stosunku do klasycznych kolekcji

Microsoft Immutable Collections: Wydajność

Posted December 9th, 2014 by Piotr Zieliński
Categories: C#, Patterns & Practices

W poprzednim wpisie obiecywałem, porównać wydajność kolekcji immutable z klasycznymi typami z System.Collections.Generic. W dzisiejszym benchmarku, porównamy zarówno zużycie pamięci, jak i czas potrzebny na dodanie węzłów.

Zacznijmy od klasycznych (zmiennych) typów:

var list = new List<int>(); const int n = 100000; long beforeAvailableMemory = GC.GetTotalMemory(true); var stopwatch = Stopwatch.StartNew(); for (int i = 0; i < n; i++) { list.Add(i); } Console.WriteLine("Mutable, czas: {0}", stopwatch.ElapsedTicks); long afterAvailableMemory = GC.GetTotalMemory(false); Console.WriteLine("Mutable, pamiec: {0}", afterAvailableMemory - beforeAvailableMemory);

Wynik:

image

Następnie Microsoft immutable:

var immutableList = ImmutableList<int>.Empty; const int n = 100000; long beforeAvailableMemory = GC.GetTotalMemory(true); var stopwatch = Stopwatch.StartNew(); for (int i = 0; i < n; i++) { immutableList = immutableList.Add(i); } Console.WriteLine("Immutable, czas: {0}", stopwatch.ElapsedTicks); long afterAvailableMemory = GC.GetTotalMemory(false); Console.WriteLine("Immutable, pamiec: {0}", afterAvailableMemory - beforeAvailableMemory);

Wynik:

image

Różnica w pamięci jest tak naprawdę mniejsza niż można byłoby spodziewać się. Niestety czas wykonania jest wielokrotnie dłuższy. Oczywiście podany przykład zastosowania niezmiennych kolekcji, jest skrajnym anty-wzorcem, który pokazuje jak bardzo wydajność może spaść. W przyszłym wpisie, pokażę buildery, które umożliwiają tymczasową modyfikacje niezmiennych kolekcji – analogicznie do StringBuilder dla String.

Microsoft Immutable Collections

Posted December 6th, 2014 by Piotr Zieliński
Categories: C#

Kolekcje z System.Collections.Generic są wszystkim dobrze znane. Czasami jednak zachodzi potrzeba skorzystania z typów immutable.  Ogólnie o tych obiektach pisałem tutaj.  Szczególnie w środowisku wielowątkowym są one przydatne. Jak można przeczytać we wspomnianym poście, obiekty takie nigdy nie mogą zostać zmienione a modyfikowanie stanu polega na tworzeniu nowej instancji.

Ktoś mógłby zasugerować, że mamy w końcu ReadOnlyCollection. Niestety, interfejs uniemożliwia modyfikacje tej kolekcji wyłącznie jego użytkownikowi. Załóżmy, że mamy metodę:

private void DoSomething (IReadOnlyCollection<int> list) { }

Co prawda w DoSomething nie możemy zmodyfikować danych i interfejs jasno o tym mówi, ale nic nie stoi na przeszkodzie, aby ktoś z zewnątrz dokonywał zmian na kolekcji. Jeśli byłoby to kolekcja immutable, mielibyśmy pewność, że nikt jej w międzyczasie (np. z osobnego wątku) nie zmodyfikuje.

Aby zacząć zabawę, musimy najpierw zainstalować zewnętrzny pakiet:

image

 

Do dyspozycji będziemy mieli wiele typów:

- ImmutableStack<T>

- ImmutableQueue<T>

- ImmutableList<T>

- ImmutableHashSet<T>

- ImmutableSortedSet<T>

- ImmutableDictionary<K, V>

- ImmutableSortedDictionary<K, V>

Wszystkie one znajdują się w System.Collections.Immutable;. Sprawdźmy, jak działa najpopularniejsza z nich, ImmutableList.

Jeśli spróbujemy obiekt zainicjalizować w standardowy sposób, zakończy to się błędem:

ImmutableList<int> immutableList = new ImmutableList<int ();

Po prostu, konstruktor jest prywatny. Dlaczego? Obiekty niezmienne inicjalizuje się w następujący sposób:

ImmutableList<int> immutableList = ImmutableList<int>.Empty;

Może wydawać to się dziwne, ale jaki jest sens inicjalizowania pustego obiektu, jeśli żadna operacja na nim nie zostanie wykonana? Wszystkie operacje typu Add, Remove tworzą zawsze nową kopię. Zatem ten pierwszy, bazowy może być wspólny bo nie ma to znaczenia, a zaoszczędzi pamięć.

Napiszmy kawałek kodu, aby przekonać się, że zawsze nowa instancja jest zwracana:

ImmutableList<int> immutableList = ImmutableList<int>.Empty; ImmutableList<int> list1= immutableList.Add(1); ImmutableList<int> list2 = list1.Add(12); Debug.Assert(immutableList.Count==0); Debug.Assert(list1.Count == 1); Debug.Assert(list2.Count == 2);

Z tego wynika, że jeśli chcemy dodać serie wartości, wtedy osobne wywołania Add nie są najlepszym wyborem, ponieważ spowodują relokacje zasobów. Innymi słowy, poniższy kod jest bardzo brzydki:

ImmutableList<int> immutableList = ImmutableList<int>.Empty; immutableList=immutableList.Add(1); immutableList=immutableList.Add(2); immutableList=immutableList.Add(3);

Z tego względu, dużo lepiej jest:

ImmutableList<int> immutableList = ImmutableList<int>.Empty; immutableList.AddRange(new []{1,2,3,4});

Alternatywą również jest użycie Fluent API:

ImmutableList<int> immutableList = ImmutableList<int>.Empty; immutableList.Add(3).Add(1).Add(5);

O tym jednak, więcej w następnym wpisie. Planuje również zrobić test wydajności między różnymi kolekcjami, aby pokazać kiedy nie należy stosować przedstawionych kolekcji.

Visual Studio 2015: Analiza jakości kodu

Posted December 1st, 2014 by Piotr Zieliński
Categories: Ogólne, Visual Studio

VS 2015 usprawnił, moim zdaniem znaczącą sposób analizy kodu. Sam sposób podpowiedzi również został poprawiony i coraz bliżej im do Resharper, a w pewnych sprawach, jak zobaczymy, daje potencjalnie większe możliwości.

Zacznijmy od przykładu pokazanego w prezentacji VS 2015. Załóżmy, że chcemy zaimplementować interfejs ISerializable:

class Sample :ISerializable
{
}

Jeśli tylko namespace zawierający ISerializable nie został dołączony, naciskamy ctrl+. i wyświetli się następujące menu:

image

Nowością jest tutaj podgląd tego, co zostanie zmienione (w tym przypadku dołączenie przestrzeni za pomocą using).

Kolejnym etapem będzie implementacja metod wymaganych przez interfejs. Podgląd będzie wtedy wyglądać następująco:

image

W tej chwili mamy następujący kod:

class Sample : ISerializable
{
   public void GetObjectData(SerializationInfo info, StreamingContext context)
   {
       throw new NotImplementedException();
   }
}

Wspomniałem na początku o nowym mechanizmie analizy kodu. W VS 2015 będzie to rozszerzalne i każdy taki analizator można zainstalować w formie pakietu NuGet. Daje to ogromne możliwości. Zainstalujmy więc jeden z nich:

image

Zaglądając do References->Analysers zobaczymy listę reguł:

image

Wracając do naszego przykładu. Po instalacji powyższego pakietu, zostaniemy poinformowani, że powinniśmy dodać atrybut [Serializable] do stworzonej klasy.

image

Widzimy również numer reguły (CA2237).

Dostępny jest już teraz pakiet reguł dla aplikacji Azure. Opisywany mechanizm, potencjalnie może przydać się do pisania reguł, specyficznych dla danego projektu.

Z drugiej strony, długa jeszcze droga zanim zastąpi to nDepend i Resharper, ale moim zdaniem idzie to w tym kierunku…

Code Review: Enum.ToString()–wydajność

Posted November 28th, 2014 by Piotr Zieliński
Categories: C#, Patterns & Practices

Dzisiaj zaciekawiła mnie informacje, znaleziona w sieci, że ToString() na enum jest bardzo powolne. Postanowiłem to sprawdzić samemu. Załóżmy,  że mamy:

public enum Month
{
   January,
   February,
   March,
   April,
   May,
   June,
   July,
   August,
   September,
   October,
   November,
   December
};

Prosty benchmark, może wyglądać następująco:

int n = 100000;

Stopwatch stopwatch = Stopwatch.StartNew();
string text = null;

for (int i = 0; i < n; i++)
{
 var month = (Month)(i % 12);
 text = month.ToString();
}

Console.WriteLine(stopwatch.ElapsedTicks);

Wynik:

image

Taki wynik za wiele nam nie mówi. Spróbujmy napisać drugi przykład, który zamiast enum.ToString(), będzie korzystał z tablicy napisów tzn.:

string[] names = new[]
       {
           "January",
           "February",
           "March",
           "April",
           "May",
           "June",
           "July",
           "August",
           "September",
           "October",
           "November",
           "December"
       };

Wartość enum’a może zostać przeskalowana na indeks w tej tablicy:

stopwatch = Stopwatch.StartNew();
text = null;

for (int i = 0; i < n; i++)
{
 var month = (Month) (i%12);
 text = names[(int)month];                    
}

Console.WriteLine(stopwatch.ElapsedTicks);

Uruchamiając oba benchmarki, zobaczymy:

image

Różnica podobno jest na tyle duża, że nie można tego traktować jako mikro-optymalizacji. Jeśli mamy kod w jakieś pętli (serwer), to stanowi to zwykłe marnowanie zasobów.

No dobra, to odpowiedzmy sobie na pytanie, skąd taka duża różnica? Zaglądamy oczywiście do IL:

// loop start (head: IL_0025)
IL_0010: ldloc.2
IL_0011: ldc.i4.s 12
IL_0013: rem
IL_0014: stloc.3
IL_0015: ldloc.3
IL_0016: box ConsoleApplication2.Month
IL_001b: callvirt instance string [mscorlib]System.Object::ToString()
IL_0020: pop
IL_0021: ldloc.2
IL_0022: ldc.i4.1
IL_0023: add
IL_0024: stloc.2

IL_0025: ldloc.2
IL_0026: ldloc.0
IL_0027: blt.s IL_0010
// end loop

Widzimy, że ToString jest metodą wirtualną (należącą do Object) a enum jest value type. Oznacza to, że będziemy mieli boxing, co widać po IL. Dla porównania drugie rozwiązanie nie zawiera żadnego boxingu:

// loop start (head: IL_00ce)
IL_00bb: ldloc.s i
IL_00bd: ldc.i4.s 12
IL_00bf: rem
IL_00c0: stloc.s month
IL_00c2: ldloc.s names
IL_00c4: ldloc.s month
IL_00c6: ldelem.ref
IL_00c7: pop
IL_00c8: ldloc.s i
IL_00ca: ldc.i4.1
IL_00cb: add
IL_00cc: stloc.s i

IL_00ce: ldloc.s i
IL_00d0: ldloc.0
IL_00d1: blt.s IL_00bb
// end loop

Niestety to nie wszystko. Zagłębmy się dalej do Enum.ToString():

//Enum.ToString()
public override string ToString()
{
    return Enum.InternalFormat((RuntimeType)base.GetType(), this.GetValue());
}

Następnie do Enum.InternalFormat:

// System.Enum
private static string InternalFormat(RuntimeType eT, object value)
{
    if (eT.IsDefined(typeof(FlagsAttribute), false))
    {
        return Enum.InternalFlagsFormat(eT, value);
    }
    string name = Enum.GetName(eT, value);
    if (name == null)
    {
        return value.ToString();
    }
    return name;
}

W zależności czy Enum jest opatrzony atrybutem Flags, mamy dwie różne implementacje. O flagach już kiedyś pisałem (zapraszam do lektury).

W naszym przypadku, przechodzimy do Enum.GetName():

public static string GetName(Type enumType, object value)
{
    if (enumType == null)
    {
        throw new ArgumentNullException("enumType");
    }
    return enumType.GetEnumName(value);
}

Nic tu ciekawego nie ma więc zaglądamy teraz do GetEnumName:

// System.Type
/// <summary>Returns the name of the constant that has the specified value, for the current enumeration type.</summary>
/// <returns>The name of the member of the current enumeration type that has the specified value, or null if no such constant is found.</returns>
/// <param name="value">The value whose name is to be retrieved.</param>
/// <exception cref="T:System.ArgumentException">The current type is not an enumeration.-or-<paramref name="value" /> is neither of the current type nor does it have the same underlying type as the current type.</exception>
/// <exception cref="T:System.ArgumentNullException">
///   <paramref name="value" /> is null.</exception>
public virtual string GetEnumName(object value)
{
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }
    if (!this.IsEnum)
    {
        throw new ArgumentException(Environment.GetResourceString("Arg_MustBeEnum"), "enumType");
    }
    Type type = value.GetType();
    if (!type.IsEnum && !Type.IsIntegerType(type))
    {
        throw new ArgumentException(Environment.GetResourceString("Arg_MustBeEnumBaseTypeOrEnum"), "value");
    }
    Array enumRawConstantValues = this.GetEnumRawConstantValues();
    int num = Type.BinarySearch(enumRawConstantValues, value);
    if (num >= 0)
    {
        string[] enumNames = this.GetEnumNames();
        return enumNames[num];
    }
    return null;
}

Widzimy znów masę if’ow. Chcemy się jednak bliżej przyjrzeć BinarySearch oraz GetEnumNames. Pierwsza z nich daje nam indeks, a druga nazwy enumów.  GetEnumNames wykonuje kilka ifów i wywoła GetEnumData:

// System.Type
private void GetEnumData(out string[] enumNames, out Array enumValues)
{
    FieldInfo[] fields = this.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
    object[] array = new object[fields.Length];
    string[] array2 = new string[fields.Length];
    for (int i = 0; i < fields.Length; i++)
    {
        array2[i] = fields[i].Name;
        array[i] = fields[i].GetRawConstantValue();
    }
    IComparer @default = Comparer.Default;
    for (int j = 1; j < array.Length; j++)
    {
        int num = j;
        string text = array2[j];
        object obj = array[j];
        bool flag = false;
        while (@default.Compare(array[num - 1], obj) > 0)
        {
            array2[num] = array2[num - 1];
            array[num] = array[num - 1];
            num--;
            flag = true;
            if (num == 0)
            {
                break;
            }
        }
        if (flag)
        {
            array2[num] = text;
            array[num] = obj;
        }
    }
    enumNames = array2;
    enumValues = array;
}

Pierwsza rzecz jaką tu widzimy to refleksja, o której wiemy, że nie jest szybka i spowalnia kod. Widzimy, metoda jest bardzo uniwersalna, ale to skutkuje, że jest wolna dla najprostszych przykładów (brak flag i przerw między enumami). Ponadto, drugi parametr wyjściowy, kompletnie jest niepotrzebny dla naszego przypadku (w zasadzie jest ignorowany przez GetEnumNames):

public virtual string[] GetEnumNames()
{
    if (!this.IsEnum)
    {
        throw new ArgumentException(Environment.GetResourceString("Arg_MustBeEnum"), "enumType");
    }
    string[] result;
    Array array;
    this.GetEnumData(out result, out array);
    return result;
}

BinarySearch znajduje daną wartość korzystając oczywiście z przeszukiwania binarnego:

ulong[] array2 = new ulong[array.Length];
for (int i = 0; i < array.Length; i++)
{
array2[i] = Enum.ToUInt64(array.GetValue(i));
}
ulong value2 = Enum.ToUInt64(value);
return Array.BinarySearch<ulong>(array2, value2);

Widzimy zatem, że kod wykonuje bardzo dużo rzeczy (refleksja, warunki, binarne przeszukiwanie, znajdowanie nazwy, boxing). W drugim rozwiązaniu, nie jest potrzebna żadna pętla aby znaleźć konkretną wartość (w końcu już ją mamy).

Warto mieć  uwadze  powyższe uwagi, wrzucając Enum.ToString gdzieś na serwer. Często przecież ma to charakter niejawny tzn.:

Console.WriteLine(month);
// powyzsza linia jest rownowazna z:
Console.WriteLine(month.ToString());

Visual Studio 2015: Smart Unit Tests

Posted November 25th, 2014 by Piotr Zieliński
Categories: C#, Testy, Visual Studio

Jeśli jeszcze nie ściągneliście VS 2015, to zapraszam:

http://www.visualstudio.com/en-us/downloads/visual-studio-2015-downloads-vs.aspx

Smart Unit Tests to sposób automatycznego wygenerowania scenariuszy dla testów jednostkowych. Wiele o tym już na blogu jak i w artykułach na MSDN pisałem, a konkretnie o Pex & moles. Chciałbym dzisiaj jednak zaprezentować wbudowaną funkcję w VS 2015. Nie wymaga ona instalacji zewnętrznych narzędzi.

Na początku od razu poważna wada.. Póki co, wspierany jest wyłącznie MSTest. Mam nadzieję, że zmieni się to w oficjalnym wydaniu bo inaczej większość osób nie będzie mogła z tego korzystać.

Załóżmy, że zaimplementowaliśmy już jakąś metodę i chcemy wygenerować test:

public double AnyMethod(int a,int b,double c,string text)
{
  if (a == 3)
      return 1;
  else if (b == a && a == 1)
      return 2;
  else if (b == (int)c && a == 6 && text == "Hello")
      return 3;
  else if (Int32.Parse(text) == a)
      return 4;
  else if (c == a + b)
      return 5;

  return 6;
}

Smart Unit tests postara się wygenerować tak dane wejściowe, aby każda gałąź została pokryta. Zaznaczamy zatem metodę i z menu kontekstowego wybieramy Smart Unit Test:

image

Zostanie wyświetlone następujące okno ze scenariuszami:

image

Widzimy po kolumnie result, że wszystkie scenariusze zostały wygenerowane – wartości od 1 do 6 (wszystkie możliwe ścieżki).

Ponadto, część scenariuszy generuje wyjątki. Wiąże się to z tym, że nie można przekazać tekstu do parametru text i potem wywoływać Int32.Parse. Klikając na każdym z nich, po prawej stronie wyświetli się wygenerowany test:

image

Możemy dowolny scenariusz zapisać do pliku np.:

public partial class SampleTest
{
   [TestMethod]
   [PexGeneratedBy(typeof(SampleTest))]
   public void AnyMethod898()
   {
       double d;
       Sample s0 = new Sample();
       d = this.AnyMethod(s0, 6, 0, 0, "Hello");
       Assert.AreEqual<double>(3, d);
       Assert.IsNotNull((object)s0);
   }
}

Oczywiście możemy wszystkie pozycje zaznaczyć i zapisać.

Powyższe okno również zawiera uwagi i wskazówki:

image

Warto zawsze przejrzeć listę bo czasami możemy dowiedzieć się czegoś, czego nie spodziewaliśmy się po naszym kodzie.

Zmodyfikujmy teraz trochę AnyMethod, aby sprawdzał, że text jest zawsze liczbą:

public double AnyMethod(int a, int b, double c, string text)
{
  if (text == null || !text.All(x => char.IsDigit(x)))
      throw new ArgumentException();

  if (a == 3)
      return 1;
  else if (b == a && a == 1)
      return 2;
  else if (b == (int)c && a == 6 && text == "Hello")
      return 3;
  else if (Int32.Parse(text) == a)
      return 4;
  else if (c == a + b)
      return 5;

  return 6;
}

Po ponownym wygenerowaniu scenariuszy zobaczymy:

image

Smart Unti Test rozpoznał sam, że wyrzucenie ArgumentException jest spodziewanym wynikiem, gdy przekazujemy tekst zamiast liczby. Niestety nie uwzględniliśmy przypadku, gdy Text jest pusty, stąd 4 testy zakończone błędem. Zmodyfikujmy kod i odpalmy analizę ponownie:

public double AnyMethod(int a, int b, double c, string text)
{
  if (string.IsNullOrEmpty(text) || !text.All(x => char.IsDigit(x)))
      throw new ArgumentException();

  if (a == 3)
      return 1;
  else if (b == a && a == 1)
      return 2;
  else if (b == (int)c && a == 6 && text == "Hello")
      return 3;
  else if (Int32.Parse(text) == a)
      return 4;
  else if (c == a + b)
      return 5;

  return 6;
}

image

Jak widzimy, wszystkie gałęzie zostały pokryte, włączając te, które wyrzucają ArgumentException.

Podsumowując… Przede wszystkim, należy pamiętać, że pokrycie kodu 100% nie znaczy, że wszystko zostało przetestowane. Sprawdza to, czy każda gałąź zostanie wywołania, a nie czy każda kombinacja gałęzi jest pokryta.

Kolejna wątpliwość to bardzo popularne dzisiaj TDD. Tak naprawdę, kod powinien być pokryty już testami, jak kończymy implementację czegoś.

Dla mnie największą jednak wadą jest brak wsparcia dla nUnit. Gdybym mógł z niego korzystać, to nawet w połączeniu z TDD może to się przydać. Zawsze mamy legacy kod i możemy popełnić jakiś błąd więc Smart Unit Test byłoby fajnym sposobem na weryfikację naszego kodu. Innymi słowy, nie korzystałbym z tego jako coś bazowego dla moich testów, ale wyłącznie uzupełniającego.

Co prawda, nie testowałem tego jeszcze, ale podobno Microsoft Pex oraz Code Contracts współpracują ze sobą, co oznacza, że moglibyśmy generować bardziej trafne scenariusze użycia za pomocą Smart Unit Test.

C# 6.0: String interpolation

Posted November 22nd, 2014 by Piotr Zieliński
Categories: C#

Dziś znów o nowościach w C# 6.0. Tym razem o interpolacji string’a, który usprawni jego aktualną implementację. Należy, zaznaczyć, że ostateczna wersja nie jest jeszcze znana i wszystkie wpisy, które poświęciłem C# 6.0 mogą zostać zmienione jeszcze w oficjalnej wersji języka.

Ale po kolei… Najpierw polecam ściągnąć VS 2015, jeśli jeszcze tego nie zrobiliście. Co to jest string interpolation?Jak to w tej branży bywa, jest to skomplikowane słowo na bardzo prostą rzecz. W aktualnych wersjach języka często korzystamy z string.Format:


string firstName =...
string lastName = ...
string text = string.Format("Witaj {0} {1}", firstName, lastName);

Innymi słowy, interpolowanie stringa polega na zastąpieniu pewnych miejsc ({0} – “placeholdders”) konkretnymi wartościami.

Co jest złego w string.Format? Dla skomplikowanych wyrażeń,  jest to po prostu mało wygodne w użyciu.

Na przykład, stara wersja:

String.Format("{0}, {1}!", hello, world)

Jest równoznaczna, z napisaniem w c# 6.0 tego:

$"{hello}, {world}!"

Składnia o wiele krótsza. To dopiero jednak początek. Kolejny przykład (z oficjalnej dokumentacji na roslyn codeplex):

Będzie równoznaczny z :

$"Name = {myName}, hours = {DateTime.Now:hh}"

String.Format("Name = {0}, hours = {1:hh}", myName, DateTime.Now)

Placeholder zatem zamiast zawierać indeks zmiennej, stanowi po prostu jej nazwę.

Warto wspomnieć, że w preview na razie dostępna jest wyłącznie taka składnia:

"\{hello}, \{world}!"

Z tego co wiem, w oficjalnym wydaniu będzie $, ale to jeszcze nie jest pewne.

Składnia nie ogranicza się wyłącznie do wstawiania wartości z określonym formatowaniem. Można wykonać dowolne wyrażenie np.:

int a = 5;
Console.WriteLine("Value is \{(a>0?"positive":"negative")}");