Tak jak obiecałem, zajmiemy się teraz przetestowaniem powstałej klasy SeparatedFileReader. Również tym razem wykorzystamy mechanizm automatycznych testów Visual Studio 2010. Czy możemy skorzystać z poprzedniego zestawu testów? Niespecjalnie, nie testujemy już metod, ale konkretną klasę. Poza tym – proszę zauważyć, że nie tworzymy już XML-a, zatem porównanie z plikiem zawierającym XML nie ma szans na pozytywny wynik. Ktoś może zapytać: to po co to wszystko, te całe testy? Wykorzystaliśmy je tylko raz i mamy je sobie podarować?! Tak to już z refaktoryzacją bywa. Moglibyśmy co prawda zrobić tak, że otrzymane w imporcie wartości przetworzymy na XML, za pomocą metody służącej do transferu tych wartości. Rzecz w tym, że nie ma takiej potrzeby. Poprzednio nie bardzo było wyjście – metody wyodrębnione do klasy MethodObject miały pozostać niezmienne, więc siłą rzeczy testy musiały się do nich dostosować. Tu też metoda Read() klasy ma taką, a nie inną postać, zatem dobrze byłoby przetestować wyłącznie ją, a nie przy okazji także metodę komponującą XML. Najlepiej by była ona jak najprostsza, aby umożliwić przetestowanie tylko tego, co ma przetestować.

Jakie zatem kryterium poprawności testów przyjmiemy? Myślę, że będzie to nadal porównanie tekstów, tylko że teraz, po prostu skleimy wszystkie wartości w jeden ciąg (zrobi to metoda podstawiona pod delegata transfer) i po imporcie porównany z oczekiwanym tekstem. Tekst ten będzie – jak można się domyślić – następujący

jeden1dwa2trzy3cztery4pięć5

Metoda podstawiana pod delegata transfer, będzie wyglądała następująco:

public void StoreContent(params string[] values)
{
	actual += values[0] + values[1];
}
#region pola
private string actual;
#endregion

będzie jeszcze potrzebna stała do porównywania:

#region stałe
const string expected = "jeden1dwa2trzy3cztery4pięć5";
#endregion

a samo porównanie będzie realizowane tak:

Assert.AreEqual(expected, actual);

Pozostają metody testujące import plików separowanych przecinkiem i tabulacją, czyli:

[TestMethod]
public void test_Comma_Separated_File_Import()
{
	actual = "";
	ProgressMock progress = new ProgressMock();
	SeparatedFileReader reader = new SeparatedFileReader(Folder + "csv.csv", ',', StoreContent, progress);
	reader.Read();
	Assert.AreEqual(expected, actual);
}

[TestMethod]
public void test_Tab_Separated_File_Import ()
{
	actual = "";
	ProgressMock progress = new ProgressMock();
	SeparatedFileReader reader = new SeparatedFileReader(Folder + "txt.txt", '\t', StoreContent, progress);
	reader.Read();
	Assert.AreEqual(expected, actual);
}
#region pola
private string actual;
#endregion
#region stałe
const string expected = "jeden1dwa2trzy3cztery4pięć5";
const string Folder = @"..\..\..\";
#endregion

Trudno nie zauważyć, że obie metody różnią się właściwie dwoma drobnymi szczegółami. Nie można tego tak zostawić, należy je zrefaktoryzować (a jakże ;)), do prostszej postaci. Przy okazji – skoro w poprzedniej części odkryliśmy, że mamy teraz uniwersalną metodę importu dowolnie separowanych plików, zróbmy też test na import pliku separowanego średnikami. Oto plik (o nazwie ssv.ssv – od semicolon separated values):

jeden;1
dwa;2
trzy;3
cztery;4
pięć;5

A tu kompletny kod testów:

[TestClass]
public class SeparatedFileReaderTest
{
	#region metody testowe
	[TestMethod]
	public void test_Comma_Separated_File_Import()
	{
		ImportTest("csv.csv", ',');
	}

	[TestMethod]
	public void test_Tab_Separated_File_Import ()
	{
		ImportTest("txt.txt", '\t');
	}

	[TestMethod]
	public void test_Semicolon_Separated_File_Import()
	{
		ImportTest("ssv.ssv", ';');
	}
	#endregion
	#region metody
	private void ImportTest(string fileName, char separator)
	{
		actual = "";
		ProgressMock progress = new ProgressMock();
		SeparatedFileReader reader = new SeparatedFileReader(Folder + fileName, separator, StoreContent, progress);
		reader.Read();
		Assert.AreEqual(expected, actual);
	}
	public void StoreContent(params string[] values)
	{
		actual += values[0] + values[1];
	}
	#endregion
	#region pola
	private string actual;
	#endregion
	#region stałe
	const string expected = "jeden1dwa2trzy3cztery4pięć5";
	const string Folder = @"..\..\..\";
	#endregion
}

public class ProgressMock : IProgressNotifier
{
	public int Expected { get; set; }
	public int Completed { get; set; }
}

Widać tu dodatkową klasę udającą interfejs prezentacji postępu przetwarzania (ProgressMock), która oczywiście – tak, jak w poprzednich testach – jest zwykła atrapą. I tak jak we wcześniejszych testach – tak i tutaj trzeba najpierw sprawić, by metody testujące nie przechodziły testów. Ja ten etap w prezentacji kodu pomijam, aby nie komplikować przekazu. Pozostaje uruchomić testy – najpierw dla csv.csv. Pomimo spodziewanego sukcesu mamy jednak porażkę! Dlaczego? Bo niechcący – w poprzedniej części cyklu – usunąłem z kodu ustalenie kodowanie znaków narodowych w konstruktorze strumienia (chyba nikt nie czyta tego cyklu, skoro ten błąd przeszedł wówczas bez echa :(). Skoro tak, to trzeba błąd poprawić (niech ktoś powie, że testy to nie jest zbawienny mechanizm ;)) i wtedy test daje wynik poprawny. Kolejno dołączamy pozostałe dwa testy i tutaj także (skoro przeszedł csv.csv) uzyskujemy wynik poprawny.

Tym samym kolejny etap refaktoryzacji zostaje zakończony. W kolejnej części spróbujemy zrobić pierwsze podejście do unifikacji wszystkich metod importu do wspólnej postaci.