Napisanie minimalnego kodu, aby przejść test jednostkowy - bez oszukiwania!


36

Jak robiąc TDD i pisząc test jednostkowy, jak oprzeć się pokusie „oszukiwania” podczas pisania pierwszej iteracji testowanego kodu „implementacyjnego”?

Na przykład:
muszę obliczyć silnię liczby. Zaczynam od testu jednostkowego (przy użyciu MSTest) czegoś takiego jak:

[TestClass]
public class CalculateFactorialTests
{
    [TestMethod]
    public void CalculateFactorial_5_input_returns_120()
    {
        // Arrange
        var myMath = new MyMath();
        // Act
        long output = myMath.CalculateFactorial(5);
        // Assert
        Assert.AreEqual(120, output);
    }
}

Uruchamiam ten kod i kończy się niepowodzeniem, ponieważ CalculateFactorialmetoda nawet nie istnieje. Więc teraz piszę pierwszą iterację kodu, aby zaimplementować testowaną metodę, pisząc minimalny kod wymagany do zaliczenia testu.

Chodzi o to, że ciągle mam ochotę napisać:

public class MyMath
{
    public long CalculateFactorial(long input)
    {
        return 120;
    }
}

Jest to technicznie poprawne, ponieważ tak naprawdę jest to minimalny kod wymagany do tego, aby ten konkretny test zdał (przejść na zielono), chociaż jest to oczywiście „oszustwo”, ponieważ tak naprawdę nie próbuje nawet wykonać funkcji obliczania silni. Oczywiście teraz część refaktoryzacji staje się ćwiczeniem w „pisaniu poprawnej funkcjonalności”, a nie prawdziwym refaktoryzowaniem implementacji. Oczywiście dodanie dodatkowych testów z różnymi parametrami zakończy się niepowodzeniem i wymusi refaktoryzację, ale musisz zacząć od tego jednego testu.

Moje pytanie brzmi więc, jak uzyskać równowagę między „pisaniem minimalnego kodu, aby przejść test”, a jednocześnie utrzymywać go w funkcjonowaniu i zgodnie z duchem tego, co faktycznie próbujesz osiągnąć?


4
To ludzka sprawa: musisz się oprzeć pokusie oszukiwania. Nie ma w tym nic więcej. Możesz dodać więcej testów i napisać więcej kodu testowego niż kodu do przetestowania, ale jeśli nie masz tego luksusu, musisz po prostu się oprzeć. Istnieje wiele miejsc w kodowaniu, w których musimy się oprzeć pokusie włamania lub oszukiwania, ponieważ wiemy, że chociaż może działać dzisiaj, nie zadziała później.
Dan Rosenstark,

7
Z pewnością w TDD robienie tego na odwrót to oszustwo - tzn. Powrót 120 to właściwy sposób. Trudno mi to zmusić, nie ścigam się i nie zaczynam pisać obliczeń czynnikowych.
Paul Butcher,

2
Uznałbym to za oszustwo, tylko dlatego, że może przejść test, ale nie dodaje żadnej prawdziwej funkcjonalności ani nie przybliża cię do ostatecznego rozwiązania problemu.
GrumpyMonkey

3
Jeśli okaże się, że kod klienta przesyła zawsze tylko cyfrę 5, zwracanie wartości 120 nie jest zwykłym oszustwem, ale w rzeczywistości jest uzasadnionym rozwiązaniem.
Kramii przywraca Monikę

Zgadzam się z @PaulButcher - w rzeczywistości wiele przykładów testów jednostkowych w tekstach i artykułach przyjąłoby to podejście.
HorusKol,

Odpowiedzi:


45

To całkowicie legalne. Czerwony, zielony, refaktor.

Pierwszy test zalicza się.

Dodaj drugi test z nowym wejściem.

Teraz szybko przejdź do zielonego, możesz dodać if-else, który działa dobrze. To mija, ale jeszcze nie skończyłeś.

Najważniejsza jest trzecia część Red, Green, Refactor. Refaktoryzuj, aby usunąć duplikację . W twoim kodzie będzie teraz duplikat. Dwie instrukcje zwracające liczby całkowite. Jedynym sposobem na usunięcie tego powielania jest prawidłowe kodowanie funkcji.

Nie mówię, że nie pisz tego poprawnie za pierwszym razem. Mówię tylko, że to nie oszustwo, jeśli nie.


12
To tylko nasuwa pytanie, dlaczego po prostu nie napisać poprawnie funkcji w pierwszej kolejności?
Robert Harvey

8
@Robert, liczby silni są banalnie proste. Prawdziwą zaletą TDD jest pisanie nietrywialnych bibliotek, a napisanie testu w pierwszej kolejności zmusza do zaprojektowania interfejsu API przed implementacją, co - z mojego doświadczenia - prowadzi do lepszego kodu.

1
@Robert, to ty martwisz się rozwiązaniem problemu zamiast zdania testu. Mówię wam, że w przypadku nietrywialnych problemów po prostu lepiej jest odłożyć trudny projekt, dopóki nie przeprowadzisz testów.

1
@ Thorbjørn Ravn Andersen, nie, nie mówię, że możesz mieć tylko jeden zwrot. Istnieją ważne powody wielu (tj. Instrukcji wartowniczych). Problem polega na tym, że obie deklaracje zwrotne były „równe”. Zrobili tę samą „rzecz”. Po prostu miały różne wartości. TDD nie polega na sztywności i przestrzeganiu określonego rozmiaru stosunku test / kod. Chodzi o stworzenie poziomu komfortu w bazie kodu. Jeśli potrafisz napisać test zakończony niepowodzeniem, to funkcja, która będzie działać w przyszłych testach tej funkcji, świetnie. Zrób to, a następnie napisz testy przypadku, upewniając się, że funkcja nadal działa.
CaffGeek,

3
chodzi o to, aby nie pisać od razu pełnej (choć prostej) implementacji, ponieważ wtedy nie masz żadnej gwarancji, że twoje testy MOGĄ się nie udać. punktem, w którym test kończy się niepowodzeniem przed jego zaliczeniem, jest fakt, że masz rzeczywisty dowód, że zmiana w kodzie jest tym, co satysfakcjonuje stwierdzenie, które na nim poczyniłeś. jest to jedyny powód, dla którego TDD jest tak świetny do budowania zestawu testów regresji i całkowicie ociera podłogę w tym sensie podejście „test po”.
sara

25

Oczywiście konieczne jest zrozumienie ostatecznego celu i osiągnięcie algorytmu, który spełnia ten cel.

TDD nie jest magiczną kulą do projektowania; nadal musisz wiedzieć, jak rozwiązywać problemy za pomocą kodu, i nadal musisz wiedzieć, jak to zrobić na poziomie wyższym niż kilka wierszy kodu, aby pomyślnie przejść test.

Podoba mi się pomysł TDD, ponieważ zachęca do dobrego projektowania; sprawia, że ​​myślisz o tym, jak napisać kod, aby był on testowalny, i ogólnie rzecz biorąc, filozofia popchnie kod w kierunku lepszego ogólnego projektu. Ale nadal musisz wiedzieć, jak zaprojektować rozwiązanie.

Nie popieram redukcjonistycznych filozofii TDD, które twierdzą, że możesz rozwinąć aplikację, pisząc najmniejszą ilość kodu, aby przejść test. Bez myślenia o architekturze to nie zadziała, a twój przykład to potwierdza.

Wujek Bob Martin mówi:

Jeśli nie zajmujesz się rozwojem opartym na testach, bardzo trudno jest nazwać się profesjonalistą. Jim Coplin zadzwonił do mnie po dywan. Nie podobało mu się to, co powiedziałem. W rzeczywistości jego stanowisko polega obecnie na tym, że Test Driven Development niszczy architektury, ponieważ ludzie piszą testy, aby porzucić wszelkie inne myśli i rozszarpują swoje architektury w szalonym pędzie, aby przejść testy, a on ma interesujący punkt, to ciekawy sposób na nadużywanie rytuału i utratę zamiaru dyscypliny.

jeśli nie zastanawiasz się nad architekturą, jeśli zamiast tego ignorujesz architekturę i zlewasz testy razem i je przechodzisz, niszczysz rzecz, która pozwoli budynkowi pozostać w górze, ponieważ jest to koncentracja na struktura systemu i solidne decyzje projektowe, które pomagają utrzymać integralność strukturalną systemu.

Nie możesz po prostu zrzucić całej gamy testów i zmusić je do przejścia przez dekadę po dekadzie po dekadzie i założyć, że twój system przetrwa. Nie chcemy ewoluować w piekło. Zatem dobry programista testowy jest zawsze świadomy podejmowania decyzji architektonicznych, zawsze myśląc o dużym obrazie.


Naprawdę nie jest to odpowiedź na pytanie, ale 1+
Nikt

2
@rmx: Um, pytanie brzmi: Jak uzyskać równowagę między „pisaniem minimalnego kodu, aby przejść test”, a jednocześnie zachować funkcjonalność i duch tego, co faktycznie próbujesz osiągnąć? Czy czytamy to samo pytanie?
Robert Harvey

Idealnym rozwiązaniem jest algorytm i nie ma on nic wspólnego z architekturą. Wykonanie TDD nie spowoduje wymyślenia algorytmów. W pewnym momencie musisz wykonać kroki w zakresie algorytmu / rozwiązania.
Joppe,

Zgadzam się z @rmx. To tak naprawdę nie odpowiada na moje konkretne pytanie, ale daje podstawy do zastanowienia się nad tym, jak TDD ogólnie pasuje do ogólnego obrazu całego procesu tworzenia oprogramowania. Z tego powodu +1.
CraigTP

Myślę, że można by zastąpić „algorytmy” - i innymi terminami - „architekturą”, a argument nadal się utrzymuje; chodzi o to, że nie można zobaczyć drewna drzew. O ile nie zamierzasz pisać osobnego testu dla każdego wejścia liczb całkowitych, TDD nie będzie w stanie odróżnić właściwej implementacji czynnikowej od niektórych przewrotnych kodowań, które działają dla wszystkich testowanych przypadków, ale nie dla innych. Problem z TDD polega na łatwości, z jaką „wszystkie testy zdają” i „kod jest dobry” są skonfliktowane. W pewnym momencie należy zastosować dużą miarę zdrowego rozsądku.
Julia Hayward

16

Bardzo dobre pytanie ... i muszę się nie zgodzić z prawie wszystkimi oprócz @Robert.

Pisanie

return 120;

wykonanie funkcji czynnikowej przez wykonanie jednego testu jest stratą czasu . To nie jest „oszukiwanie”, ani dosłownie podążanie za czerwono-zielonym refaktorem. Jest źle .

Dlatego:

  • Oblicz czynnik faktorowy jest funkcją, a nie „zwraca stałą”. „return 120” nie jest obliczeniem.
  • argumenty „refaktoryzatora” są błędne; jeśli masz dwa przypadki testowe dla 5 i 6, kod ten jest wciąż złe, ponieważ nie jesteś obliczania silni w ogóle :

    if (input == 5) { return 120; } //input=5 case
    else { return 720; }   //input=6 case
    
  • jeśli dosłownie podążymy za argumentem „refaktoryzator” , wówczas gdy będziemy mieli 5 przypadków testowych, wywołamy YAGNI i zaimplementujemy funkcję za pomocą tabeli odnośników:

    if (factorialDictionary.Contains(input)) {
        return factorialDictionary[input]; 
    }
    throw new Exception("Input failure");
    

Żaden z nich są rzeczywiście obliczenia niczego, jesteś . I to nie jest zadanie!


1
@rmx: nie, nie przegapiłem tego; „refaktoryzacja w celu usunięcia duplikacji” może być zadowolona z tabeli odnośników. BTW zasada, że ​​testy jednostkowe kodują wymagania, nie jest specyficzna dla BDD, jest to ogólna zasada Agile / XP. Jeśli wymaganie brzmiało: „Odpowiedz na pytanie”, jaka jest silnia 5, to „zwróć 120;” byłoby uzasadnione ;-)
Steven A. Lowe

2
@Chad to wszystko niepotrzebna praca - po prostu napisz tę funkcję za pierwszym razem ;-)
Steven A. Lowe

2
@Steven A.Lowe, według tej logiki, po co pisać testy ?! „Po prostu napisz aplikację za pierwszym razem!” Punkt TDD to małe, bezpieczne, przyrostowe zmiany.
CaffGeek

1
@Chad: strawman.
Steven A. Lowe

2
chodzi o to, aby nie pisać od razu pełnej (choć prostej) implementacji, ponieważ wtedy nie masz żadnej gwarancji, że twoje testy MOGĄ się nie udać. punktem, w którym test kończy się niepowodzeniem przed jego zaliczeniem, jest fakt, że masz rzeczywisty dowód, że zmiana w kodzie jest tym, co satysfakcjonuje stwierdzenie, które na nim poczyniłeś. jest to jedyny powód, dla którego TDD jest tak świetny do budowania zestawu testów regresji i całkowicie ociera podłogę w tym sensie podejście „test po”. nigdy nie piszesz przypadkowo testu, który nie może zakończyć się niepowodzeniem. także spójrz na kata wujek bobs prime czynnik.
sara

10

Kiedy napisałeś tylko jeden test jednostkowy, implementacja jednowierszowa ( return 120;) jest uzasadniona. Pisanie pętli obliczającej wartość 120 - to byłoby oszustwo!

Takie proste testy początkowe są dobrym sposobem na wyłapanie przypadków skrajnych i zapobieganie błędom jednorazowym. Pięć w rzeczywistości nie jest wartością wejściową, od której zacznę.

Przydatna tutaj może być zasada: zero, jeden, wiele, wiele . Zero i jeden są ważnymi przypadkami krawędzi dla silni. Mogą być realizowane za pomocą jednowarstwowych. Przypadek testowy „wiele” (np. 5!) Zmusiłby cię do napisania pętli. Przypadek testowy „partii” (1000 !?) może zmusić Cię do wdrożenia alternatywnego algorytmu do obsługi bardzo dużych liczb.


2
Przypadek „-1” byłby interesujący. Ponieważ nie jest dobrze zdefiniowany, więc zarówno facet piszący test, jak i facet piszący kod muszą najpierw uzgodnić, co powinno się stać.
gnasher729

2
+1 za wskazanie, że factorial(5)jest to zły pierwszy test. zaczynamy od najprostszych możliwych przypadków i w każdej iteracji robimy testy bardziej szczegółowe, zachęcając kod, aby stał się nieco bardziej ogólny. to właśnie wujek Bob nazywa priorytetowym założeniem transformacji ( blog.8thlight.com/uncle-bob/2013/05/27/… )
sara

5

Tak długo, jak masz tylko jeden test, minimalny kod wymagany do zaliczenia testu jest naprawdę return 120;, i możesz go łatwo zachować, dopóki nie masz więcej testów.

Umożliwia to odłożenie dalszego projektowania do momentu napisania testów, które wykonują INNE zwracane wartości tej metody.

Proszę pamiętać, że test jest runnable wersja specyfikacji, a jeśli wszystko że specyfikacja mówi, że f (6) = 120 następnie, że idealnie pasuje do tej roli.


Poważnie? Zgodnie z tą logiką będziesz musiał przepisać kod za każdym razem, gdy ktoś wymyśli nowe dane wejściowe.
Robert Harvey

6
@Robert, w NIEKTÓRYM punkcie dodanie nowej skrzynki nie będzie już skutkować najprostszym możliwym kodem, w którym to momencie napiszesz nową implementację. Ponieważ masz już testy, wiesz dokładnie, kiedy nowa implementacja działa tak samo jak stara.

1
@ Thorbjørn Ravn Andersen, właśnie najważniejsza część Refaktora Czerwono-Zielonego, to refaktoryzacja.
CaffGeek,

+1: Jest to również ogólny pomysł z mojej wiedzy, ale należy coś powiedzieć o wypełnieniu dorozumianej umowy (tj. Czynnikowa nazwa metody ). Jeśli podasz tylko (tj. Test) f (6) = 120, to zawsze będziesz musiał tylko „zwrócić 120”. Gdy zaczniesz dodawać testy, aby upewnić się, że f (x) == x * x-1 ... * xx-1: upperBound> = x> = 0, to dojdziesz do funkcji spełniającej równanie czynnikowe.
Steven Evers

1
@SnOrfus, miejscem na „domniemane umowy” są przypadki testowe. Jeżeli umowa została zawarta na silni, ty TEST jeśli znane są silni, a jeśli znane non-silni nie są. Wiele z nich. Nie trzeba długo przekształcać listy dziesięciu pierwszych silni do pętli for testującej każdą liczbę do dziesiątej silni.

4

Jeśli jesteś w stanie oszukiwać w taki sposób, sugeruje to, że twoje testy jednostkowe są wadliwe.

Zamiast testować metodę czynnikową z pojedynczą wartością, sprawdź, że był to zakres wartości. Testowanie oparte na danych może tutaj pomóc.

Zobacz swoje testy jednostkowe jako przejaw wymagań - muszą one wspólnie określać zachowanie metody, którą testują. (Jest to znane jako rozwój oparty na zachowaniu - jego przyszłość ;-))

Więc zadaj sobie pytanie - gdyby ktoś zmienił implementację na coś niepoprawnego, czy Twoje testy nadal by się zdały, czy powiedzieliby „poczekaj chwilę!”?

Mając to na uwadze, jeśli twój jedyny test był tym, który dotyczy pytania, to technicznie odpowiednia implementacja jest poprawna. Problem jest następnie postrzegany jako źle zdefiniowane wymagania.


Jak zauważyła Nanda, zawsze możesz dodać nieskończoną serię caseinstrukcji do switchi nie możesz napisać testu dla każdego możliwego wejścia i wyjścia dla przykładu OP.
Robert Harvey

Możesz technicznie przetestować wartości z Int64.MinValue do Int64.MaxValue. Uruchomienie zajęłoby dużo czasu, ale wyraźnie zdefiniowałoby to wymaganie bez miejsca na błędy. Przy obecnej technologii jest to niewykonalne (podejrzewam, że może stać się bardziej powszechne w przyszłości) i zgadzam się, że można oszukiwać, ale myślę, że pytanie PO nie było praktyczne (nikt tak naprawdę nie oszukiwałby w taki sposób w praktyce), ale teoretyczny.
Nikt

@rmx: Gdybyś mógł to zrobić, testy byłyby algorytmem i nie trzeba już pisać algorytmu.
Robert Harvey

To prawda. Moja praca dyplomowa dotyczy automatycznego generowania implementacji przy użyciu testów jednostkowych jako przewodnika z algorytmem genetycznym jako pomocą TDD - i jest to możliwe tylko przy solidnych testach. Różnica polega na tym, że powiązanie wymagań z kodem zazwyczaj jest znacznie trudniejsze do odczytania i uchwycenia niż pojedyncza metoda, która zawiera testy jednostkowe. Potem pojawia się pytanie: jeśli twoja implementacja jest manifestacją twoich testów jednostkowych, a twoje testy jednostkowe są manifestacją twoich wymagań, dlaczego nie po prostu całkowicie pominąć testowanie? Nie mam odpowiedzi.
Nikt

Czy my, jako ludzie, nie popełniamy tak samo błędu w testach jednostkowych, jak w kodzie implementacyjnym? Dlaczego więc w ogóle test jednostkowy?
Nikt

3

Po prostu napisz więcej testów. W końcu pisanie byłoby krótsze

public long CalculateFactorial(long input)
{
    return input <= 1 ? 1 : CalculateFactorial(input-1)*input;
}

niż

public long CalculateFactorial(long input)
{
    switch (input) {
       case 0: return 1;
       case 1: return 1;
       case 2: return 2;
       case 3: return 6;
       case 4: return 24;
       case 5: return 120;
    }
}

:-)


3
Czemu nie tylko poprawnie napisać algorytm?
Robert Harvey

3
@Robert, jest to prawidłowy algorytm do obliczania silni liczby od 0 do 5. Poza tym, co oznacza „poprawnie”? Jest to bardzo prosty przykład, ale kiedy staje się bardziej złożony, pojawia się wiele gradacji tego, co znaczy „poprawne”. Czy program wymagający dostępu do konta root jest „poprawny”? Czy używanie XML jest „poprawne”, zamiast CSV? Nie możesz na to odpowiedzieć. Każdy algorytm jest poprawny, o ile spełnia pewne wymagania biznesowe, które są sformułowane jako testy w TDD.
P Shved

3
Należy zauważyć, że ponieważ typ wyjściowy jest długi, istnieje tylko niewielka liczba wartości wejściowych (około 20), które funkcja może ewentualnie poprawnie obsłużyć, dlatego duża instrukcja przełączania niekoniecznie jest najgorszą implementacją - jeśli prędkość jest większa ważne niż rozmiar kodu, instrukcja switch może być właściwą drogą, w zależności od twoich priorytetów.
user281377,

3

Pisanie testów „cheat” jest OK, dla wystarczająco małych wartości „OK”. Pamiętaj jednak, że testowanie jednostkowe jest zakończone tylko wtedy, gdy wszystkie testy zakończą się pomyślnie i nie będzie można napisać nowych testów, które się nie powiodą . Jeśli naprawdę chcesz mieć metodę CalculateFactorial, która zawiera kilka instrukcji if (lub jeszcze lepiej, dużą instrukcję switch / case :-) możesz to zrobić, a ponieważ masz do czynienia z liczbą o stałej precyzji wymagany kod implementacja tego jest skończona (choć prawdopodobnie dość duża i brzydka, a być może ograniczona przez kompilator lub ograniczenia systemowe dotyczące maksymalnego rozmiaru kodu procedury). W tym momencie, jeśli naprawdęNalegaj, aby wszystkie prace rozwojowe były prowadzone przez test jednostkowy. Możesz napisać test, który wymaga, aby kod obliczył wynik w czasie krótszym niż ten, który można osiągnąć, wykonując wszystkie gałęzie instrukcji if .

Zasadniczo TDD może pomóc Ci napisać kod, który poprawnie implementuje wymagania , ale nie może zmusić Cię do pisania dobrego kodu. To zależy od Ciebie.

Udostępnij i ciesz się.


+1 za „testowanie jednostkowe jest ukończone tylko wtedy, gdy wszystkie testy zakończą się pomyślnie i nie będzie można napisać żadnych nowych testów, które się nie powiodą”. Wiele osób twierdzi, że zasadne jest zwracanie stałej, ale nie należy stosować „na krótko” lub „ jeżeli ogólne wymagania wymagają tylko tych konkretnych przypadków ”
Tymina

1

Zgadzam się w 100% z sugestią Roberta Harveysa, tutaj nie chodzi tylko o zaliczenie testów, musisz również pamiętać o ogólnym celu.

Jako rozwiązanie twojego bólu: „zweryfikowano, że działa tylko z danym zestawem danych wejściowych”, zaproponowałbym użycie testów opartych na danych, takich jak teoria xunit. Podstawą tej koncepcji jest to, że pozwala ona łatwo tworzyć Specyfikacje wejść do wyjść.

W przypadku czynników czynnikowych test wyglądałby następująco:

    [Theory]
    [InlineData(0, 1)]
    [InlineData( 1, 1 )]
    [InlineData( 2, 2 )]
    [InlineData( 3, 6 )]
    [InlineData( 4, 24 )]
    public void Test_Factorial(int input, int expected)
    {
        int result = Factorial( input );
        Assert.Equal( result, expected);
    }

Możesz nawet zaimplementować dostarczanie danych testowych (które zwraca IEnumerable<Tuple<xxx>>) i zakodować niezmiennik matematyczny, taki jak wielokrotne dzielenie przez n da n-1).

Uważam, że tp to bardzo potężny sposób testowania.


1

Jeśli nadal możesz oszukiwać, testy nie są wystarczające. Napisz więcej testów! Na przykład postaram się dodać testy z danymi wejściowymi 1, -1, -1000, 0, 10, 200.

Niemniej jednak, jeśli naprawdę chcesz oszukiwać, możesz napisać niekończące się „jeśli-to”. W takim przypadku nic nie może pomóc oprócz przeglądu kodu. Wkrótce zostaniesz złapany na teście akceptacyjnym ( napisanym przez inną osobę! )

Problem z testami jednostkowymi polega na tym, że programiści postrzegają je jako niepotrzebną pracę. Prawidłowy sposób na ich zobaczenie to narzędzie, dzięki któremu poprawisz wynik swojej pracy. Jeśli więc utworzysz „jeśli-to”, nieświadomie wiesz, że istnieją inne przypadki do rozważenia. Oznacza to, że musisz napisać kolejne testy. I tak dalej, i tak dalej, dopóki nie uświadomisz sobie, że oszustwo nie działa i lepiej jest po prostu poprawnie kodować. Jeśli nadal czujesz, że nie skończyłeś, nie skończyłeś.


1
Wygląda na to, że mówisz, że samo napisanie kodu wystarczającego do zaliczenia testu (jak zaleca TDD) nie jest wystarczające. Musisz także pamiętać o rozsądnych zasadach projektowania oprogramowania. Zgadzam się z tobą BTW.
Robert Harvey

0

Sugerowałbym, że twój wybór testu nie jest najlepszym testem.

Zacząłbym od:

silnia (1) jako pierwszy test,

silnia (0) jako druga

silnia (-ve) jako trzecia

a następnie kontynuuj w nietrywialnych przypadkach

i zakończ skrzynką przelewową.


Co to jest -ve??
Robert Harvey

wartość ujemna.
Chris Cudmore,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.