To jest kod:
package com.XXX;
public final class Foo {
private Foo() {
// intentionally empty
}
public static int bar() {
return 1;
}
}
To jest test:
package com.XXX;
public FooTest {
@Test
void testValidatesThatBarWorks() {
int result = Foo.bar();
assertEquals(1, result);
}
@Test(expected = java.lang.IllegalAccessException.class)
void testValidatesThatClassFooIsNotInstantiable() {
Class cls = Class.forName("com.XXX.Foo");
cls.newInstance(); // exception here
}
}
Działa dobrze, klasa jest przetestowana. Ale Cobertura twierdzi, że prywatny konstruktor klasy ma zerowe pokrycie kodem. Jak możemy dodać pokrycie testów do takiego prywatnego konstruktora?
java
testing
code-coverage
yegor256
źródło
źródło
Odpowiedzi:
Cóż, są sposoby, aby potencjalnie wykorzystać refleksję itp. - ale czy naprawdę warto? To jest konstruktor, którego nigdy nie należy wywoływać , prawda?
Jeśli istnieje adnotacja lub coś podobnego, które możesz dodać do klasy, aby Cobertura zrozumiał, że nie zostanie wywołana, zrób to: nie sądzę, aby warto było przechodzić przez obręcze, aby sztucznie dodawać pokrycie.
EDYCJA: Jeśli nie ma sposobu, aby to zrobić, po prostu żyj z nieco zmniejszonym zasięgiem. Pamiętaj, że zasięg ma być dla Ciebie przydatny - to ty powinieneś zarządzać narzędziem, a nie odwrotnie.
źródło
Nie do końca zgadzam się z Jonem Skeetem. Myślę, że jeśli możesz łatwo wygrać, aby zapewnić pokrycie i wyeliminować szum w raporcie pokrycia, powinieneś to zrobić. Albo powiedz swojemu narzędziu pokrycia, aby zignorowało konstruktora, lub odłóż na bok idealizm i napisz następujący test i zakończ z nim:
źródło
constructor
? Nie powinnoConstructor
być parametryzowane i nie powinno być typu surowego?constructor.isAccessible()
zawsze zwraca false, nawet w publicznym konstruktorze. Należy użyćassertTrue(Modifier.isPrivate(constructor.getModifiers()));
.Chociaż niekoniecznie jest to dla pokrycia, stworzyłem tę metodę, aby sprawdzić, czy klasa narzędzi jest dobrze zdefiniowana, a także zrobić trochę pokrycia.
Umieściłem pełny kod i przykłady w https://github.com/trajano/maven-jee6/tree/master/maven-jee6-test
źródło
Modifier.isPrivate
ponieważ w niektórych przypadkach wracałem do prywatnych konstruktorów (mockowanie zakłóceń biblioteki?).isAccessible
true
Assert.utilityClassWellDefined()
w JUnit 4.12+. Czy rozważałeś żądanie ściągnięcia?setAccessible()
do udostępnienia konstruktora powoduje problemy dla narzędzia pokrycia kodu Sonara (kiedy to zrobię, klasa znika z raportów pokrycia kodu Sonara).Uczyniłem konstruktora mojej klasy statycznych funkcji narzędziowych jako prywatny, aby spełnić wymagania CheckStyle. Ale podobnie jak w przypadku oryginalnego plakatu, Cobertura narzekała na test. Na początku wypróbowałem to podejście, ale nie ma to wpływu na raport pokrycia, ponieważ konstruktor nigdy nie jest wykonywany. Tak naprawdę wszystkie te testy dotyczą sytuacji, w których konstruktor pozostaje prywatny - i staje się to zbędne przez sprawdzenie dostępności w kolejnym teście.
Poszedłem z sugestią Javida Jamae i zastosowałem refleksję, ale dodałem asercje, aby złapać każdego, kto majstrował przy testowanej klasie (i nazwałam test, aby wskazać wysoki poziom zła).
To przesada, ale muszę przyznać, że podoba mi się ciepłe, rozmyte uczucie 100% pokrycia metodą.
źródło
fail(...)
nie jest konieczna.Dzięki Java 8 można znaleźć inne rozwiązanie.
Zakładam, że chcesz po prostu utworzyć klasę użytkową za pomocą kilku publicznych metod statycznych. Jeśli możesz użyć Java 8, możesz
interface
zamiast tego użyć .Nie ma konstruktora i nie ma narzekań ze strony Cobertury. Teraz musisz przetestować tylko te linie, na których naprawdę Ci zależy.
źródło
Powodem testowania kodu, który nic nie robi, jest osiągnięcie 100% pokrycia kodu i zauważenie, kiedy spada pokrycie kodu. W przeciwnym razie zawsze można by pomyśleć, hej, nie mam już 100% pokrycia kodu, ale PRAWDOPODOBNIE jest to spowodowane moimi prywatnymi konstruktorami. Ułatwia to wykrycie niesprawdzonych metod bez konieczności sprawdzania, czy był to tylko prywatny konstruktor. Wraz ze wzrostem bazy kodu poczujesz przyjemne, ciepłe uczucie patrząc na 100% zamiast 99%.
IMO najlepiej użyć tutaj odbicia, ponieważ w przeciwnym razie musiałbyś albo uzyskać lepsze narzędzie do pokrycia kodu, które ignoruje te konstruktory, albo w jakiś sposób powiedzieć narzędziu do pokrycia kodu, aby zignorowało metodę (może to być adnotacja lub plik konfiguracyjny), ponieważ wtedy utkniesz za pomocą specjalnego narzędzia pokrycia kodu.
W idealnym świecie wszystkie narzędzia do pokrycia kodu ignorowałyby prywatne konstruktory należące do ostatniej klasy, ponieważ konstruktor jest tam jako środek "bezpieczeństwa", nic innego :)
A potem po prostu dodaj klasy do tablicy w trakcie.Użyłbym tego kodu:
źródło
Nowsze wersje Cobertury mają wbudowaną obsługę ignorowania trywialnych programów pobierających / ustawiających / konstruktorów:
https://github.com/cobertura/cobertura/wiki/Ant-Task-Reference#ignore-trivial
Ignoruj Trivial
Ignoruj trywialne umożliwia wykluczenie konstruktorów / metod, które zawierają jedną linię kodu. Niektóre przykłady obejmują wywołanie tylko superkonstruktora, metody pobierające / ustawiające itp. Aby dołączyć argument ignorowania trywialny, dodaj następujące elementy:
lub w kompilacji Gradle:
źródło
Nie. Jaki jest sens testowania pustego konstruktora? Ponieważ cobertura 2.0 istnieje możliwość zignorowania takich trywialnych przypadków (razem z ustawiającymi / pobierającymi), możesz włączyć ją w maven, dodając sekcję konfiguracyjną do wtyczki cobertura maven:
Alternatywnie można użyć Coverage adnotacji :
@CoverageIgnore
.źródło
Wreszcie jest rozwiązanie!
źródło
Enum<E>
się naprawdę na wyliczenie ... Uważam, że lepiej ujawnia zamiar.Nie wiem o Coberturze, ale używam Clover i umożliwia ona dodawanie wykluczeń dopasowywania wzorców. Na przykład mam wzorce, które wykluczają wiersze logowania apache-commons, więc nie są uwzględniane w pokryciu.
źródło
Inną opcją jest utworzenie statycznego inicjatora podobnego do poniższego kodu
W ten sposób prywatny konstruktor jest uważany za przetestowany, a narzut czasu wykonania jest w zasadzie nie do zmierzenia. Robię to, aby uzyskać 100% pokrycie przy użyciu EclEmma, ale prawdopodobnie działa to dla każdego narzędzia pokrycia. Wadą tego rozwiązania jest oczywiście to, że piszesz kod produkcyjny (statyczny inicjalizator) tylko do celów testowych.
źródło
ClassUnderTest testClass = Whitebox.invokeConstructor (ClassUnderTest.class);
źródło
Czasami Cobertura oznacza kod, który nie jest przeznaczony do wykonania, jako „nieobjęty”, nie ma w tym nic złego. Dlaczego martwisz się posiadaniem
99%
ubezpieczenia zamiast100%
?Technicznie rzecz biorąc, nadal możesz wywołać tego konstruktora z refleksją, ale dla mnie brzmi to bardzo źle (w tym przypadku).
źródło
Gdybym miał odgadnąć cel twojego pytania, powiedziałbym:
W przypadku 1 jest oczywiste, że chcesz, aby cała inicjalizacja była wykonywana metodami fabrycznymi. W takich przypadkach twoje testy powinny być w stanie przetestować skutki uboczne konstruktora. Powinno to należeć do kategorii zwykłych prywatnych metod testowania. Zmniejsz metody, tak aby wykonywały tylko ograniczoną liczbę określonych rzeczy (najlepiej tylko jedną rzecz i dobrze jedną rzecz), a następnie przetestuj metody, które na nich polegają.
Na przykład, jeśli mój [prywatny] konstruktor ustawia pola instancji mojej klasy
a
na5
. Wtedy mogę (a raczej muszę) to przetestować:W przypadku wersji 2 możesz skonfigurować clover, aby wykluczał konstruktory Util, jeśli masz ustawiony wzorzec nazewnictwa dla klas Util. Np. W moim własnym projekcie używam czegoś takiego (ponieważ przestrzegamy konwencji, że nazwy wszystkich klas Util powinny kończyć się na Util):
Celowo pominąłem
.*
następujące,)
ponieważ takie konstruktory nie są przeznaczone do rzucania wyjątków (nie mają nic robić).Oczywiście może istnieć trzeci przypadek, w którym możesz chcieć mieć pusty konstruktor dla klasy nieużytkowej. W takich przypadkach radziłbym umieścić
methodContext
dokładny podpis konstruktora.Jeśli masz wiele takich wyjątkowych klas, możesz zmodyfikować uogólniony prywatny konstruktor reg-ex, który zasugerowałem, i usunąć
Util
go. W takim przypadku będziesz musiał ręcznie upewnić się, że efekty uboczne twojego konstruktora są nadal testowane i objęte innymi metodami w twojej klasie / projekcie.źródło
Test.java to plik źródłowy, który zawiera Twój prywatny konstruktor
źródło
Poniższe działały dla mnie na klasie utworzonej z adnotacją Lombok @UtilityClass, która automatycznie dodaje prywatnego konstruktora.
Chociaż constructor.setAccessible (true) powinno działać, gdy prywatny konstruktor został napisany ręcznie, z adnotacją Lombok nie działa, ponieważ wymusza to. Constructor.newInstance () faktycznie sprawdza, czy konstruktor jest wywoływany i to kończy pokrycie samego costructor. Dzięki assertThrows zapobiegasz niepowodzeniu testu i zarządzasz wyjątkiem, ponieważ jest to dokładnie oczekiwany błąd. Chociaż jest to obejście i nie doceniam koncepcji „pokrycia linii” w porównaniu z „pokryciem funkcjonalności / zachowania”, możemy znaleźć sens w tym teście. W rzeczywistości jesteś pewien, że Klasa Użytkowa ma w rzeczywistości prywatnego Konstruktora, który poprawnie zgłasza wyjątek, gdy jest wywoływany również przez reflaction. Mam nadzieję że to pomoże.
źródło
Moja preferowana opcja w 2019 roku: użyj lombok.
W szczególności
@UtilityClass
adnotacja . (Niestety w chwili pisania tego tekstu było to tylko „eksperymentalne”, ale działa dobrze i ma pozytywne perspektywy, więc prawdopodobnie wkrótce zostanie zaktualizowane do wersji stabilnej).Ta adnotacja doda prywatny konstruktor, aby zapobiec tworzeniu instancji i sprawi, że klasa będzie ostateczna. W połączeniu z
lombok.addLombokGeneratedAnnotation = true
inlombok.config
, prawie wszystkie platformy testowe zignorują automatycznie wygenerowany kod podczas obliczania pokrycia testu, umożliwiając ominięcie pokrycia tego automatycznie wygenerowanego kodu bez hacków lub odbić.źródło
Nie możesz.
Najwyraźniej tworzysz konstruktora prywatnego, aby zapobiec tworzeniu instancji klasy, która ma zawierać tylko metody statyczne. Zamiast próbować uzyskać pokrycie tego konstruktora (co wymagałoby utworzenia instancji klasy), powinieneś się go pozbyć i ufać programistom, że nie dodają metod instancji do klasy.
źródło