Dlaczego testowanie języka nie jest obsługiwaną funkcją na poziomie składni?

37

Możesz znaleźć nieskończoną listę blogów, artykułów i stron internetowych promujących zalety jednostkowego testowania kodu źródłowego. Jest prawie pewne, że programiści, którzy zaprogramowali kompilatory dla Java, C ++, C # i innych języków pisanych, używali testów jednostkowych do weryfikacji swojej pracy.

Dlaczego więc pomimo popularności testowanie jest nieobecne w składni tych języków?

Microsoft wprowadził LINQ do C # , więc dlaczego nie mogliby również dodać testowania?

Nie chcę przewidzieć, jakie byłyby te zmiany językowe, ale wyjaśnić, dlaczego na początku ich nie ma.

Jako przykład: wiemy, że możesz napisać forpętlę bez składni forinstrukcji. Możesz użyć whilelub if/ gotoinstrukcji. Ktoś zdecydował, że forwypowiedź jest bardziej wydajna i wprowadził ją do języka.

Dlaczego testowanie nie następowało po tej samej ewolucji języków programowania?

Reactgular
źródło
24
Czy naprawdę lepiej jest uczynić specyfikację języka bardziej złożoną przez dodanie składni, w przeciwieństwie do projektowania języka z prostymi regułami, które można łatwo rozszerzyć w ogólny sposób?
KChaloux
6
Co rozumiesz przez „na poziomie składni”? Czy masz jakieś szczegóły / wyobrażenie o tym, w jaki sposób zostanie ono wdrożone?
SJuan76
21
Czy możesz podać pseudo-kodowy przykład testowania jako funkcji obsługiwanej przez składnię? Jestem ciekawy, jak myślisz, jak by to wyglądało.
FrustratedWithFormsDesigner
12
@FrustratedWithFormsDesigner Zobacz języka D dla przykładu.
Doval,
4
Innym językiem z wbudowanymi funkcjami testowania jednostek jest Rust.
Sebastian Redl,

Odpowiedzi:

36

Podobnie jak w przypadku wielu rzeczy, testowanie jednostkowe najlepiej jest obsługiwać na poziomie biblioteki, a nie języka. W szczególności C # ma wiele dostępnych bibliotek testów jednostkowych, a także rzeczy, które są natywne dla .NET Framework, takich jak Microsoft.VisualStudio.TestTools.UnitTesting.

Każda biblioteka testów jednostkowych ma nieco inną filozofię i składnię testowania. Wszystkie rzeczy są równe, więcej wyborów jest lepszych niż mniej. Gdyby testy jednostkowe zostały wprowadzone do języka, albo byłbyś zamknięty w opcjach projektanta języka, albo używałbyś ... biblioteki i całkowicie unikałbyś funkcji testowania języka.

Przykłady

  • Nunit - idiomatycznie zaprojektowany framework do testowania jednostek ogólnego przeznaczenia, który w pełni wykorzystuje funkcje języka C #.

  • Moq - Mocking framework, który w pełni wykorzystuje wyrażenia lambda i drzewa wyrażeń, bez metafory nagrywania / odtwarzania.

Istnieje wiele innych opcji. Biblioteki, takie jak Microsoft Fakes, mogą tworzyć symulatory „shims ...”, które nie wymagają pisania klas za pomocą interfejsów lub metod wirtualnych.

Linq nie jest funkcją językową (pomimo swojej nazwy)

Linq to funkcja biblioteki. Mamy wiele nowych funkcji w samym języku C # za darmo, takich jak wyrażenia lambda i metody rozszerzeń, ale rzeczywista implementacja Linq jest w .NET Framework.

Jest trochę cukru syntaktycznego, który został dodany do C #, aby uczynić instrukcje linq czystszymi, ale ten cukier nie jest wymagany do używania linq.

Robert Harvey
źródło
1
Zgadzam się z twierdzeniem, że LINQ nie jest funkcją językową, ale aby LINQ mógł się stać, musiały istnieć pewne podstawowe funkcje językowe - szczególnie ogólne i dynamiczne typy.
Wyatt Barnett
@WyattBarnett: Tak, to samo dotyczy łatwych testów jednostkowych - takich jak odbicie i atrybuty.
Doc Brown
8
LINQ potrzebował lambd i drzewek ekspresyjnych. Generics już były w C # (i .Net ogólnie, są obecne w CLR), a dynamika nie ma nic wspólnego z LINQ; możesz wykonać LINQ bez dynamiki.
Arthur van Leeuwen,
DBIx :: Class firmy Perl jest przykładem funkcjonalności podobnej do LINQ wdrożonej ponad 10 lat po tym, jak wszystkie funkcje potrzebne do jej implementacji są w języku.
slebetman
2
@ Carson63000 Myślę, że masz na myśli anonimowe typy w połączeniu ze varsłowem kluczowym :)
M.Mimpen
21

Jest wiele powodów. Eric Lippert wielokrotnie twierdził, że powodem tego feature Xnie jest C #, ponieważ po prostu nie ma tego w budżecie. Projektanci języków nie mają nieskończonej ilości czasu ani pieniędzy na wdrożenie różnych rozwiązań, a każda nowa funkcja wiąże się z kosztami utrzymania. Utrzymanie tak małego języka, jak to tylko możliwe, nie jest tylko łatwiejsze dla projektantów języków - jest także łatwiejsze dla każdego, kto pisze alternatywne implementacje i narzędzia (np. IDE). Dodatkowo, gdy coś jest implementowane pod względem języka, a nie jego części, otrzymujesz przenośność za darmo. Jeśli testowanie jednostkowe jest implementowane jako biblioteka, wystarczy napisać go tylko raz i będzie on działał w dowolnej zgodnej implementacji języka.

Warto zauważyć, że D ma obsługę testowania jednostkowego na poziomie składni . Nie wiem, dlaczego postanowili to wrzucić, ale warto zauważyć, że D ma być „językiem programowania systemów na wysokim poziomie”. Projektanci chcieli, aby był on wykonalny dla tego rodzaju niebezpiecznego, niskiego poziomu kodu, do którego tradycyjnie używano C ++, a błąd w niebezpiecznym kodzie jest niezwykle kosztowny - niezdefiniowane zachowanie. Sądzę więc, że mieli dla nich sens dodatkowy wysiłek na wszystko, co pomaga zweryfikować, czy jakiś niebezpieczny kod działa. Na przykład możesz wymusić, aby tylko niektóre zaufane moduły mogły wykonywać niebezpieczne operacje, takie jak niesprawdzony dostęp do tablicy lub arytmetyka wskaźnika.

Szybki rozwój był również dla nich priorytetem, tak bardzo, że uczyniono z niego cel projektowy, który kod D kompiluje wystarczająco szybko, aby był użyteczny jako język skryptowy. Pomaga w tym testowanie jednostek pieczenia bezpośrednio na język, dzięki czemu można uruchomić testy, przekazując kompilatorowi dodatkową flagę.

Myślę jednak, że świetna biblioteka do testowania jednostek nie tylko znajduje jakieś metody i je uruchamia. Weźmy na przykład QuickCheck Haskella , który pozwala przetestować takie rzeczy jak „dla wszystkich x i y f (x, y) == f (y, x)”. QuickCheck jest lepiej opisany jako generator testów jednostkowych i pozwala testować rzeczy na wyższym poziomie niż „dla tego wejścia, oczekuję tego wyjścia”. QuickCheck i Linq nie różnią się tak bardzo - oba są językami specyficznymi dla domeny. Zamiast więc dodawać obsługę testów jednostkowych do języka, dlaczego nie dodać funkcji niezbędnych do tego, aby DSL były praktyczne? Skończysz nie tylko testowaniem jednostkowym, ale także lepszym językiem.

Doval
źródło
3
Aby pozostać w świecie .Net, istnieje FsCheck, który jest portem QuickCheck do F #, z kilkoma pomocnikami do użycia z C #.
Arthur van Leeuwen,
Pozostać w świecie .NET? To pytanie nie ma nic wspólnego z .NET.
Miles Rout,
@MilesRout C # jest częścią .NET. Pytania i odpowiedzi często mówią o C #, a pytanie mówi także o LINQ.
Zachary Dow
Pytanie nie jest oznaczone .NET lub C #.
Miles Rout
@MilesRout To może być prawda, ale nie można rozsądnie stwierdzić, że nie ma to nic wspólnego z tym, że nie ma znacznika. :)
Zachary Dow
13

Ponieważ testowanie, a zwłaszcza rozwój oparty na testach, jest zjawiskiem głęboko sprzecznym z intuicją.

Prawie każdy programista zaczyna karierę, wierząc, że znacznie lepiej radzi sobie ze złożonością niż w rzeczywistości. Fakt, że nawet największy programista nie jest w stanie pisać dużych i złożonych programów bez poważnych błędów, chyba że użyje wielu testów regresji, jest poważnie rozczarowujący, a nawet wstydliwy dla wielu praktyków. Stąd powszechna niechęć do regularnych testów nawet wśród profesjonalistów, którzy powinni już wiedzieć lepiej.

Myślę, że fakt, że testy religijne powoli stają się coraz bardziej popularne i oczekiwane, wynika w dużej mierze z faktu, że wraz ze wzrostem pojemności pamięci i mocy obliczeniowej budowane są większe niż kiedykolwiek systemy - a wyjątkowo duże systemy są szczególnie podatne na zawiłości, nie można zarządzać bez siatki bezpieczeństwa testów regresyjnych. W rezultacie nawet szczególnie uparci i złudzeni programiści niechętnie przyznają, że potrzebują testowania i zawsze będą musieli testować (jeśli to brzmi jak spowiedź na spotkaniu AA, jest to całkiem celowe - potrzeba sieci bezpieczeństwa jest psychologicznie trudna do zaakceptowania dla wielu osoby).

Większość popularnych obecnie języków pochodzi z wcześniejszej zmiany nastawienia, więc nie mają one wbudowanej obsługi testów: mają assert, ale nie mają umów. Jestem całkiem pewien, że jeśli trend się utrzyma, przyszłe języki będą miały większe wsparcie w języku, a nie w bibliotece.

Kilian Foth
źródło
3
Trochę przeceniasz swoją sprawę. Podczas gdy testy jednostkowe mają bez wątpienia ogromne znaczenie, budowanie programów we właściwy sposób, sposób minimalizujący niepotrzebną złożoność, jest równie ważne, jeśli nie bardziej. Języki takie jak Haskell mają systemy typów, które poprawiają niezawodność i niezawodność napisanych w nich programów, a najlepsze praktyki w kodowaniu i projektowaniu pozwalają uniknąć wielu pułapek niesprawdzonego kodu, co sprawia, że ​​testy jednostkowe są mniej krytyczne niż w innym przypadku.
Robert Harvey
1
@RobertHarve ... a testy jednostkowe to bardzo dobry sposób, aby pośrednio popchnąć programistów w tym kierunku. Praca nad interfejsem API podczas tworzenia testów, zamiast używania go z innym kodem, pomaga we właściwym nastawieniu, aby ograniczyć lub usunąć nieszczelne abstrakcje lub dziwne wywołania metod. Nie sądzę, że KillianFoth coś przesadza.
Izkata
@Robert: Jedyną rzeczą, którą uważam za przesadzoną, jest to, że „testy religijne powoli stają się coraz bardziej popularne”. Jeśli powoli określa się go w ramach geograficznych, to prawdopodobnie nie jest daleko ….. :)
mattnz
+1 W mojej obecnej pracy uderza mnie pływowa zmiana postaw programistycznych w związku z nowszymi dostępnymi bibliotekami i technologiami. Po raz pierwszy w mojej karierze ci faceci każą mi zastosować bardziej wyrafinowany proces rozwoju, zamiast mnie czuć, jakbym był na szczycie wzgórza i krzyczał na wietrze. Zgadzam się, że to kwestia czasu, zanim te postawy znajdą wyraz w składni języka ojczystego.
Rob
1
Przepraszam, jeszcze jedna myśl: komentarz Roberta Harveya. W tej chwili systemy typów to niezła próba, ale naprawdę nie są w stanie zdefiniować ograniczeń typów - dotyczą interfejsu, ale nie ograniczają prawidłowego zachowania. (tj. 1 + 1 == 3 skompiluje się, ale może, ale nie musi być prawdą, w zależności od implementacji +) Zastanawiam się, czy następnym trendem będą bardziej ekspresyjne systemy typów, które łączą to, co uważamy za testowanie jednostkowe.
Rob
6

Wiele języków obsługuje testowanie. Twierdzenia C to testy, które program może zawieść. W tym miejscu zatrzymuje się większość języków, ale Eiffel i ostatnio Ada 2012 mają zmienne wstępne (rzeczy, które muszą przejść argumenty funkcji) i postwariantne (rzeczy, które musi przekazać wynik funkcji), przy czym Ada oferuje możliwość odwołania się do początkowych argumentów w postwariant. Ada 2012 oferuje również niezmienniki typów, więc za każdym razem, gdy wywoływana jest metoda w klasie Ada, niezmiennik typu jest sprawdzany przed zwróceniem.

To nie jest typ pełnego testowania, jaki może zapewnić dobry system testowania, ale jest to ważny rodzaj testowania, który najlepiej mogą obsługiwać języki.

prosfilaes
źródło
2
Po odrobinie pracy w Eiffelu i będąc obszernym użytkownikiem stwierdzeń, moje doświadczenie jest takie, że wszystko, co możesz zrobić, które ma charakter kontraktowy, pomaga wyeliminować wiele błędów.
Blrfl
2

Niektórzy zwolennicy silnie typowanych języków funkcjonalnych twierdzą, że te cechy językowe zmniejszają lub eliminują potrzebę testów jednostkowych.

Dwa, imho, dobrym tego przykładem jest tutaj F # dla zabawy i zysku tutaj i tutaj

Osobiście nadal wierzę w wartość testów jednostkowych, ale są pewne ważne punkty. Np. Jeśli stan niezgodny z prawem jest nieczytelny w kodzie, to nie tylko napisanie testów jednostkowych dla tego przypadku jest niemożliwe.

Pete
źródło
2

Twierdziłbym, że przegapiłeś dodanie niektórych niezbędnych funkcji, ponieważ nie zostały one wyróżnione, jak w przypadku testów jednostkowych .

Na przykład testy jednostkowe w języku C # są oparte głównie na atrybutach. Funkcja Niestandardowe atrybuty zapewnia bogaty mechanizm rozszerzenia, który umożliwia frameworkom takim jak NUnit iterowanie i konkurowanie z takimi funkcjami, jak testy oparte na teorii i parametryzowane .

To prowadzi mnie do drugiego ważnego punktu - nie wiemy wystarczająco dużo o tym, co czyni dobrą testowalność upieczeniem go w języku. Tempo innowacji w testowaniu jest znacznie szybsze niż w innych konstrukcjach językowych, dlatego musimy mieć elastyczne mechanizmy w naszych językach, aby pozostawić swobodę innowacji.

Jestem użytkownikiem, ale nie jestem fanatykiem TDD - w niektórych przypadkach jest to bardzo pomocne, szczególnie w celu poprawy myślenia projektowego. Niekoniecznie jest to przydatne w przypadku starszych systemów - testowanie wyższego poziomu z dobrymi danymi i automatyzacją prawdopodobnie przyniesie więcej korzyści wraz z silną kulturą kontroli kodu .

Inne odpowiednie teksty:

Andy Dent
źródło