nie rozwiązuj w testach jednostkowych problemów z testowanym kodem
DESCRIPTION
Grzegorz Gałęzowski - prezentacja z III edycji konferencji Quality Excites.TRANSCRIPT
QUALITY EXCITES 201431 MAJA 2014
GRZEGORZ GAŁĘZOWSKIMOTOROLA SOLUTIONS
GRZEGORZ GAŁĘZOWSKIMOTOROLA SOLUTIONS
NIE ROZWIĄZUJ W TESTACHJEDNOSTKOWYCH PROBLEMÓW Z TESTOWANYM KODEM
BĘDZIE O...
TESTACH JEDNOSTKOWYCH
I PROJEKTOWANIU
ORAZ ICH ZWIĄZKU
NA 5 PRZYKŁADACH
TESTY W MOIM ŚWIECIE...
Lista TODOCzy zaimplementowałem wszystko?
AnalizaCzy wiem, co buduję?
Wykonywalna dokumentacjaCzego "klient" może od kodu oczekiwać?
ProjektowanieJak rozbić problem?
TESTY JEDNOSTKOWEDAWNIEJ I DZIŚ
"the tests must be kept to the same level of high quality as the production code. (...) Duplication must be eliminated from them."
Robert C. Martin
ROZWIĄZANIANA POZIOMIEAUTOMATYZACJI
METODY SETUP
[SetUp] public void SetTheStage(){ this.valueUsedEverywhere = 12;
}
DZIEDZICZENIE KLAS
[TestFixture] public class AuthorizationBehaviors : TestBase
METODY POMOCNICZE
message = createMessageFor("Ala");
JAKOŚĆ JEDNOSTKI:SPÓJNOŚĆ I POWIĄZANIA
SPÓJNOŚĆ ~ JEDNA RZECZ
A B
C D
AB
C D
SPÓJNOŚĆ ~ JEDNA RZECZ
AB
CD
SPÓJNOŚĆ ~ JEDNA RZECZ
POWIĄZANIA – KOGO KLASA ZNA?
A
B
C
D
MOCKI – "SYMULATORY" OBIEKTÓW
ATEST
SPÓJNOŚĆ I POWIĄZANIAA TESTY
TEST JEDNOSTKOWY TO KLIENT KODU
Jednostka
KlientTest
Test
TestTest
Test
Test
Test
ŹLE ZAPROJEKTOWANA JEDNOSTKA...
Jednostka
KlientTest
Test
TestTest
Test
Test
Test
...JEST TRUDNO TESTOWALNA
Jednostka
KlientTest
Test
TestTest
Test
Test
Test
SŁUCHAJ ŚLEPOSWOICH TESTÓW
KORZYŚCI Z ˶SŁUCHANIA TESTÓW˝
Lepszy design produktu• większa spójność• mniej powiązań
Lepsza utrzymywalność testów
PIĘĆ PRZYKŁADÓW
UWAGA! ZŁY KOD!
PRZYKŁAD 1:
NACHODZENIEZAKRESUTESTÓW
validation = new SanityValidation();
info = new PersonInfo(); info.Name = "Zenek"; info.Surname = "Kowalski"; info.Age = VALID_AGE;
Assert.DoesNotThrow( () => validation.Of(info));
validation = new SanityValidation();
info = new PersonInfo(); info.Name = "$"; info.Surname = "Kowalski"; info.Age = VALID_AGE;
Assert.Throws<InvalidValueException>(
() => validation.Of(info));
validation = new SanityValidation();
info = new PersonInfo(); info.Name = "Zenek"; info.Surname = "$"; info.Age = VALID_AGE;
Assert.Throws<InvalidValueException>(
() => validation.Of(info));
validation = new SanityValidation();
info = new PersonInfo(); info.Name = "Zenek"; info.Surname = "Kowalski"; info.Age = VALID_AGE – 1;
Assert.Throws<InvalidValueException>(
() => validation.Of(info));
PÓKI CO...
Name Surname Age
UTRZYMYWALNOŚĆ
Name Surname Age Country
Name Surname Age Country
UTRZYMYWALNOŚĆ
Wskazówka: Nachodzenie na siebie zakresów testów.
˶NADPISZ JEDNO POLE˝ - OK, ALE...
validation = new SanityValidation();
info = CreateValidPersonInfo();info.Name = CreateInvalidName();
Assert.Throws<InvalidValueException>(
() => validation.Of(info));
˶NADPISZ JEDNO POLE˝ - OK, ALE...
validation = new SanityValidation();
info = CreateValidPersonInfo();info.Name = CreateInvalidName();
Assert.Throws<InvalidValueException>(
() => validation.Of(info));
Wskazówka: Testy różnych zachowań
padają z tego samego powodu
MAŁA SPÓJNOŚĆ
SanityValidation
Walidacja Surname
Walidacja Age
Testy Surname
Testy Age
Of(x)
Testy Name Walidacja Name
Walidacja Surname
Walidacja Age
ZACHOWANIA SOBIE PRZESZKADZAJĄ
SanityValidationWalidacja Name
Walidacja Surname
Walidacja Age
Testy Surname
Testy Age
Of(x)
Testy Name
ZACHOWANIA SOBIE PRZESZKADZAJĄ
SanityValidation
Walidacja Surname
Walidacja AgeTesty Age
Of()
Testy Name Walidacja Name
Walidacja Surname
Walidacja Age
Testy Surname
ZACHOWANIA SOBIE PRZESZKADZAJĄ
SanityValidation
Walidacja Surname
Walidacja Age
Testy Surname Of(x)
Testy Name Walidacja Name
Walidacja Surname
Walidacja AgeTesty Age
PODZIAŁ LEPSZA SPOJNOSĆ
PartialValidations
Walidacja Name
Walidacja Surname
Walidacja Age
SanityValidation
Of(x)
PersonInfo
UWSPÓLNIENIE
SanityValidation PartialValidations
Walidacja Napisu
Walidacja Minimum
Of(x)
PersonInfo
MAMY DANE I FUNKCJĘ
SanityValidation PartialValidations
Walidacja Napisu
Walidacja Minimum
Of(x)
PersonInfo
LEPSZA ABSTRAKCJA
Person
Validate()
PartialValidations
Walidacja Napisu
Walidacja MinimumPersonInfo
TEST PERSON: UŻYWA WALIDACJI?
Person
Validate()
PersonInfo
Test Validate()
PartialValidations
Mock
TESTY WALIDACJI NAPISU
PartialValidations
Walidacja Napisu
Walidacja Minimum
Testy Napisu
Testy Minimum
TEST WALIDACJI MINIMUM
Validations
Walidacja MinimumTesty Minimum
Walidacja NapisuTesty Napisu
PRZYKŁAD 2:
KOMBINACJEFUNKCJONALNOŚCI
var processing = new MessageProcessing();
messageProcessing.Encryption = true;messageProcessing.Translation = true;messageProcessing.Compression = true;
processing.For(message);
...
...
var processing = new MessageProcessing();
messageProcessing.Encryption = true;messageProcessing.Translation = true;messageProcessing.Compression = true;
processing.For(message);
...
...
Wskazówka: Eksplozja Kombinacji
[TestCase(true, true, true)]public void ShouldProcessMessage( bool encrypt, bool translate, bool compress){processing = new MessageProcessing();processing.Encryption = encrypt;processing.Translation = translate;processing.Compression = compress;//...
[TestCase(true , true , true )][TestCase(false, true , true )][TestCase(true , false, true )][TestCase(true , true , false)][TestCase(false, false, true )][TestCase(true , false, false)][TestCase(false, true , false)][TestCase(false, false, false)]public void ShouldProcessMessage( bool encrypt, bool translate, bool compress)
ZBYT WIELE ODPOWIEDZIALNOŚCI
MessageProcessing
Walidacja Surname
Walidacja Age
For(msg)
Encryption
Translation
Compression
PODZIAŁ NA MNIEJSZE OBIEKTY
MessageProcessing
For(msg)
Encryption
Translation
Compression
WYŁĄCZANIE TRANSFORMACJI
MessageProcessing
For(msg)
NullEncryption
Translation
Compression
TEST PRZETWARZANIA - LEPIEJ, ALE...
MessageProcessing
For(msg)
(1)
(2)
(3)
TEST PRZETWARZANIA - LEPIEJ, ALE...
MessageProcessing
For(msg)Wskazówka:
Dużo różnych mocków na test
WSPÓLNY MIANOWNIK...
MessageProcessing
Encryption
Translation
Compression
For(msg)
PODZIAŁ OBIEKTÓW
MessageProcessing
For(m)
EncryptionTranslation Compression
TransformationTransformationTransformation
TEST PRZETWARZANIA – JESZCZE RAZ
MessageProcessing
For(m)
PRZYKŁAD 3:
ŁAŃCUCHY ZALEŻNOŚCI
MOCKI - LEGENDA
Utworzenie:mock = Substitute.For<Interface>()
Zaprogramowanie rezultatu wywołanie metody:mock.GetSomething().Returns(something);
system = Substitute.For<System>();radio = Substitute.For<Radio>();owner = Substitute.For<User>();
system.GetRadio(1).Returns(radio);radio.GetOwner().Returns(owner);owner.GetName().Returns(name);
ZESTAWIANIE ŁAŃCUCHA MOCKÓW
Test
Name
WSZYSTKO WIDOCZNE PUBLICZNIE
System
Radio
OwnerName
Klient
PROBLEM Z ZALEŻNOŚCIAMI
System
Radio
OwnerName
Klient
PROBLEM Z ZALEŻNOŚCIAMI
System
Radio
OwnerName
Klient
Wskazówka: mocki zwracające mocki
NAJPROSTSZE ROZWIĄZANIE
System
Radio
OwnerRadioOwnerName
Klient
ŁATWIEJSZE TESTY
RadioOwnerName
Klient
PRZYKŁAD 4:
MUSZĘ TESTOWAĆ PRYWATNE METODY
Obiekt
D()B()
C()
A()
- CHCĘ TESTOWAĆ PRYWATNĄ METODĘ
- ZRÓB TO PRZEZ PUBLICZNE API
Test Obiekt
B()
C()
A()
D()
- ALE ONA JEST NA TYLE ODRĘBNA...
Test 2 Obiekt
B()
C()
A()
D()
... ŻE CHCĘ JĄ TESTOWAĆ OSOBNO.
Test 2 Obiekt
B()
C()
A()
D()Wskazówka:
Chęć testowania prywatnych metod
- TO JĄ WYDZIEL!
Test 2 Obiekt
B()
C()
A()
InnyObiekt
D()
PRZYKŁAD 5:
CIĘŻKI KONTEKST
configData = new ConfigData();configData.GetHeader().SetStdState(StdState.SUBMITTED);configData.GetHeader().SetType(FileType.NECB);
ge1 = CreateGroupEntry(1, TALKGROUP, null, configData.Id);ge2 = CreateGroupEntry(2, TALKGROUP, null, configData.Id);ge3 = CreateGroupEntry(3, MULTIGROUP, null,
configData.Id);ge4 = CreateGroupEntry(4, MULTIGROUP, null,
configData.Id);ge5 = CreateGroupEntry(5, MULTIGROUP, null,
configData.Id);
talkgroup1 = CreateTalkgroup(1, 1, null, configData.Id);talkgroup2 = CreateTalkgroup(1, 3, null, configData.Id);talkgroup3 = CreateTalkgroup(2, 0, null, configData.Id);talkgroup4 = CreateTalkgroup(3, 5, null, configData.Id);
context = new ValidationObjectContext(configData);
configData = new ConfigData();configData.GetHeader().SetStdState(StdState.SUBMITTED);configData.GetHeader().SetType(FileType.NECB);
ge1 = CreateGroupEntry(1, TALKGROUP, null, configData.Id);ge2 = CreateGroupEntry(2, TALKGROUP, null, configData.Id);ge3 = CreateGroupEntry(3, MULTIGROUP, null,
configData.Id);ge4 = CreateGroupEntry(4, MULTIGROUP, null,
configData.Id);ge5 = CreateGroupEntry(5, MULTIGROUP, null,
configData.Id);
talkgroup1 = CreateTalkgroup(1, 1, null, configData.Id);talkgroup2 = CreateTalkgroup(1, 3, null, configData.Id);talkgroup3 = CreateTalkgroup(2, 0, null, configData.Id);talkgroup4 = CreateTalkgroup(3, 5, null, configData.Id);
context = new ValidationObjectContext(configData);
Wskazówka: pracochłonne ustawianie kontekstu
WNIOSKI
Słuchaj ślepo swoich testówUważaj na:
• eksplozje kombinacji• długie testy• testowanie prywatnych metod• mocki zwracające mocki• ciężki kontekst
Szukaj najpierw źródła problemu w designie
PYTANIA?
Słuchaj ślepo swoich testówUważaj na:
• eksplozje kombinacji• długie testy• testowanie prywatnych metod• mocki zwracające mocki• ciężki kontekst
Szukaj najpierw źródła problemu w designie
MOTOROLA, MOTO, MOTOROLA SOLUTIONS and the Stylized M Logo are trademarks or registered trademarks of Motorola Trademark Holdings, LLC and are used under license. All other trademarks are the property of their respective owners. © 2010 Motorola, Inc. All rights reserved.
iProtect Classification: Public