Niejednokrotnie musimy przeczytać jakieś dane “skądś”, i nie mam na myśli bazy danych. Czy to zewnętrzny system przysyła excela, czy na dysku ląduje plik tekstowy, czy jeszcze coś innego. Takie pliki musimy przetworzyć, więc prawdopodobnie utworzymy klasę “XFileProcessor” albo “XFileParser”, czy coś w ten deseń.
To jest akurat idealny scenariusz do pisania testów: dostajemy input i znamy oczekiwany output. Klient przysyła plik wejściowy i mówi, jakie dane powinniśmy z niego uzyskać. Jazda!
Więc jak takie coś testować? Pisząc testy oczywiście, ale… jak te dane testowe przekazać do testów? Można je trzymać na dysku w znanej lokalizacji i w testach je z dysku czytać. Można je dodać do projektu, zaznaczyć property pliku “Copy to output directory” na “Always copy” i… też czytać z dysku.
To drugie rozwiązanie stosowałem przez długi czas, ale jeden czy dwa projekty temu znalazłem lepsze rozwiązanie. Kopiowanie plików w projekcie testowym na dysku z jednego miejsca w inne miejsce nie jest złe. Ale czasami się nie sprawdza. Na przykład gdy używamy NCrunch: chyba to właśnie on sprawił mi trochę problemów, przez co zacząłem szukać alternatywy. NCrunch kopiuje do swojego “workspace”, w którym uruchamia testy, pliki oznaczone jako “do skopiowania” nie we właściwościach pliku w VS, ale w konfiguracji NCrunch (jednak ponownie: ręki sobie za to uciąć nie dam).
Ale to nieważne.
Tak czy siak wygodniejsze okazało się posiadanie dllki z testami, która nie potrzebuje żadnej specjalnej struktury katalogów i plików z danymi w nich umieszczonych. Zamiast tego oznaczam takie pliki jak “embedded resource” (w “build action”) i są one “wkompilowywane” w dllkę. Mechanizm jest oczywiście stary jak świat, jednak z jakichś powodów wcześniej o tym nie pomyślałem.
Dobieranie się do tych zasobów często stanowiło trochę problem, ponieważ nazwą zasobu jest “default namespace” projektu plus cała struktura katalogów aż do docelowego pliku. A na koniec nazwa tegoż. Utworzyłem więc sobie helper wykorzystywany w testach, który otworzy mi stream zasobu i wystarczy mu tylko nazwa pliku, bez całej struktury katalogów:
public static class EmbeddedData { public static string AsString(string name) { var stream = AsStream(name); using (var reader = new StreamReader(stream)) { return reader.ReadToEnd(); } } public static Stream AsStream(string name) { var callingAssembly = Assembly.GetCallingAssembly(); var resources = callingAssembly.GetManifestResourceNames() .Where(x => x.EndsWith(name)) .ToList(); if (resources.Count == 0) { throw new ArgumentException(string.Format("Embedded resource with name '{0}' cannot be found in assembly '{1}", name, callingAssembly.FullName)); } if (resources.Count > 1) { throw new ArgumentException(string.Format("Multiple resource with name '{0}' found in assembly '{1}", name, callingAssembly.FullName)); } string fullName = resources[0]; return callingAssembly .GetManifestResourceStream(fullName); } }
Żadne to rocket science, ale przydało mi się już kilkukrotnie.