Wątek przeniesiony 2015-05-28 18:19 z C# i .NET przez somekind.

Testy jednostkowe - rady i wskazówki

0

Mniej-więcej rok temu napisałem (o czym zresztą był temat na 4p) niewielkiego CMSa. Projekt ten jest po wielu poprawkach, głównie dzięki Waszym sugestiom, stąd sam kod jest chyba w całkiem dobrym stanie. Brakuje jednak jednego elementu - testów.

Projekt na githubie: https://github.com/AlfaLeporis/blog/tree/master/Blog

Przymierzam się do napisania nowego CMSa z większą ilością funkcji tym razem już z testami, stąd pytanie do Was - jak powinny wyglądać testy jednostkowe dla wyżej podanego projektu z linku? Chodzi mi o jakieś rady/wskazówki co powinienem testować/mockować itp.

Pozdrawiam : )

2

asp.net mvc znam bardzo pobieznie wiec dam ci 3 bardzo ogolne i proste rady:

  1. testuj tylko nieprywatne skladowe
  2. pisz male testy z nazwami tlumaczacymi wejscie i spodziewany rezultat
  3. powinienes mockowac zaleznosci, zwlaszcza te z side effectami (baza danych, pliki, timery itp)
0

Dzięki, przyda się :) Mam jeszcze pytanie dotyczące stricte ASP.NET MVC. Otóż załóżmy że mamy sobie jakiś kontroler z metodą Home, do której przypisane jest sporo różnych filtrów (przykładowo filtr który dodaje do ViewData jakieś informacje). Jak w takim razie testować wspomnianą metodę? Bez filtrów jest to proste, ale co zrobić w takim przypadku? Ew. jakiś link w którym to jest wyjaśnione bo nie znalazłem szczerze mówiąc niczego na ten temat.

3

Filtry testuje się niezależnie od kontrolerów.

Niemniej ja uważam, że kontrolery nie powinny zawierać logiki, więc nie ma sensu ich testować.

0

Mam prośbę, zerknijcie jak wyglądają poniższe testy i napiszcie czy jest ok/co poprawić/czy w ogóle mam złe podejście do tematu:

http://4programmers.net/Pastebin/4134

Dla wyjaśnienia, testowany jest tutaj pewien serwis do odczytywania konfiguracji (zwykłe pary key-value). Do mockowania użyłem Moq, a FakeDbSet to DbSet in memory. Ale to na pewno wiecie : ]

1

Dobra książka o testach to ta stąd:
http://practicalunittesting.com/
Co do jej dostępności to musisz pytać googla :-) ale na pewno widziałem na Amazonie, nie wiem jak w księgarniach czy z innych źródeł.

0

Zmieniłbym u Ciebie nazewnictwo testów. Wywaliłbym prefix Test (od oznaczania testów masz atrybuty) i słówko Should (niepotrzebne wydłużenie i tak zwykle długich nazw metod testowych). Nazwa powinna też konkretniej opisywać test. Czyli na przykład zamiast: TestSetValueWithBadKeyShouldReturnException użyj: SetValueForNonExistingKeyThrowsException.

2

Czytelne nazwy metod testujących pisze się np. tak: MetodaTestowana__SprawdzaneWarunki__OczekiwanyWynik
Dla Twojego przykładu:

GetValueByKeyName__KeyNotExists__ThrowsKeyNotFoundException
GetValueByKeyName__KeyExists__ReturnsValidValue
Exist__KeyNotExists__ReturnsFalse
Exist__KeyExists__ReturnsTrue
AddPair__KeyAlreadyExists__ThrowsDuplicateKeyException

I jeszcze ogólne uwagi:

  1. ExpectedException może być niebezpieczny, bo może pomyłkowo złapać wyjątek, który nie będzie wynikiem testowanej metody lecz po prostu nastąpi w metodzie testującej. W efekcie wynik testu będzie fałszywie pozytywny.
  2. Testy wielu metod w jednej klasie powodują, że klasa testująca rozrasta się do gigantycznych rozmiarów. Warto zachować umiar, i jeśli klasa robi się długa, podzielić ją na zasadzie jedna klasa testująca dla jednej metody testowanej. Tak jest czytelniej, zgodnie z SRP, a poza tym setup testu robi się krótszy. Wówczas należy też pominąć pierwszą część nazwy metody testującej z przykładów powyżej.
0

Nietestowanie prywatnych metod - całkowicie się z tym nie zgadzam. Testy jednostkowe powinny testować każdą metodę, sprawdzać czy dla prawidłowych danych zwraca prawidłowe wyniki i czy dla danych nieprawidłowych zachowuje się przewidywalnie (rzuca określone wyjątki/zwraca określoną wartość). Jeśli nie będziesz testować prywatnych metod (a w praktyce internal), to nie będziesz w stanie napisać testów jednostkowych, tylko testy integracyjne.

Podwójne znaki podkreślenia w nazwach - wygląda to paskudnie. Jeśli dwa są lepsze niż jeden, to czemu nie trzy albo cztery? Wygląda jak powrót do notacji węgierskiej.
U nas używamy nieco innej składni - jeśli testowane są przypadki typowego, poprawnego działania, to metoda testująca nazywa się tak samo jak metoda testowana. Jeśli testujemy zachowanie dla nieprawidłowych danych, to nazwę stanowi nazwa testowanej metody + podkreślenie + nazwa case'a, np. SendPasswordRetrievalEmailToUser_UserNotFound. Jak widać trochę podobnie do tego, co zaproponował somekind, ale bez podwójnych podkreśleń (fuu!) i bez zawierania w nazwie tego, co ma się zadziać. To ostatnie jest spowodowane DRY - jeśli ktoś robiąc zmianę w testowanej metodzie, a potem w teście, zapomni zmienić nazwę testu, to potem będą głupoty - nazwa testu będzie sugerować jedno, jego działo będzie robić drugie. A jak wiadomo, jeśli coś złego może się przydarzyć, to na pewno się przydarzy ;-)
BTW "KeyNotExists" to nie po angielsku.

Nietestowanie kontrolerów to podobny żart jak nietestowanie prywatnych metod. Przecież to nadal kod pisany przez programistę i potem potencjalnie modyfikowany przez kolejnych programistów. Z tego względu MUSI być przykryty testem, chociażby tylko po to, żeby sprawdzić że mock został wywołany i jego wynik został wepchnięty do modelu.

Patrząc na kod testów @AlfaLeporis:
Jeśli używałbyś NUnita, to warto zapoznać się z atrybutami SetUp, TearDown, TestFixtureSetUp i TestFixtureTearDown. Potem zamiast w każdym teście wywoływać var service = GetServiceWithData(); (DRY!) robisz sobie pole private Service sut; (sut od Service Under Tests) i w Setup inicjalizujesz sobie je na nowo przed każdym testem, tak samo robisz z mockami. Z kolei w Teardown walidujesz wszystkie mocki.
U nas wygląda to tak (część kodu wycięta) - NUnit + Moq:

	public class UserControllerTests
	{
		private Mock<IUserService> userServiceMock;
		private Mock<ILicenceContextService> licenceContextServiceMock;
		...

		private Mock<UserController> cutPartialMock;
		private UserController cut; // cut, bo Controller Under Tests

		#region Setup/Teardown
		[SetUp]
		public void TestSetUp()
		{
			userServiceMock = new Mock<IUserService>();
			licenceContextServiceMock = new Mock<ILicenceContextService>(MockBehavior.Strict);
			...

			cut = new UserController(userServiceMock.Object, licenceContextServiceMock.Object, ...);
			cutPartialMock = new Mock<UserController>(MockBehavior.Loose, userServiceMock.Object, licenceContextServiceMock.Object, ...) { CallBase = true };
		}


		[TearDown]
		public void TestTearDown()
		{
			cutPartialMock.VerifyAll();
			userServiceMock.VerifyAll();
			licenceContextServiceMock.VerifyAll();
			...
		}
		#endregion

// i jakiś przykładowy test, od razu na partial mocku żeby było więcej wiedzy
		[TestCase(true, true, null)]
		[TestCase(false, true, ErrorMessages.NoSuchUser)]
		[TestCase(true, false, ErrorMessages.CantCancelPendingAccountDelete)]
		public void CancelDeleteUserAccount(bool userExist, bool isDeleteCanceled, string expectedMessage)
		{
			var user = new User { Id = 128123, Email = "[email protected]", DeleteProcedureHash = "abc" };

			userServiceMock.Setup(x => x.GetUserById(user.Id)).Returns(userExist ? user : null);

			if (userExist)
				userAccountServiceMock.Setup(x => x.CancelDelete(user.Email, user.DeleteProcedureHash)).Returns(isDeleteCanceled);

			var result = cut.CancelDeleteUserAccount((int)user.Id);

			var data = (JsonStatusModel)result.Data;
			Assert.AreEqual(expectedMessage._(), data.statusMessage);
		}

Jeszcze jedna sprawa - ze względu na bazę danych niektóre testy muszą być integracyjne z dwóch powodów. Po pierwsze EF nie pozwala na mockowanie, więc na najniższym poziomie - tam gdzie testuje się metody pracujące z bazą - trzeba pracować z "żywą" bazą danych. Po drugie ze względu na indeksy i klucze obce (albo i triggery), a także ze względu na współbieżność i transakcje czasem może się okazać, że jednostkowo/na mockach cały proces działa fajnie, a mimo to w praktyce się wywala. Z tego powodu czasem warto dodać czasem też test integracyjny sprawdzający cały proces (acz testowanie zachowania transakcji - UpdateException, OptimisticConcurrencyException - to rzecz koszmarnie trudna do automatycznego odtworzenia).

2

Metody prywatne testują się przy okazji testów metod publicznych, a jeśli realizują tak skomplikowaną logikę, że wymagają jakichś dodatkowych przypadków testowych, to znaczy, że klasa łamie SRP, i taką metodę trzeba wyciągnąć do innej klasy, uczynić publiczną i normalnie przetestować.

Podwójne znaki podkreślenia w nazwach - wygląda to paskudnie. Jeśli dwa są lepsze niż jeden, to czemu nie trzy albo cztery?

Dla mnie właściwie bez różnicy, czy są dwa czy jeden. To kwestia konwencji, a celem jest oddzielenie poszczególnych części nazwy metody.

Jak widać trochę podobnie do tego, co zaproponował somekind, ale bez podwójnych podkreśleń (fuu!) i bez zawierania w nazwie tego, co ma się zadziać. To ostatnie jest spowodowane DRY - jeśli ktoś robiąc zmianę w testowanej metodzie, a potem w teście, zapomni zmienić nazwę testu, to potem będą głupoty - nazwa testu będzie sugerować jedno, jego działo będzie robić drugie.

Oczekiwany wynik w nazwie metody jest dość istotny, bo informuje o celu testu. Gdy test się wywali nie trzeba później tracić czasu na domyślanie się, jaki miał być wynik. W praktyce jest to dodatkowa forma dokumentacji kodu.
Problem utrzymania kodu testowego to inna sprawa, no i jeśli ktoś tego nie robi, to jest leniwy, a nie konwencja jest zła.

Nietestowanie kontrolerów to podobny żart jak nietestowanie prywatnych metod. Przecież to nadal kod pisany przez programistę i potem potencjalnie modyfikowany przez kolejnych programistów. Z tego względu MUSI być przykryty testem, chociażby tylko po to, żeby sprawdzić że mock został wywołany i jego wynik został wepchnięty do modelu.

Jeśli ktoś ma nadmiar czasu, albo płacą mu od sztuki testu, to pewno ma to sens.
Ja wychodzę z założenia, że od testowania GUI są testy GUI, czy to automatyczne, czy też manualne. A kontroler to właśnie GUI - nie powinien robić nic poza wywołaniem niższej warstwy i przekierowaniem do widoku, jeśli w kontrolerze zawarta jest jakakolwiek inna logika, to mamy fat controller i łamiemy MVC.

http://www.maciejaniserowicz.com/2011/11/30/ut-4-co-testowac-a-czego-nie-testowac/

1

tak, pewnie jeszcze jak nie ma 100% pokrycia, to w ogole projekt jest do wyrzucenia ;)
testowanie kazdej metody (na dodatek prywatnej) sprawia ze lamie sie enkapsulacje i zasade DRY o ktorej wspominasz.
enkapsulacje - bo upubliczniasz 'flaki' i tworzysz zaleznosci ktorych nie powinno byc.
DRY - bo wielokrotnie testujesz te same metody (zwlaszcza gdy jest sporo malych metod pomocniczych).

podkreslenia to nie jest zadna notacja wegierska a jedynie poprawienie czytelnosci, w teamie stosujemy format NazwaMetody_opis_wejscia_opis_spodziewanego_rezultatu i calkiem niezle sie to sprawdza.
testujac tylko elementy publicznego interfejsu, bardzo rzadko zdarza sie ze zmienia sie nazwy, jesli juz to dochodza nowe albo cala rzecz wylatuje (przynajmniej w projektach w ktorych pracuje).

Nie wypowiem sie o [nie]testowaniu kontrolerow bo nie znam sie na tyle zeby dyskutowac, ale mysle ze troche przesadzasz twierdzac ze cos jest 'zartem', to ze masz takie a nie inne podejscie to nie znaczy ze to jest fakt i jedynie sluszna praktyka.

Mysle ze nie zawsze warto uzywac SetUp/TearDown, czasem lepiej wrzucic ta jedna linijke na poczatku metody, zwlaszcza gdy testowane metody maja rozne wymagania co do zaleznosci.
TestFixtureXXX czesto prowadzi do tego ze testy traca niezaleznosc miedzy soba wiec raczej staram sie unikac.

Napisalabym ze unit testy z zewnetrzna baza to zart, ale sie powstrzymam ;)

0

Testowanie metod prywatnych przy okazji testowania metod publicznych zaprzecza idei testów jednostkowych. W sumie to kwestia podejścia, bo testując integracyjnie można traktować testowaną metodę jak czarne pudełko, co też ma swoje zalety.

Kontroler to nie GUI. Między fat controller'em a kontrolerem walidującym i wstępnie przetwarzającym dane z requesta na potrzeby serwisów droga jest daleka. Jeśli używasz innego modelu, w którym kontroler faktycznie woła tylko serwis wypełniając jego odpowiedzią model dla view, to mogę się z Tobą zgodzić. W każdym innym przypadku kontroler musi być przykryty testami (tak jest u nas).

0
ŁF napisał(a):

Testowanie metod prywatnych przy okazji testowania metod publicznych zaprzecza idei testów jednostkowych. W sumie to kwestia podejścia, bo testując integracyjnie można traktować testowaną metodę jak czarne pudełko, co też ma swoje zalety.

To, że metoda publiczna woła metodę prywatną, to tylko szczegół wewnętrznej implementacji. Testowaną jednostką wciąż pozostaje metoda publiczna.

Kontroler to nie GUI. Między fat controller'em a kontrolerem walidującym i wstępnie przetwarzającym dane z requesta na potrzeby serwisów droga jest daleka. Jeśli używasz innego modelu, w którym kontroler faktycznie woła tylko serwis wypełniając jego odpowiedzią model dla view, to mogę się z Tobą zgodzić. W każdym innym przypadku kontroler musi być przykryty testami (tak jest u nas).

Wykryliście jakieś błędy dzięki tym testom? Czemu testy Selenium są w takim przypadku złe?

1

ŁF chyba potrzebuje nieco literatury zasięgnąć bo bredzi strasznie.

0
katelx napisał(a):

tak, pewnie jeszcze jak nie ma 100% pokrycia, to w ogole projekt jest do wyrzucenia ;)

Skąd taki wniosek?

katelx napisał(a):

testowanie kazdej metody (na dodatek prywatnej) sprawia ze lamie sie enkapsulacje i zasade DRY o ktorej wspominasz.
enkapsulacje - bo upubliczniasz 'flaki' i tworzysz zaleznosci ktorych nie powinno byc.
DRY - bo wielokrotnie testujesz te same metody (zwlaszcza gdy jest sporo malych metod pomocniczych).

Nie upubliczniam flaków, są internalami. To oczywiście jest wadą, bo metody są nadal widoczne w ramach jednego namespace'a i w każdym innym zaprzyjaźnionym, jednak u nas to nie przeszkadza, bo klasy komunikują się ze sobą poprzez interfejsy, jedynie testy odwołują się do "gołych" klas.
Łamanie DRY - możesz mi wyjaśnić, skąd ten wniosek? Przecież zawsze testujesz metodę z danymi poprawnymi, a potem w kolejnym teście/testach z danymi niepoprawnymi, chyba że załatwiasz to test case'ami, co nie zawsze jest możliwe/czytelne. Testy jednostkowe mają właśnie tę zaletę, że za każdym razem testujemy niezbędne minimum kodu, a nie - tak jak robi się to w testach integracyjnych - całe mięso. Ciekawi mnie jak sobie poradzisz integracyjnie z testem metody, która w pewnym przypadku może wywołać metodę innej klasy, która wysyła mail.

katelx napisał(a):

podkreslenia to nie jest zadna notacja wegierska a jedynie poprawienie czytelnosci, w teamie stosujemy format NazwaMetody_opis_wejscia_opis_spodziewanego_rezultatu i calkiem niezle sie to sprawdza.

Śmiałem się z podwójnych podkreśleń, nie faktu łączenia dwóch różnych notacji - sam robię podobnie, bo zwykle nie da się inaczej rozdzielić informacji o tym co jest testowane od tego jak to jest testowane.

katelx napisał(a):

Nie wypowiem sie o [nie]testowaniu kontrolerow bo nie znam sie na tyle zeby dyskutowac, ale mysle ze troche przesadzasz twierdzac ze cos jest 'zartem', to ze masz takie a nie inne podejscie to nie znaczy ze to jest fakt i jedynie sluszna praktyka.

Podtrzymuję opinię, że jest to niepoważne podejście do tematu, bo kontroler to klasa jak inne i tak jak każda inna klasa powinien mieć testy.

katelx napisał(a):

Mysle ze nie zawsze warto uzywac SetUp/TearDown, czasem lepiej wrzucic ta jedna linijke na poczatku metody, zwlaszcza gdy testowane metody maja rozne wymagania co do zaleznosci.
TestFixtureXXX czesto prowadzi do tego ze testy traca niezaleznosc miedzy soba wiec raczej staram sie unikac.

No. Czasem :-) Zdrowy rozsądek zawsze na pierwszym miejscu, potem wiedza, na końcu reguły i wzorce.
Jednak muszę przyznać, że nie spotkałem takiej sytuacji, żeby testy traciły niezależność od siebie z powodu wspólnego setupu czy teardown'a. Klasa bazowa testów zapewnia zwinięcie transakcji, zresetowanie EF, utworzenie od nowa wszystkich instancji potrzebnych klas, tym samym resetując świat pomiędzy testami.

katelx napisał(a):

Napisalabym ze unit testy z zewnetrzna baza to zart, ale sie powstrzymam ;)

A jednak napisałaś. Nie da się pisać testów jednostkowych z ORMem uniemożliwiającym mockowanie (np. Entity Framework), napisałem to we wcześniejszym poście, może tylko nie wyraziłem się wystarczająco jasno.

0

Chłopie czegoś Ty się naćpał? W ET nie można wykonywać testów jednostkowych? To co ja robię od 3 lat :D?

1
somekind napisał(a):

To, że metoda publiczna woła metodę prywatną, to tylko szczegół wewnętrznej implementacji. Testowaną jednostką wciąż pozostaje metoda publiczna.

Nas interesują szczegóły implementacji. Dzięki temu testy są bardzo krótkie, a przez to bardzo czytelne i łatwe do poprawienia przy refactoringu.

Wykryliście jakieś błędy dzięki tym testom? Czemu testy Selenium są w takim przypadku złe?

Tak, wykryliśmy.
Co do pytania o Selenium, to zachowujesz się jak rasowy polityk i to raczej nie komplement. Nic w tym wątku o Selenium jeszcze nie napisałem oO Selenium jest jak najbardziej OK, testuje rzeczy, których nie jesteśmy w stanie przetestować NUnitem.

0

Behavior Driven, anyone?
Dajcie spokój, nazwy metod to nie miejsce na poematy.

0
Zimny Krawiec napisał(a):

ŁF chyba potrzebuje nieco literatury zasięgnąć bo bredzi strasznie.

Konkrety.

Zimny Krawiec napisał(a):

Chłopie czegoś Ty się naćpał? W ET nie można wykonywać testów jednostkowych? To co ja robię od 3 lat :D?

Co to jest ET? Jak chcesz zamockować np. dodanie użytkownika do bazy danych? Zdradź mi ten sekret, bo najwyraźniej od trzech lat źle piszę testy.

0
ŁF napisał(a):

Nas interesują szczegóły implementacji. Dzięki temu testy są bardzo krótkie, a przez to bardzo czytelne i łatwe do poprawienia przy refactoringu.

Nie rozumiem jakim cudem dla procesu biznesowego może być istotne, czy jest wykonywany przez jedną metodę na 30 linijek, czy 5 metod po 6 linijek.

Co do pytania o Selenium, to zachowujesz się jak rasowy polityk i to raczej nie komplement. Nic w tym wątku o Selenium jeszcze nie napisałem oO Selenium jest jak najbardziej OK, testuje rzeczy, których nie jesteśmy w stanie przetestować NUnitem.

Mi chodzi tylko o to, że Selenium i tak przetestuje kontrolery, więc pisanie dodatkowych testów jednostkowych do tak prostej logiki niczego nie wnosi. Jak widać po tekście Aniserowicza, nie tylko ja tak uważam.

0

Jak chcesz zamockować np. dodanie użytkownika do bazy danych? Zdradź mi ten sekret, bo najwyraźniej od trzech lat źle piszę testy.

Nie używasz IoC czy jak? Nie rozumiem, w czym problem.

1

100% pokrycia to byl zart - wierze ze jest bardzo malo teamow ktore maja taki cel.

z tym lamaniem DRY chodzi mi o to ze testujesz:
a) metody publiczne (ktore same uzywaja metod prywatnych)
b) metody prywatne (ktore zwykle uzywaja innych metod prywatnych)
czyli to co jest w b) jest (co najmniej) duplikacja testow z a)

poza klasami tools/utils czy strukturami, nie posiadam raczej typow ktore maja publiczne skladniki poza implementowanym interfejsem, dlatego kazda klasa jest latwa zarowno do zmockowania jak i do testowania bo wiadomo co jest kontraktem i relatywnie niepredko sie zmieni.

wysylanie maila musi byc mockowane aby dalo sie napisac unit test ktory ma taka zaleznosc, nie wazne czy testujesz skladowa public czy internal, chyba cie nie zrozumialam co masz na mysli w tym miejscu.

ok - tak jak mowie nie wiem co sie trzyma w kontrolerach w asp mvc. uwazam ze nie kazda klasa wymaga testow, dosc czesto stosuje np null object pattern czy proste encje z equals/gethashcode i w 99% przypadkow testow albo nie ma albo sa szczatkowe.

mialam pare przypadkow ze testy tracily niezaleznosc, najgorsze ze czasem problem wychodzil po ilus miesiacach od faktycznego grzebania w danej funkcjonalnosci, w projekcie mam pare takich klas bazowych do testow ktore sobie robia rozne myki z teardown/setup i za kazdym razem jak na nie patrze to lzy mi sie zbieraja w oczach bo niektore wymknely sie spod kontroli lata temu i czekam jak na wyrok na moment gdy bede musiala je naprawic.

ostatnio z baza danych czy orm pracowalam dobre 6 lat temu a z EF nigdy wiec szczerze mowiac nie wiem jakie sa opcje, po prostu poleganie na bazie, systemie plikow, watkach, timerach czy randomizacji w testach wlacza mi czerwona kontrolke ;)
moze np. daloby sie uzyc jakas prosta baze in memory sql ce albo sqlite (tworzona blyskawicznie per testFixture)

0
somekind napisał(a):

Nie rozumiem jakim cudem dla procesu biznesowego może być istotne, czy jest wykonywany przez jedną metodę na 30 linijek, czy 5 metod po 6 linijek.

Parafrazując - nie rozumiem jakim cudem dla procesu biznesowego może być istotne, czy jest przykryty testem, czy nie.
Proces biznesowy nie ma nic do rzeczy. Jest jakieś wymaganie, zostało spełnione, dziękuję, tu się proces biznesowy kończy. To od programisty w dużej mierze zależy jak napisał kod realizujący to wymaganie (zwłaszcza w Agile'u), tu się chyba zgodzimy. I tu wychodzi różnica między programistą a dobrym programistą. Dobremu programiście nie będzie wszystko jedno, czy to 1x30, czy 5x6. Podzieli kod na wiele małych metod, być może wyodrębni kolejne klasy. Teraz jeśli masz wiele metod, to zgodnie z DRY będą potencjalnie używane przez wiele innych metod. Jak raz napiszesz test do tej małej metodki, to nigdy więcej nie będziesz musiał jej testować, więc test każdej nowej metody z korzystający z tego maleństwa może je mockować. Ponownie - testy robią się krótsze, przez co są łatwiejsze w utrzymaniu, oraz przejrzystsze, przez co są jeszcze łatwiejsze do utrzymania.

Mi chodzi tylko o to, że Selenium i tak przetestuje kontrolery, więc pisanie dodatkowych testów jednostkowych do tak prostej logiki niczego nie wnosi. Jak widać po tekście Aniserowicza, nie tylko ja tak uważam.

O Selenium nie było mowy do tej pory, wspomniałeś o nim dopiero jak zabrakło argumentów do obrony tezy o nieprzykrywaniu testami klas kontrolerów.
Idąc dalej tym tokiem myślenia - po co pisać testy jednostkowe, skoro można otestować kod Selenium ;-)

1
ŁF napisał(a):

Dobremu programiście nie będzie wszystko jedno, czy to 1x30, czy 5x6. Podzieli kod na wiele małych metod, być może wyodrębni kolejne klasy. Teraz jeśli masz wiele metod, to zgodnie z DRY będą potencjalnie używane przez wiele innych metod.

W moim przykładzie chodzi o to, że zamiast jednej metody publicznej na 30 linijek, mam jedną krótką metodę publiczną, która korzysta z kliku krótkich metod prywatnych. Żadne inne klasy, z tych metod nie korzystają, więc są one prywatne.
I ponawiam pytanie - dlaczego miałbym pisać dodatkowe testy do tych metod prywatnych, skoro zostaną w pełni przetestowane testami metody publicznej?

O Selenium nie było mowy do tej pory, wspomniałeś o nim dopiero jak zabrakło argumentów do obrony tezy o nieprzykrywaniu testami klas kontrolerów.

Teza jak teza - dość powszechnie stosowana praktyka.

Idąc dalej tym tokiem myślenia - po co pisać testy jednostkowe, skoro można otestować kod Selenium ;-)

To nie jest ten tok myślenia. Mój tok myślenia jest pragmatyczny - wolę pokryć testami te klasy, które mają faktyczne znaczenie dla aplikacji i zawierają logikę biznesową, nie infrastrukturę, nie proxy i nie GUI. To wydatek czasowy, który nie przynosi żadnego efektu.

0
somekind napisał(a):

zamiast jednej metody publicznej na 30 linijek, mam jedną krótką metodę publiczną, która korzysta z kliku krótkich metod prywatnych. Żadne inne klasy, z tych metod nie korzystają, więc są one prywatne.
I ponawiam pytanie - dlaczego miałbym pisać dodatkowe testy do tych metod prywatnych, skoro zostaną w pełni przetestowane testami metody publicznej?

Jak widać to zależy od architektury i podejścia. U nas jest dużo metod wykorzystywanych przez wiele innych metod i testy jednostkowe na poziomie każdej metody doskonale zdają egzamin.
Ciekawi mnie jak jesteś w stanie zasymulować np. wyjątek z bazy danych rzucany przez jedną z tych prywatnych metodek.

wolę pokryć testami te klasy, które mają faktyczne znaczenie dla aplikacji i zawierają logikę biznesową, nie infrastrukturę, nie proxy i nie GUI. To wydatek czasowy, który nie przynosi żadnego efektu.

Metody prywatne nie są żadnymi z wymienionych przez Ciebie. Kontrolery również. Ale chyba nie ma co się kłócić, to kwestia założeń, oba podejścia mają swoje wady i zalety.

katelx napisał(a):

z tym lamaniem DRY chodzi mi o to ze testujesz:
a) metody publiczne (ktore same uzywaja metod prywatnych)
b) metody prywatne (ktore zwykle uzywaja innych metod prywatnych)
czyli to co jest w b) jest (co najmniej) duplikacja testow z a)

Testy metod publicznych są zrobione na mockach (partial mocks). Nie ma łamania DRY, z wyjątkiem rzadko robionych testów integracyjnych, kiedy musimy przetestować obsługę zachowania po konfliktach transakcji na bazie danych.

2

Ciekawi mnie jak jesteś w stanie zasymulować np. wyjątek z bazy danych rzucany przez jedną z tych prywatnych metodek.

Wyjątek z bazy może być rzucony w warstwie dostępu do bazy, której nie musisz testować. Jeśli jest to pomieszane z logiką biznesową, to jest to prawdopodobnie jakiś błąd architektury.
Jak już chyba było napisane wyżej, jeżeli masz potrzebę testowania prywatnej metody osobno, najprawdopodobniej klasa łamie zasadę SR i metoda powinna być wyniesiona na zewnątrz i wtedy przetestowana. Nie musi być publiczna, wystarczy internal (to nie to samo co private!).

2
ŁF napisał(a):

U nas jest dużo metod wykorzystywanych przez wiele innych metod i testy jednostkowe na poziomie każdej metody doskonale zdają egzamin.

Takie metody umieszcza się w innych klasach, upublicznia i pisze zwykłe testy. To nie są wtedy metody prywatne.

Ciekawi mnie jak jesteś w stanie zasymulować np. wyjątek z bazy danych rzucany przez jedną z tych prywatnych metodek.

Np. mockując ORMa/repozytorium, z którego ta metoda korzysta.

Metody prywatne nie są żadnymi z wymienionych przez Ciebie. Kontrolery również. Ale chyba nie ma co się kłócić, to kwestia założeń, oba podejścia mają swoje wady i zalety.

Ale ja metody prywatne pokrywam testami - tyle, że nie bezpośrednio.
A kontrolery to jest jak najbardziej infrastruktura, nie logika.

Kiedyś brałem udział w projekcie, w którym testowane były kontrolery. Testy obejmowały wszystko - konstruktory, wywołania mockowanych metod, łącznie ze zliczaniem ich wywołań. Testy obejmowały nawet wywołania metod loggera. Napisanie ich zajmowało sporo czasu, nie wykryły praktycznie żadnych błędów, a aplikacja i tak potrafiła nie działać - bo przecież unit test kontrolera nie symuluje tego, co kontroler zazwyczaj robi, czyli wyświetlenia strony do przeglądarki. Moim zdaniem zmarnowano dużo energii na coś mało przydatnego.

Nie uważam tej dyskusji za kłótnię. Zawsze warto poznawać inne podejścia i poglądy, bo można w ten sposób się czegoś nauczyć.

0

Np. mockując ORMa/repozytorium, z którego ta metoda korzysta.

Nie możesz zrobić mocków na EF. Pisałem o tym wcześniej.
Co do kłócenia się - dla mnie ta dyskusja to sposób na konfrontację własnych pomysłów z tym, co robią inni. Kłóceniem nazwałem sztywne obstawanie przy swoim zdaniu niezależnie od okoliczności (np. testowanie kontrolerów - nie testujesz, bo nie masz czego, ja testuję, bo mam tam więcej kodu niż jedynie wywołanie serwisu).

@Hrypa - wspaniale, że uzmysławiasz mi różnicę pomiędzy private, public a internal ;-) Robienie własnej customowej warstwy często jest zbyt pracochłonne, z kolei nie każdy ORM da się zamockować. I stąd było moje pytanie, ale chyba niewystarczająco jasno je sformułowałem. Jak by brzmiała Twoja odpowiedź w świecie rzeczywistym, gdzie masz EF, względnie skomplikowane zapytania LINQ/Extension Methods i chcesz zasymulować wyjątek lecący z bazy? Ja robię mocka na małej metodzie internal np. wrzucającej dane do bazy. A Ty? Wydzielasz do tego osobną klasę i robisz tę metodkę publiczną?

0
ŁF napisał(a):

Nie możesz zrobić mocków na EF. Pisałem o tym wcześniej.

Pisałem o ORM, nie zabawce od MS. :)
Tak czy siak - EF trzeba opakowywać w repozytoria czy inne obiekty dostępu do danych, a te już zamockować można.

Co do kłócenia się - dla mnie ta dyskusja to sposób na konfrontację własnych pomysłów z tym, co robią inni. Kłóceniem nazwałem sztywne obstawanie przy swoim zdaniu niezależnie od okoliczności (np. testowanie kontrolerów - nie testujesz, bo nie masz czego, ja testuję, bo mam tam więcej kodu niż jedynie wywołanie serwisu).

Ale ja nie krytykuję Twojego podejścia, nie piszę, że robisz źle. Jedynie mówię, że ja tak nie robię i dlaczego tak nie robię.

0

Liczyłem że kłótnia pociągnie dłużej, jest to chyba najlepsza metoda w której można zweryfikować różne poglądy - oraz które z nich najbardziej nam odpowiadają:) Osobiście testuje wszystko co jest publiczne, nie licząc setterów i konstruktorów których jedynym zadaniem jest inicjalizacja pól. Dla mnie testowanie metod prywatnych jest łamaniem OOP i nie powinno mieć miejsca. Metody prywane służą wyłącznie DRY oraz Clean Code. Samo testowanie kontrolerów w .NET MVC jest zwyczajnie upierdliwe ze względu na ilość pól które należy zmockować aby współpracowały z np. takim AutoFixture, dlatego zdarze mi się nieco łamać zasady TDD w tym wypadku. Ogólnie średnio mi leży rendowanie widoków po stronie serwera, wolę korzystać z WebApi oraz jakiegoś framaworka JavaScript którego łatwo mogę przetestować, np. AngularJs.

Może uda się nieco pociągnąc dyskusję pytaniem: W jaki sposób łatwo przetestować metodę, która inicjalizuje obiekty zewnętrznych klas w swoim ciele, wywołuje ich metody, jest typu void oraz nie ma możliwości przeprowadzić testów integracyjnych (załóżmy że wyniki są zapisywane w bd)? W przypadku dużych aplikacji - trylion fabrych czy dwa interfejsów?

public class Foo
{
private Bar _bar;
public Foo(Repo repo)
{
_repo = repo;
}

public void Baz(string id, (...)
{
var quix = new Quix(id, (...));
quix.Biz(...);
_repo.Create(quix);
}
}
 

1 użytkowników online, w tym zalogowanych: 0, gości: 1