Moja domena składa się z wielu prostych niezmiennych klas takich jak to:
public class Person
{
public string FullName { get; }
public string NameAtBirth { get; }
public string TaxId { get; }
public PhoneNumber PhoneNumber { get; }
public Address Address { get; }
public Person(
string fullName,
string nameAtBirth,
string taxId,
PhoneNumber phoneNumber,
Address address)
{
if (fullName == null)
throw new ArgumentNullException(nameof(fullName));
if (nameAtBirth == null)
throw new ArgumentNullException(nameof(nameAtBirth));
if (taxId == null)
throw new ArgumentNullException(nameof(taxId));
if (phoneNumber == null)
throw new ArgumentNullException(nameof(phoneNumber));
if (address == null)
throw new ArgumentNullException(nameof(address));
FullName = fullName;
NameAtBirth = nameAtBirth;
TaxId = taxId;
PhoneNumber = phoneNumber;
Address = address;
}
}
Pisanie kontroli zerowej i inicjowanie właściwości jest już bardzo uciążliwe, ale obecnie piszę testy jednostkowe dla każdej z tych klas, aby sprawdzić, czy sprawdzanie poprawności argumentów działa poprawnie i czy wszystkie właściwości zostały zainicjowane. Wydaje się, że praca jest wyjątkowo nudna i przynosi niewspółmierne korzyści.
Prawdziwym rozwiązaniem byłoby, aby język C # obsługiwał niezmienność i typy zerowalne. Ale co mogę w międzyczasie poprawić? Czy warto pisać wszystkie te testy? Czy dobrym pomysłem byłoby napisanie generatora kodu dla takich klas, aby uniknąć pisania testów dla każdej z nich?
Oto, co mam teraz na podstawie odpowiedzi.
Mogę uprościć sprawdzanie wartości zerowej i inicjowanie właściwości, aby wyglądały następująco:
FullName = fullName.ThrowIfNull(nameof(fullName));
NameAtBirth = nameAtBirth.ThrowIfNull(nameof(nameAtBirth));
TaxId = taxId.ThrowIfNull(nameof(taxId));
PhoneNumber = phoneNumber.ThrowIfNull(nameof(phoneNumber));
Address = address.ThrowIfNull(nameof(address));
Za pomocą następującej implementacji Roberta Harveya :
public static class ArgumentValidationExtensions
{
public static T ThrowIfNull<T>(this T o, string paramName) where T : class
{
if (o == null)
throw new ArgumentNullException(paramName);
return o;
}
}
Testowanie czeków zerowych jest łatwe przy użyciu narzędzia GuardClauseAssertion
from AutoFixture.Idioms
(dzięki za sugestię, Esben Skov Pedersen ):
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var assertion = new GuardClauseAssertion(fixture);
assertion.Verify(typeof(Address).GetConstructors());
Można to jeszcze bardziej skompresować:
typeof(Address).ShouldNotAcceptNullConstructorArguments();
Za pomocą tej metody rozszerzenia:
public static void ShouldNotAcceptNullConstructorArguments(this Type type)
{
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var assertion = new GuardClauseAssertion(fixture);
assertion.Verify(type.GetConstructors());
}
źródło
Odpowiedzi:
Stworzyłem szablon t4 właśnie dla tego rodzaju przypadków. Aby uniknąć pisania dużej liczby płyt dla klas Niezmiennych.
https://github.com/xaviergonz/T4Immutable T4Immutable to szablon T4 dla aplikacji C # .NET, który generuje kod dla klas niezmiennych.
Mówiąc konkretnie o testach niepustych, jeśli użyjesz tego:
Konstruktor będzie taki:
Powiedziawszy to, jeśli używasz Adnotacji JetBrains do sprawdzania wartości NULL, możesz również to zrobić:
Konstruktor będzie taki:
Jest też kilka innych funkcji niż ta.
źródło
Możesz uzyskać trochę poprawy dzięki prostemu refaktoryzacji, która może złagodzić problem pisania wszystkich tych ogrodzeń. Najpierw potrzebujesz tej metody rozszerzenia:
Następnie możesz napisać:
Zwrócenie oryginalnego parametru w metodzie rozszerzenia tworzy płynny interfejs, więc możesz rozszerzyć tę koncepcję o inne metody rozszerzenia, jeśli chcesz, i połączyć je wszystkie razem w zadaniu.
Inne techniki są bardziej eleganckie w koncepcji, ale stopniowo coraz bardziej skomplikowane w wykonaniu, takie jak dekorowanie parametru
[NotNull]
atrybutem i używanie tego typu Refleksji .To powiedziawszy, możesz nie potrzebować wszystkich tych testów, chyba że twoja klasa jest częścią publicznego API.
źródło
null
argument dla konstruktora, nie otrzymasz testu zakończonego niepowodzeniem.throw
zewnętrznej części konstruktora; tak naprawdę nie przenosisz kontroli w konstruktorze gdzieś indziej. To tylko użyteczna metoda i sposób płynnego robienia rzeczy , jeśli tak zdecydujesz.W krótkim okresie nie można wiele zrobić w kwestii żmudności pisania takich testów. Istnieje jednak pewna pomoc związana z wyrażeniami rzutowania, które zostaną zaimplementowane w ramach następnej wersji C # (v7), prawdopodobnie w ciągu najbliższych kilku miesięcy:
Możesz eksperymentować z wyrażeniami rzutowania za pośrednictwem aplikacji internetowej Try Roslyn .
źródło
Dziwi mnie, że nikt jeszcze nie wspomniał o NullGuard.Fody . Jest dostępny za pośrednictwem NuGet i automatycznie wplata te kontrole zerowe w IL podczas kompilacji.
Więc twój kod konstruktora byłby po prostu
a NullGuard doda te kontrole zerowe za przekształcenie go w dokładnie to, co napisałeś.
Zauważ jednak, że NullGuard jest opt-out , to znaczy doda te kontrole zerowe do każdej metody i argumentu konstruktora, gettera właściwości i ustawiacza, a nawet zwróci wartości metod, chyba że wyraźnie zezwolisz na wartość zerową z tym
[AllowNull]
atrybutem.źródło
[NotNull]
atrybut zamiast nie pozwalając, aby wartość domyślna to null.Nie, prawdopodobnie nie. Jakie jest prawdopodobieństwo, że to spieprzysz? Jakie jest prawdopodobieństwo, że niektóre semantyki zmienią się spod ciebie? Jaki wpływ ma ktoś, kto to spieprzy?
Jeśli spędzasz dużo czasu na testowaniu czegoś, co rzadko się psuje, i jest to trywialna poprawka, jeśli to zrobiło ... może nie było tego warte.
Może? Tego rodzaju rzeczy można łatwo zrobić za pomocą refleksji. Należy wziąć pod uwagę generowanie kodu dla prawdziwego kodu, więc nie masz N klas, które mogą zawierać błąd ludzki. Zapobieganie błędom> wykrywanie błędów.
źródło
if...throw
, co mająThrowHelper.ArgumentNull(myArg, "myArg")
, w ten sposób logika jest nadal obecna i nie drażni Cię zasięg kodu. GdzieArgumentNull
metoda zrobiif...throw
.Czy warto pisać wszystkie te testy?
Nie.
Ponieważ jestem pewien, że przetestowałeś te właściwości za pomocą innych testów logiki, w których używane są te klasy.
Na przykład możesz mieć testy dla klasy Factory, które mają asercje oparte na tych właściwościach (
Name
na przykład wystąpienie utworzone z odpowiednio przypisaną właściwością).Jeśli te klasy zostaną udostępnione publicznemu interfejsowi API, który jest używany przez jakiegoś trzeciego użytkownika / użytkownika końcowego (@EJoshua dziękuję za uwagę), testy na oczekiwane
ArgumentNullException
mogą być przydatne.Podczas oczekiwania na C # 7 możesz użyć metody rozszerzenia
Do testowania można użyć jednej sparametryzowanej metody testowej, która używa odbicia do utworzenia
null
odniesienia dla każdego parametru i potwierdzenia oczekiwanego wyjątku.źródło
Zawsze możesz napisać następującą metodę:
Przynajmniej zaoszczędzi Ci to trochę pisania, jeśli masz kilka metod, które wymagają tego rodzaju sprawdzania poprawności. Oczywiście to rozwiązanie zakłada, że żaden z parametrów twojej metody nie może być
null
, ale możesz to zmodyfikować, aby to zmienić, jeśli chcesz.Można to również rozszerzyć, aby przeprowadzić inną weryfikację specyficzną dla typu. Na przykład, jeśli masz regułę, że ciągi nie mogą być wyłącznie białymi znakami lub puste, możesz dodać następujący warunek:
Metoda GetParameterInfo, do której się odwołuję, w zasadzie robi odbicie (aby nie musiałem ciągle powtarzać tego samego, co byłoby naruszeniem zasady DRY ):
źródło
Nie sugeruję zgłaszania wyjątków od konstruktora. Problem polega na tym, że będziesz miał trudności z przetestowaniem tego, ponieważ musisz przekazać prawidłowe parametry, nawet jeśli nie mają one znaczenia dla twojego testu.
Na przykład: jeśli chcesz sprawdzić, czy trzeci parametr zgłasza wyjątek, musisz poprawnie podać pierwszy i drugi. Więc twój test nie jest już izolowany. gdy ograniczenia pierwszego i drugiego parametru zmienią się, ten przypadek testowy zawiedzie, nawet testuje trzeci parametr.
Proponuję użyć interfejsu API sprawdzania poprawności Java i zlecić proces sprawdzania poprawności na zewnątrz. Sugeruję zaangażowanie czterech obowiązków (zajęć):
Obiekt sugestii z parametrami sprawdzania poprawności języka Java opatrzonymi adnotacjami parametrami za pomocą metody sprawdzania poprawności, która zwraca zestaw ograniczeń ograniczeń. Jedną z zalet jest to, że można ominąć te obiekty bez potwierdzenia ważności. Możesz opóźnić sprawdzanie poprawności, dopóki nie będzie to konieczne, bez konieczności tworzenia instancji obiektu domeny. Obiekt sprawdzania poprawności może być używany na różnych warstwach, ponieważ jest to POJO bez wiedzy specyficznej dla poszczególnych warstw. Może być częścią publicznego API.
Fabryka obiektu domeny odpowiedzialna za tworzenie poprawnych obiektów. Przekazujesz obiekt sugestii, a fabryka wywoła sprawdzanie poprawności i utworzy obiekt domeny, jeśli wszystko będzie w porządku.
Sam obiekt domeny, który powinien być w większości niedostępny dla instancji innych programistów. Do celów testowych sugeruję, aby mieć tę klasę w zasięgu pakietu.
Interfejs domeny publicznej do ukrywania konkretnego obiektu domeny i utrudniania niewłaściwego użycia.
Nie sugeruję sprawdzania wartości pustych. Gdy tylko przejdziesz do warstwy domeny, pozbędziesz się przekazywania wartości null lub zwracania wartości null, o ile nie masz łańcuchów obiektów, które mają zakończenia lub funkcje wyszukiwania pojedynczych obiektów.
Jeszcze jedna kwestia: pisanie mniejszej ilości kodu nie jest miarą jakości kodu. Pomyśl o konkursach na 32 000 gier. Ten kod jest najbardziej kompaktowy, ale także najbardziej bałaganiarski, jaki możesz mieć, ponieważ dba on tylko o kwestie techniczne i nie dba o semantykę. Ale semantyka to punkty, które czynią wszystko kompleksowym.
źródło