Wprowadzenie do C# dla testerów automatyzujących

Autor: Michał Dobrzycki

Wprowadzenie do Visual Studio

Microsoft wypuścił dwa narzędzia, Visual Studio oraz Visual Studio Code. To są dwa różne narzędzia.

Potrzebujemy trzech aplikacji do wykonania ćwiczeń z C#:

Gdzie szukać informacji o Visual Studio (IDE)

Czym jest NuGet?

Definiuje sposób tworzenia, hostowania i obsługi pakietów dla platformy .NET

Dostarcza też narzędzia do zarządzania nimi

nuget.exe to manager paczek (coś jak npm dla JS albo maven dla Javy)

  • Pozwala wrzucić skompilowaną DLL do sieci i udostępnić innym
  • Automatyzuje podpinanie bibliotek pod projekt
  • Dostarcza narzędzia (klienta) do obsługi CI/CD

Paczka nugetowa, to nic innego jak plik ZIP, który ma w środku skompilowane binarki projektu napisanego w C#.

Kilka linków:

Czym jest .NET?

Platforma udostępniona przez Microsoft dla developerów, do tworzenia wielu rodzajów aplikacji, takich jak:

  • Serwisy i mikroserwisy (REST API, GraphQL)
  • Aplikacje mobilne
  • Aplikacje desktopowe
  • Gry
  • Cloud Native apps
  • ML & AI apps

Aplikacje .NET można tworzyć dla wielu systemów operacyjnych, na przykład:

  • Windows
  • macOS
  • Linux
  • Android
  • iOS

Aktualna najnowsza wersja .NET to 6.0

My będziemy korzystać na tym kursie z wersji 3.1 (LTS)

Czym dla nas jest .NET (praktycznie)

  • Kompilator języka C# (potrafi też kompilować F#, Visual Basic i kilka innych języków MS)
  • Debugger C# (pozwoli nam wykonać aplikację krokowo

Wprowadzenie do C#

.NET potrafi kompilować język C#

Język C# jest:

  • Obiektowy
  • Statycznie typowany
  • Silnie typowany
  • Wielowątkowy

Język C# vs JavaScript

C#

							
							public class Program() {
								public static void Main(string[] args) {
									Console.Writeline("Hello world!");
								}
							}
							
						

JavaScript

							
							console.log("Hello world!");
							
						

Porównanie ze względu na użyteczność w testach automatycznych

Cecha C# JavaScript
Obiektowy TAK TAK
Funkcyjny TAK TAK
Kompilowany TAK NIE
Wielowątkowy TAK NIE
Działa w przeglądarce NIE TAK
Wsparcie BDD TAK TAK
Praca z ORM/DB ŁATWA TRUDNA
Próg wejścia ŚREDNI NISKI

To który z nich jest lepszy do automatyzacji?

Jakby odpowiedział konsultant

To zależy...

01-Wstep

Wszystkie zadania z C# będziemy wykonywać w jednym projekcie

Zróbcie forka i klona na swoje maszyny z tego repozytorium, żeby móc wypychać swoje zmiany na githuba.

Jak definiujemy zmienne w C#?

Musimy podać im typ oraz nazwę zmiennej, przypisać wartość i zakończyć średnikiem

public class Movies() 
{
	int  movieNumber = 65;
	string merchandise = "Avengers";
}
					

Magiczne słówko var, powoduje że C# sam się domyśli jaki to ma być typ.

Zapis poniżej jest równoznaczny z poprzednim

public class Movies() 
{
	var movieNumber = 65;
	var merchandise = "Avengers";
}
					

Rozwiążcie zadania 1-3 z modułu:

01-Wstep

Stringi w C# są niezmienialnymi (immutable) obiektami

Możemy je deklarować na kilka sposobów:

public class Example() {
	string text1; // bez inicjalizacji
	string text2 = null; // inicjalizacja nullem
	string text3 = System.String.Empty; // pusty string jest równoznaczny z string text3 = ""
	string escapePath = "c:\\Users\\Programista";
	var noEscapePath = @"c:\Users\Programista";
}
					

Interpolacja ciągów (string interpolation)

Możemy wstawiać zmienne do środka stringów:

public static void Main(string[] args) 
{
	string name = "Michał";
	string surname = "Dobrzycki";
	double stanKredytu = -300000.43;
	string doDruku = $"Witaj {name} {surname}, twoje aktualne zadłużenie konta wynosi {stanKredytu}";
}
					

Na Stringach można pracować jak z tablicami znaków

public static void Main(string[] args)
{
	string s = "Kobyla ma maly bok";
	char[] charArray = s.ToCharArray();
	// możemy używać wbudowanych funkcji tablic https://docs.microsoft.com/pl-pl/dotnet/api/system.array?view=netcore-3.1
	string reversed = new string( charArray );
}
					

Rozwiążcie zadania 4-6 z modułu:

01-Wstep

Instrukcje warunkowe

Instrukcja if:

public static void Main(string[] args) 
{
		if (warunek1) {
			// jeżeli warunek1 jest TRUE to wykonaj kod
		} else if (warunek2) {
			// jeżeli warunek1 jest FALSE oraz warunek2 jest TRUE to wykonaj kod
		} else {
			// jeżeli wszystkie warunki powyżej są FALSE to wykonaje ten kod
		}
}
					

Instrukcje warunkowe

Tenary operator

public static void Main(string[] args) 
{
		var zmienna = (warunek) ? a : b;
		// if (warunek) 
		// { 
		//	zmienna = a;
		// } 
		// else 
		// { 
		//  zmienna = b;
		// };
}
					

Instrukcje warunkowe

Instrukcja switch (zamiennie do if):

public static void Main(string[] args) 
{
	// załóżmy że użytkownik podaje zmienną typu int age z klawiatury
	switch (age) {
		case < 0:
			Console.WriteLine("Nie można mieć mniej niż 0 lat");
			break;
		case > 150:
			Console.WriteLine("Nie można mieć mniej więcej niż 150 lat");
			break;
		case 42:
			Console.WriteLine("Douglas Adams bardzo lubi ten wiek!");
			break;
		default:
			Console.WriteLine($"Masz {age} lat!");
			break;
}

					

Rozwiążcie zadania 7-9 z modułu:

01-Wstep

Metody

W C# punktem wejściowym do programu jest funkcja public static void Main()

Metody to funkcje (bloki kodu zawierające instrukcje), które przyjmują dane na wejściu i zwracają przetworzone dane (być może innego typu) na wyjściu

Metody muszą coś zwracać, a jeżeli nie zwracają używamy słowa void (zwracamy pustkę)

Metody i zwracanie wartości

W C# każda instrukcja jest wykonywana w kontekście metody.

Metody definiujemy za pomocą schematu

[atrybut dostępu] [static] typ_zwracany NazwaMetody(int parametr1, ...)

Elementy w nawiasach kwadratowych są opcjonalne, opowiemy o nich później.

Przykłady definicji metod

public static double CalculateGross(int netto, double vat) { return netto + (netto * vat) };
string GetFullName(string name, string surname) { return $"{name} {surname}"};
void SendSMS(string email, string message) { /* kod wysyłający email */ };
					

Rozwiążcie zadania 1-3 z modułu:

01-Wstep

Switch + when

Od .NET 3.1 w górę możemy używać bardziej skomplikowanego dopasowania wzorców

Musimy zrobić tutaj trick ze znakiem _, którego używamy jako placeholder dla zmiennej

int result = x switch
{
	_ when x > 0 && y > 0 => zwróć taką wartość,
	_ when x > 0 && y < 0 => zwróć taką wartość,
	_ when x < 0 && y < 0 => zwróć taką wartość,
	_ when x < 0 && y > 0 => zwróć taką wartość,
	_ => 0
};
					

Rozwiążcie zadania 10-14 z modułu:

01-Wstep

Debugowanie

Debugowanie to krokowe i kontrolowane wykonywanie programu. Visual Studio pozwala nam wykonać program linijka po linijce.

Debugowanie - instrukcja

Ustaw breakpoint

Debugowanie warunkowe - instrukcja

Ustaw breakpoint warunkowy
Podgląd na debugowanie w Visual Studio

Rozwiążcie zadania modułu:

02-Debugowanie

Wyjątki

Czym są wyjątki?

Wyjątek są po to, żebyśmy mogli radzić sobie z niespodziewanymi sytuacjami w kodzie.

Wyobraźcie sobie co może się stać jeżeli:

  • każdemy użytkownikowi wpisać maila w formularz rejestracyjny
  • nasza aplikacja przyjmuje pliki excela wrzucane przez użytkownika
  • nasza aplikacja zależy od połączenia z inną aplikacja (przez HTTP)
  • chcemy pobrać dziesiąty element z listy, która ma 5 elementów
  • przez przypadek admin usunął nam pliki konfiguracyjne z serwera

Jak obsługujemy wyjątki?

try
{
	result = 10/0;
	Console.WriteLine(result);
}
catch (DivideByZeroException e)
{
	Console.WriteLine("Attempted divide by zero.");
}
					

Jak rzucać wyjątkiem z naszych funkcji?

Możemy tworzyć własne wyjątki lub rzucać gotowymi. Np. Selenium tworzy kilkanaście specyficznych dla siebie wyjątków.

throw new Exception("Treść wyjątku");
					

Czas na zadania z działu 03-Wyjątki

Struktury danych w C#

Podstawowymi strukturami danych potrzebnymi w testach automatycznych są:

  • Tablice
  • Enumy
  • Listy
  • Słowniki

Tablice

Dodajemy [] na końcu typu zmiennej żeby zdefiniować tablicę. Musimy również podać jej długość. Raz zdefiniowanej długości nie możemy już zmienić.

int[] myIntArray = new int[5] { 1, 2, 3, 4, 5 };
						

Enum - typ wyliczeniowy

Tworzymy go poza klasą po to, żeby ograniczyć liczbę możliwych wartości

public enum Season
{
	Spring,
	Summer,
	Autumn,
	Winter
}
					

Listy

Struktury przypominające tablice, ale tablice na sterydach (możemy zmieniać jej długość). Przechowują kolekcję konkretnych typów.

{
List<int> numberList = new List<int>();
numberList.Add(54);
numberList.Add(45);
numberList.Remove(45); // remove an item
numberList.RemoveAt(0); // remove item with index 0
}

Szybkie definiowanie list z danymi

List<string> names = new List<string>()
{
	"Jan",
	"Bogna",
	"Zofia"
}					

Słowniki

Struktura danych trzymająca pary danych klucz - wartość.

Dictionary<string, string> openWith = new Dictionary<string, string>();
	openWith.Add("txt", "notepad.exe");
	openWith.Add("bmp", "paint.exe");

Czytanie danych ze słowników, używamy try-catch

try
	{
		Console.WriteLine("For key = \"tif\", value = {0}.", openWith["tif"]);
	}
	catch (KeyNotFoundException)
	{
		Console.WriteLine("Key = \"tif\" is not found.");
	}
					

Lepsze czytanie danych ze słownika. Uwaga: magiczne słówko out

string value = "";
if (openWith.TryGetValue("tif", out value))
{
	Console.WriteLine("For key = \"tif\", value = {0}.", value);
}
else
{
	Console.WriteLine("Key = \"tif\" is not found.");
}					

Czas na zadania z działu 04-Struktury-danych

Obiektowość w C#

Cztery filary programowania obiektowego:

  • Abstrakcja - chowanie logiki
  • Hermetyzacja - chowamy kod przed niezaplanowanym użyciem
  • Polimorfizm - podszywamy się pod inne klasy
  • Dziedziczenie - przejmowanie właściwości i funkcjonalności

OOP w C#

Tutorial OOP

Klasy, czyli przepis na tworzenie obiektu

Myślimy o rzeczywistości w postaci obiektów (rzeczowników). Obiekty te mają swoje:

  • właściwości (atrybuty, struktury danych)
  • zachowania (metody, funkcje)
public class Car 
{
	public double horsePower; // właściwość
	public static void Main(string[] args) { ... } // metoda
}
					

Konstruktor, czyli ustawianie danych przy tworzeniu obiektu

Jeżeli utworzymy klasę to wszystkie właściwości będą miały domyślne wartości.

Możemy zmienić to zachowanie konstruktorem który wymusza ustawienie wartości pól podczas tworzenia obiektu.

public class Car 
{
	public double horsePower; // właściwość
	public Car(double horsePower) 
	{
		this.horsePower = horsePower;
	}
}					

Interfejsy, czyli definicja zachowania dla klas

interface IWebDriver<T>
{
	void Quit(); // każdy webdriver musi potrafić zamknąć przeglądarkę
	IWebElement FindElement(); // i znaleźć element na stronie
}					

Czas na zadania z działu 05-Obiekty

Modyfikatory dostępu w C#

  • public
  • internal
  • private
  • protected

Tabelka

Properties

Dokumentacja

Czas na zadania z działu 05-Modyfikatory

Wyrażenia regularne

Pomocne linki dla regexów:

Czytanie plików do stringa

Dokumentacja klasy File

Ważne: żeby móc korzystać w trybie build z pliku tekstowego, musimy po jego dodaniu kliknąć na Properties i ustawić "Copy to output directory" na wartość "Copy always". Domyślnie ustawione jest "Do not copy".

string wynnik = File.ReadAllText("plik.txt");

Przypomnienie wzorców:

  • [a-zA-Z] - litery (bez polskich znaków)
  • [^abc] - nie zawiera litery abc
  • \p{L} - dowolna litera Latin (z polskimi znakami)
  • . - dowolny znak
  • \s+ - spacja jedna lub więcej
  • \S* - nie spacja zero lub więcej
  • \d{1,2} - cyfra jedna lub dwie
  • (Piwo|Cydr|Wino) - Piwo lub Cydr Lub Wino

Jak używamy ich w C#

public static void Main(string[] args) 
{
	// używajmy głównie @"" żeby traktować znak \ jako część regexa
	string wyszukiwany = "...";
	Regex re = new Regex(@"wzorzec");
	// znajdź wszystkie wystąpienia (możemy po nich iterować)
	MatchCollection result = re.Matches(wyszukiwany);
	foreach (Match r in result)
	{
		Console.WriteLine(r.Value);
	}
}					

Co zrobić żeby traktować każdą linijkę stringa/pliku jako osobny string

public static void Main(string[] args) 
{
	// znajdź od początku linii wielkie litery
	Regex re = new Regex(@"^[A-Z]+", RegexOptions.Multiline);
}
					

Czas na zadania z działu 07-RegEx

Arrow functions

Lambda operator

Służy trzem celom:

  • Lambda operator
  • Expression body definition
  • Do korzystania z Linq

Lambdy możemy wrzucać do środka jako funkcje anonimowe

string[] words = { "bot", "apple", "apricot" };
int minimalLength = words
	.Where(w => w.StartsWith("a"))
	.Min(w => w.Length);
						

Expression body definition jest popularne dla krótkich funkcji.

Na przykład nadpisanie metody toString obiektu.

public override string ToString() => $"{fname} {lname}".Trim();

Czas na zadania z działu 08-ArrowFunctions

LINQ

Język zapytań charakterystyczny dla C#. Przypomina trochę SQL. Służby do szukania elementów w kolekcjach, plikach XML i bazach danych.

Wstęp do LINQ

Wybór i obróbka elementów

.Select()

var powers = numbers.Select(x => x * x);

lub statycznie definiując

IEnumerable powers = numbers.Select(x => x * x);

.Where()

var dobrePiwa = beers.Where(p => p.Contains("pełne"));

Jeżeli potrzebujemy Listę, to możemy użyć:

var greaterThan50 = numbers.Where(e => e>50).ToList();

.GroupBy()

Zakładając że mamy Listę obiektów:

new Customer(){ Name="Bartosz Walaszek", OrdersQuantity=40, TotalPurchasesAmount=8345.66, Bank="MIL"},

Możemy grupować elementy tej listy po wybranych polach (properties)

var banks = customers.GroupBy(b => b.Bank);

To zwraca nam coś "jakby" słownik gdzie kluczem będzie nazwa banku.

.OrderBy()

Możemy posortować obiekty według konkretnych pól

var banks = customers.OrderBy(b => b.OrdersQuantity);

.FirstOrDefault()

Pobierz pierwszy lub wartość domyślną (w przeciwieństwie do First() jest bardziej bezpieczne)

List<int> months = new List<int> { };
int firstMonth = months.FirstOrDefault(); // zwraca 0 mimo pustej tablicy						
					

Inne funkcje agregujące (do znalezienia w dokumentacji):

  • .Count()
  • .Sum()
  • .Average()
  • .Max()
  • .Min()

Czas na zadania z działu 09-Linq