Czy mierzenie pokrycia UT ma sens

0

W korpo, w którym robię, pojawiło się zarządzenie CTO spływające na wszystkie projekty, żeby uzyskać pokrycie 90% UT.
Moje podejście do UT było dość pragmatyczne - trzeba je pisać tam gdzie jest sens i pisanie ich w sposób dający korzyści, czyli skupienie się na pokrywaniu tego co warto pokryć i zorientowaniu na jakość testów, zamiast na ich ilość i mityczne pokrycie. Z drugiej strony CTO nie jest kretynem i chociaż w tym przypadku pomysł odbieram jako delikatnie rzecz ujmując, kontrowersyjny, to może jest to tylko mój brak doświadczenia/wiedzy. Mam w związku z tym parę pytań:

  • Czy taki wymóg może mieć jakikolwiek sens i jaki?
  • W jaki sposób wymóc systematyczne zwiększanie pokrycia - zastanawiam się nad dodaniem dodatkowych reguł na Sonarze wywalających buildy, jeżeli pokrycie spada, lub pokrycie dla nowego kodu jest poniżej wymaganej wartości.
  • W jaki sposób zapewnić jakość unit testów - jeżeli tylko się da, wolę uniknąć z jednej strony pisania testów na metodach dostępowych, testowania prawidłowej konfiguracji mocków, co w moim przekonaniu prowadzi do stanu, w którym niby jest pokrycie, ale faktycznie testy nie dają nic, czy wręcz powodują spadek jakości, bo nawet w miejscach, gdzie są niezbędne zostaną z przyzwyczajenia zrobione na odwal.
  • Czy jest możliwe zapewnienie takiego pokrycia w aplikacjach technologicznie zapóźnionych np. JavaFX, JSF.
2

Jeśli jakaś miara zaczyna być celem samym w sobie, np. celuje się w „90% pokrycia UT”, to szybko przestanie mieć ona znaczenie opisowe — bo ludzie będą celowo starali się tworzyć pod te wymagania, obchodząc ideę, która za nimi stoi.

Nie ma absolutnie żadnego problemu, żeby stworzyć kompletnie nieprzetestowany kod z bardzo wysokim stopniem pokrycia (np. testowanie mocków w durny sposób). Podobnie jak nie ma problemu z tworzeniem nonsensownych linijek gdy szefostwo chce wiele linii kodu czy mnóstwa niepotrzebnych funkcji, gdy płacą od funkcji, itd.

Tym niemniej, duże pokrycie testami w przeważającej większości przypadków będzie nieco pozytywne — bo zawsze coś-tam wyłapią, jak nie będą pisane celowo bezużyteczne, a aktywnie szkodliwe być raczej nie są w stanie… No chyba, że ktoś im zawierzy za bardzo („testy jednostkowe nic nie wykazały, więc na pewno kod jest doskonały!”) — ale to byłoby problemem także przy małym pokryciu.

A co do praktyki: tutaj za wiele doświadczenia z różnymi podejściami niestety nie mam (ot, robię jak kazali i tyle; nigdy nie sprawdzałem, co by się stało, gdybyśmy robili inaczej). Reguła na CI brzmi rozsądnie jako punkt wyjścia, jak nam naprawdę zależy na jakości, można rozważyć testy mutacyjne — jeśli jesteśmy w stanie przeżyć to, że są wooooolne.

2
  1. tak. Na przykład w projekcie który ma 88% pokrycia kodu. A poważnie - zależy od stanu aktualnego projektu.
  2. Tak jak napisałeś - wystarczy wymagać żeby pokrycie nie spadało.
  3. Code review
  4. Tak. Patrz:
    https://jsfunit.jboss.org/
    https://www.primefaces.org/easy-unit-testing-jsf-backing-beans/
    https://codenotfound.com/jsf-primefaces-automated-unit-testing-selenium.html
5

Z pokryciem testami jednostkowymi trzeba uważnie. Jak się uprzesz to możesz mieć 100% pokrycia i nie działający kod, np:

class Calculator {
  type T = Int

  def add(a: T, b: T): T = a * b

  def mul(a: T, b: T): T = a + b

  def leq(a: T, b: T): Boolean = a < b

}

I złe testy do tego:

object CalculatorTest extends TestSuite {
  override val tests: Tests = Tests {
    val calculator = new Calculator()
    'addition - {
      val addition: (Int, Int) => Int = (x, y) => calculator.add(x, y)
      "0 + 0 == 0" - {
        assert(addition(0, 0) == 0)
      }
      "2 + 2 == 4" - {
        assert(addition(2, 2) == 4)
      }
    }
    'multiplication - {
      val multiplication: (Int, Int) => Int = (x, y) => calculator.mul(x, y)
      "0 + 0 == 0" - {
        assert(multiplication(0, 0) == 0)
      }
      "2 + 2 == 4" - {
        assert(multiplication(2, 2) == 4)
      }
    }
    'less_or_equal - {
      val less_or_equal: (Int, Int) => Boolean = (x, y) => calculator.leq(x, y)
      "0 <= 2 == true" - {
        assert(less_or_equal(0, 2))
      }
      "2 <= 0 == false" - {
        assert(!less_or_equal(2, 0))
      }
    }
  }
}

Źródło: Dynamiczna analiza kodu dla SBT - testy jednostkowe

0

Wymaganie pokrycia testami jednostkowymi uważam za szkodliwe. Co innego pokrycie przez testy funkcjonalne, ale tej metryki zazwyczaj nikt nie mierzy

0
slsy napisał(a):

Wymaganie pokrycia testami jednostkowymi uważam za szkodliwe. Co innego pokrycie przez testy funkcjonalne, ale tej metryki zazwyczaj nikt nie mierzy

Nie do końca się zgadzam, testy jednostkowe taż są przydatne, jak każde inne. O ile są pisane by testować, coś a nie "dla pokrycia".

3

Pokrycie warto mierzyć i sprawdzać przy review (np. żeby widzieć jakie linie nie są pokryte).

Biurokratyczny wymóg pokrycia X% zwykle szybciutko prowadzi to psucia testów - łatwo mieć pokrycie 100% jeśli nie będziesz robił asercji :-)
Ewentualnie jeśli ktoś sprawdza asercje to łatwo wszystko zamokować, byłem w takich projektach, że mockowanie było tak doskonałe, że nawet wywalenie połowy kodu nie psuło testów.

Ze wzgledów technicznych (jacoco, build) nawet w projektach gdzie wiem, że mam prawie 100% często mierzone pokrycie wynosi np. 80%.
Np. multimodule projekty w kotlinie często dają mi zaniżone, da się to poprawić ostro hakując build, ale mi się nie chce tego robić dla samych statów.

JSF, JavaFx IMO nie warto. (ale patrz niżej: nie pogarszać).
Uważam, że selenium jest przeważnie nie warte swojej ceny (kosztów pisania i utrzymania testów), ale to zależy od aplikacji (liczba użytkowników, krytycznosć).
Natomiast warto testować JS/TS jeśli taki masz (częsty błąd w projektach odjavowych to testowanie tylko javy nawet jeśli sporo logiki i kodu jest w JS).

Robiłem czasem coś takiego jak pokazywanie trendów pokrycia, spada, wzrasta - nawet specjalnie dashboardem do tego. Jak widziałeś po push, że jest tąpnięcie ... to czasem łapało się motywację (dodałem do tego jeszcze issues na sonarze - pogarszałeś - ryp projekt na czerwono). Generalnie(zwłaszcza w legacy projektach) nie liczy się tak bardzo czy masz idealne, numerkowe staty - ważne, żeby nie pogarszać.

Co do jakości testów - trzeba robić okresowo warsztaty i ćwiczyć. Nie każdy musi lubić i chcieć używać TDD (czyli nawet więcej), ale każdy w projekcie musi umieć.
Testowanie powinno być możliwe i proste dla (prawie) wszystkich warstw projektu.
Jak słyszysz, że czegoś się nie da testować ... to trzeba się pochylić, zrobić przykłąd, że się da i zaprezentować jak.

1

Moim zdaniem taki wymóg nie bardzo ma jakiś sens i skończy się pisaniem unit testów dla getterów i setterów, albo pisanie testów bez asercji, byleby tylko dotknąć każdej linijki kodu i dobić do wymaganego %.

Testy które mają sens to pokrycie przypadków użycia / funkcji systemu / testy akceptacyjne. Takie testy zresztą automatycznie powinny pokryć nam 100% kodu, bo jeśli przetestowaliśmy wszystkie przypadki użycia (także negatywne ścieżki), a jakaś linijka kodu nigdy nie została dotknięta, to nasuwa się pytanie co to za kod i czemu tu jest.

2
piotrpo napisał(a):
  • Czy taki wymóg może mieć jakikolwiek sens i jaki?

Wywołanie orgazmu u pomysłodawcy.

  • W jaki sposób zapewnić jakość unit testów

Zmienić pracę, bo skoro w tej idziecie w ilość, to jakości już nie będzie.

1

Rzeczywista sytuacja u nas, to legacy code, który po przejęciu pokryliśmy w teoretycznych 100% :D automatycznymi testami funkcjonalnymi, czy to od strony UI, czy testami API, żeby mieć chociaż odrobinę spokoju z refaktoringiem. System może nawet nie jest jakiś gigantyczny, ale ma masę różnych komponentów - backend (trochę ~µS, trochę monolit), frontend, backend z frontendem, aplikacje desktopowe, mobilne, wszystko działające w oparciu o enterprise cloud z wykorzystaniem w coraz większym stopniu usług zarządzalnych. Mamy więc do pokrycia trochę kodu ~1M linii tak na oko.
Generalnie zdaję sobie sprawę w jakiej sytuacji nas to stawia i jakie są problemy wynikają z podejścia godnego 16-latka "pokryć wszystko co się rusza". Próbuję po prostu znaleźć sposób na uniknięcie dość oczywistej z mojego punktu widzenia sytuacji, kiedy będziemy mieli od groma testów, które nic nie testują, a wynikiem jest zielono w excelu i kod, który jest pokryty tak, że nawet nie wiadomo co jest, a co nie jest przetestowane, bo już żadne narzędzie tego nie pokaże.

0

Nie, zwłaszcza że UT mają mało kiedy sens i lepsze są integracyjne.
Mamy 21 wiek, Test containers, architecture rozproszona.

1

Moje podejście:

  • UT jak najbardziej tak, ale raczej w formie property testingu, a nie "sztywnych wartości"
  • coverage nie musi mieć jakiegoś poziomu (czasem jest to trudne lub nie warte zachodu by każdą linijkę pokrywać testami), ale pilnuję by nie spadało za bardzo i raczej ma rosnąć niż spadać (ale nie zawsze się da to osiągnąć)
  • najlepiej by linijki zmienione w commicie miały 100% pokrycia (to jest coś innego od pokrycia całego projektu)
1
piotrpo napisał(a):

Generalnie zdaję sobie sprawę w jakiej sytuacji nas to stawia i jakie są problemy wynikają z podejścia godnego 16-latka "pokryć wszystko co się rusza". Próbuję po prostu znaleźć sposób na uniknięcie dość oczywistej z mojego punktu widzenia sytuacji, kiedy będziemy mieli od groma testów, które nic nie testują, a wynikiem jest zielono w excelu i kod, który jest pokryty tak, że nawet nie wiadomo co jest, a co nie jest przetestowane, bo już żadne narzędzie tego nie pokaże.

Sposób znam jeden - porozmawiać z kimś, kto za to płaci, że dopisywanie bezsensownych testów to koszt, który nie daje żadnych zysków, i czy mu pasuje, żeby np. wstrzymać development na rok, żeby napisać rzekomo brakujące testy.

1

Szczytna idea pisania testów rozbija się o umiejętności programistów, którzy tych testów pisać nie umieją* albo robią to w sposób słaby. Wtedy robienie testów pomaga tylko do jakiegoś poziomu, ale jak jest ich więcej, to rodzą się problemy w stylu "żeby cokolwiek zmienić, trzeba przepisać testy" (co sygnalizuje źle napisane testy, które testują szczegóły implementacyjne), poza tym testy też trzeba utrzymywać jakoś potem, organizować itp.

*co nie musi świadczyć, że programiści są słabi. Pewne rzeczy są trudne a czasem niemożliwe w otestowaniu. A czasem nawet jeśli są możliwe, to testowanie czegoś jest dość bezsensowne. Myślę, że praktyczne podejście do testowania to umiejętność rozeznania, czego nie testować. Moim zdaniem bardziej otestowane powinny być rzeczy, na których się opieramy i na których chcemy polegać w przyszłości (np. idea pokrycia w 90-100% popularnej biblioteki open source jest słuszna, ale już - czy aplikacja, która korzysta z tej biblioteki powinna mieć tak samo duże pokrycie testami? No i czy każda część większej aplikacji wymaga tak samo dokładnego testowania? (być może pewne części większej aplikacji wymagałyby tego 90-100%, a inne niekoniecznie)

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