Jak testujesz enkoder?

9

Mam coś takiego:

public byte[] EncodeMyObject(MyObject obj)

Testowałem tak:

byte[] expectedResults = new byte[3]{ 0x01, 0x02, 0xFF };
Assert.IsEqual(expectedResults, EncodeMyObject(myObject));

EDYCJA: Dwa proponowane przeze mnie sposoby to:

1) Używanie zakodowanych oczekiwanych wartości, jak w powyższym przykładzie.

2) Używanie dekodera do dekodowania zakodowanej tablicy bajtów i porównywanie obiektów wejścia / wyjścia.

Problem, który widzę w metodzie 1, polega na tym, że jest ona bardzo krucha i wymaga dużej ilości zakodowanych wartości.

Problem z metodą 2 polega na tym, że testowanie enkodera zależy od prawidłowego działania dekodera. Jeśli koder / dekoder są równo uszkodzone (w tym samym miejscu), testy mogą dać fałszywie dodatnie wyniki.

Mogą to być jedyne sposoby przetestowania tego rodzaju metody. Jeśli tak jest, to dobrze. Zadaję pytanie, aby sprawdzić, czy istnieją lepsze strategie dla tego typu testów. Nie mogę ujawnić wewnętrznych elementów konkretnego enkodera, nad którym pracuję. Pytam ogólnie, jak byś rozwiązał ten problem, i nie uważam, żeby elementy wewnętrzne były ważne. Załóżmy, że dany obiekt wejściowy zawsze wytworzy tę samą wyjściową tablicę bajtów.

ConditionRacer
źródło
4
Jak myObjectprzejść od myObjectdo { 0x01, 0x02, 0xFF }? Czy ten algorytm można rozbić i przetestować? Powód, dla którego obecnie pytam, wygląda na to, że masz test, który dowodzi, że jedna magiczna rzecz wytwarza inną magiczną rzecz. Twoja jedyna pewność polega na tym, że jedno wejście daje jedno wyjście. Jeśli potrafisz złamać algorytm, możesz zyskać dalsze zaufanie do algorytmu i mniej polegać na magicznych wejściach i wyjściach.
Anthony Pegram
3
@Codism Co się stanie, jeśli koder i dekoder zostaną zepsute w tym samym miejscu?
ConditionRacer
2
Testy z definicji coś robią i sprawdzają, czy uzyskasz oczekiwane wyniki, co robi twój test. Musisz oczywiście upewnić się, że wykonujesz wystarczającą liczbę takich testów, aby upewnić się, że wykonałeś cały swój kod i obejmowałeś przypadki brzegowe i inne dziwactwa.
Blrfl
1
@ Justin984, cóż, teraz idziemy głębiej. Nie ujawniłbym tych prywatnych elementów wewnętrznych jako członków API kodera, z pewnością nie. Całkowicie usunę je z kodera. A raczej Enkoder przekazuje gdzie indziej zależność . Jeśli jest to bitwa między nieuleczalną metodą potworów lub grupą klas pomocników, za każdym razem wybieram klasy pomocników. Ale znowu, w tym momencie dokonuję niedoinformowanych wniosków na temat twojego kodu, ponieważ go nie widzę. Ale jeśli chcesz zyskać zaufanie do swoich testów, sposobem na osiągnięcie tego są mniejsze metody robienia mniej rzeczy.
Anthony Pegram,
1
@ Justin984 Jeśli specyfikacja ulegnie zmianie, zmienisz oczekiwany wynik w teście, a teraz się nie powiedzie. Następnie zmieniamy logikę enkodera na pozytywną. Wygląda dokładnie na to, jak ma działać TDD i zawiedzie tylko wtedy, gdy powinno. Nie rozumiem, jak to sprawia, że ​​jest kruchy.
Daniel Kaplan

Odpowiedzi:

1

Jesteś w trochę nieznośnej sytuacji. Jeśli miałeś format statyczny, w którym byłeś kodowany, pierwszą metodą byłaby droga. Gdyby to był tylko twój własny format i nikt inny nie musiałby dekodować, to druga metoda byłaby dobrym rozwiązaniem. Ale tak naprawdę nie pasujesz do żadnej z tych kategorii.

Chciałbym spróbować załamać poziom abstrakcji.

Zacznę więc od czegoś na poziomie bitowym, że coś przetestuję

bitWriter = new BitWriter();
bitWriter.writeInt(42, bits = 7);
assertEqual( bitWriter.data(), {0x42} )

Chodzi o to, że autor tekstów wie, jak wypisywać najbardziej prymitywne typy pól, takie jak ints.

Bardziej złożone typy zostaną zaimplementowane przy użyciu i przetestowane:

bitWriter = new BitWriter();
writeDate(bitWriter, new Datetime(2001, 10, 4));

bitWriter2 = new BitWriter();
bitWriter2.writeInt(2001, 12)
bitWriter2.writeInt(10, 4)
bitWriter2.writeInt(4, 6)

assertEquals(bitWriter.data(), bitWriter2.data() )

Zauważ, że pozwala to uniknąć jakiejkolwiek wiedzy na temat sposobu pakowania rzeczywistych bitów. Zostało to przetestowane w poprzednim teście, a do tego testu założymy, że działa.

Następnie na kolejnym poziomie abstrakcji mielibyśmy

bitWriter = new BitWriter();
encodeObject(bitWriter, myObject);


bitWriter2 = new BitWriter();
bitWriter2.writeInt(42, 32)
writeDate(bitWriter2, new Datetime(2001, 10, 4));
writeVarString(bitWriter2, "alphanumeric");

assertEquals(bitWriter.data(), bitWriter2.data() )

dlatego też nie staramy się uwzględniać wiedzy o tym, w jaki sposób kodowane są zmienne, daty lub liczby. W tym teście interesuje nas tylko kodowanie produkowane przez encodeObject.

Ostateczny wynik jest taki, że jeśli format dat zostanie zmieniony, będziesz musiał naprawić testy, które faktycznie dotyczą dat, ale wszystkie inne kody i testy nie dotyczą sposobu, w jaki daty są faktycznie kodowane, a po zaktualizowaniu kodu, aby która działa, wszystkie testy przejdą dobrze.

Winston Ewert
źródło
Lubię to. Myślę, że tak właśnie mówili inni komentatorzy o rozbiciu go na mniejsze części. Nie zmienia to całkowicie problemu, gdy zmienia się specyfikacja, ale czyni go lepszym.
ConditionRacer
6

Zależy. Jeśli kodowanie jest czymś całkowicie naprawionym, gdzie każda implementacja ma tworzyć dokładnie takie same dane wyjściowe, nie ma sensu sprawdzanie niczego innego niż weryfikacja przykładowych danych wejściowych odwzorowanych na dokładnie oczekiwane wyniki. To najbardziej oczywisty test i prawdopodobnie również najłatwiejszy do napisania.

Jeśli jest miejsce do poruszania się z alternatywnymi wyjściami, jak w standardzie MPEG (np. Istnieją pewne operatory, które możesz zastosować do wejścia, ale możesz swobodnie kompromisować wysiłek kodowania w porównaniu z jakością wyjściową lub przestrzenią dyskową), wtedy lepiej zastosować zdefiniowaną strategię dekodowania danych wyjściowych i sprawdź, czy jest ona taka sama jak na wejściu - lub, jeśli kodowanie jest stratne, że jest dość zbliżona do oryginalnego wejścia. Jest to trudniejsze do zaprogramowania, ale chroni cię przed wszelkimi przyszłymi ulepszeniami twojego enkodera.

Kilian Foth
źródło
2
Załóżmy, że używasz dekodera i porównujesz wartości. Co jeśli koder i dekoder są zepsute w tym samym miejscu? Koder koduje niepoprawnie, a dekoder dekoduje niepoprawnie, ale obiekty wejściowe / wyjściowe są poprawne, ponieważ proces został wykonany nieprawidłowo dwukrotnie.
ConditionRacer
@ Justin984 następnie użyj tak zwanych „wektorów testowych”, poznaj pary wejścia / wyjścia, których możesz użyć do dokładnego przetestowania enkodera i dekodera
maniak zapadkowy
@ ratchet freak To przywraca mnie do testowania z oczekiwanymi wartościami. Co jest w porządku, właśnie to robię, ale jest trochę kruche, więc szukałem, czy są lepsze sposoby.
ConditionRacer
1
Oprócz uważnego przeczytania standardu i stworzenia przypadku testowego dla każdej reguły, nie ma sposobu, aby uniknąć, aby zarówno koder, jak i dekoder zawierały ten sam błąd. Załóżmy na przykład, że „ABC” musi zostać przetłumaczone na „xyz”, ale koder nie wie o tym i twój dekoder również nie zrozumie „xyz”, jeśli kiedykolwiek go spotka. Ręcznie wykonane skrzynki testowe nie zawierają sekwencji „ABC”, ponieważ programista nie zdawał sobie sprawy z tej reguły, a także test z kodowaniem / dekodowaniem losowych ciągów nie przejdzie poprawnie, ponieważ zarówno koder, jak i dekoder ignorują problem.
user281377,
1
Aby pomóc w wychwytywaniu błędów dotyczących zarówno dekoderów, jak i koderów napisanych przez ciebie z powodu braku wiedzy, postaraj się uzyskać dane wyjściowe enkoderów od innych dostawców; a także spróbuj przetestować wyjście kodera na dekoderach innych firm. Nie ma wokół tego alternatywy.
rwong
3

Przetestuj to encode(decode(coded_value)) == coded_valuei decode(encode(value)) == value. Jeśli chcesz, możesz dać losowy wkład do testów.

Nadal możliwe jest, że zarówno koder, jak i dekoder są uszkodzone w komplementarny sposób, ale wydaje się to mało prawdopodobne, chyba że masz koncepcyjne nieporozumienie ze standardem kodowania. Wykonywanie zakodowanych testów kodera i dekodera (tak jak już to robisz) powinno temu zapobiec.

Jeśli masz dostęp do innej implementacji tego, o której wiadomo, że działa, możesz przynajmniej użyć go, aby uzyskać pewność, że twoja implementacja jest dobra, nawet jeśli użycie jej w testach jednostkowych byłoby niemożliwe.

Michael Shaw
źródło
Zgadzam się, że komplementarny błąd kodera / dekodera jest ogólnie mało prawdopodobny. W moim konkretnym przypadku kod dla klas enkoderów / dekoderów jest generowany przez inne narzędzie oparte na regułach z bazy danych. Dlatego czasami zdarzają się błędy uzupełniające.
ConditionRacer
Jak mogą wystąpić „błędy uzupełniające”? To implikuje, że istnieje zewnętrzna specyfikacja dla zakodowanej formy, a zatem zewnętrzny dekoder.
kevin cline
Nie rozumiem twojego użycia słowa „zewnętrzna”. Ale istnieje specyfikacja sposobu kodowania danych, a także dekoder. Błąd uzupełniający polega na tym, że zarówno koder, jak i dekoder działają w sposób komplementarny, ale niezgodny ze specyfikacją. Mam przykład w komentarzach pod pierwotnym pytaniem.
ConditionRacer
Jeśli koder miał zaimplementować ROT13, ale przypadkowo zrobił to ROT14 i dekoder również, wówczas dekoduj (koduj ('a')) == 'a', ale koder jest nadal zepsuty. W przypadku rzeczy o wiele bardziej skomplikowanych, prawdopodobnie jest o wiele mniej prawdopodobne, że coś takiego się wydarzy, ale teoretycznie może.
Michael Shaw
@MichaelShaw to tylko drobiazg, koder i dekoder dla ROT13 są takie same; ROT13 jest własną odwrotnością. Jeśli przez przypadek zaimplementowałeś ROT14, decode(encode(char))nie byłby równy char(byłby równy char+2).
Tom Marthenal
2

Testuj zgodnie z wymaganiami .

Jeśli wymaganiem jest tylko „kodowanie do strumienia bajtów, który po zdekodowaniu tworzy równoważny obiekt”, to po prostu przetestuj koder dekodując. Jeśli piszesz zarówno koder, jak i dekoder, po prostu przetestuj je razem. Nie mogą mieć „pasujących błędów”. Jeśli pracują razem, test kończy się pomyślnie.

Jeśli istnieją inne wymagania dotyczące strumienia danych, musisz je przetestować, sprawdzając zakodowane dane.

Jeśli kodowany format jest wstępnie zdefiniowany, albo będziesz musiał zweryfikować zakodowane dane w stosunku do oczekiwanego wyniku, jak to zrobiłeś, lub (lepiej) uzyskać dekoder referencyjny, któremu można zaufać w celu weryfikacji. Zastosowanie dekodera referencyjnego eliminuje możliwość błędnej interpretacji specyfikacji formatu.

Kevin Cline
źródło
1

W zależności od używanej struktury testowej i paradygmatu możesz nadal używać do tego wzoru Arrange Act Assert, tak jak powiedziałeś.

[TestMethod]
public void EncodeMyObject_ForValidInputs_Encodes()
{
    //Arrange object under test
    MyEncoder encoderUnderTest = new MyEncoder();
    MyObject validObject = new MyOjbect();
    //arrange object for condition under test

    //Act
    byte[] actual = encoderUnderTest.EncodeMyObject(myObject);

    //Assert
    byte[] expected = new byte[3]{ 0x01, 0x02, 0xFF };
    Assert.IsEqual(expected, actual);
}

Powinieneś znać wymagania EncodeMyObject()i możesz użyć tego wzorca do przetestowania każdego z nich pod kątem poprawnych i niepoprawnych kryteriów, poprzez ułożenie każdego z nich i zakodowanie oczekiwanego wyniku expected, podobnie dla dekodera.

Ponieważ oczekiwane są zakodowane na stałe, będą kruche, jeśli wprowadzisz ogromną zmianę.

Możesz być w stanie zautomatyzować za pomocą czegoś opartego na parametrach (spójrz na Pex ) lub jeśli robisz DDD lub BDD, spójrz na gerkin / ogórek .

StuperUser
źródło
1

Ty decydujesz, co jest dla Ciebie ważne.

Czy jest dla Ciebie ważne, aby Obiekt przetrwał podróż w obie strony, a dokładny format drutu nie jest tak naprawdę ważny? A może dokładny format przewodowy jest ważną częścią funkcjonalności twojego kodera i dekodera?

Jeśli to pierwsze, to po prostu upewnij się, że obiekty przetrwają podróż w obie strony. Jeśli zarówno koder, jak i dekoder są uszkodzone w dokładnie komplementarny sposób, tak naprawdę to nie obchodzi.

Jeśli to drugie, musisz przetestować, czy format drutu jest zgodny z oczekiwaniami dla danych wejściowych. Oznacza to albo testowanie formatu bezpośrednio, albo użycie implementacji referencyjnej. Ale po przetestowaniu podstaw możesz uzyskać wartość z dodatkowych testów w obie strony, które powinny być łatwiejsze do napisania w objętości.

Bill Michell
źródło