7. TDD GIR OSS TILBAMELEDINGER PÅ
Design
Implementasjon
(er koden
(virker koden)
vellstrukturert)
8. TEST OPPFØRSEL – IKKE METODER
I begynnelsen skrev vi tester som «speiler» klassen som testes
public class OrderProcessing
{
public void ProcessOrder(Order order){}
public void ShipOrder(Order order){}
}
[TestFixture]
public class OrderProcessingTest
{
[Test]
public void ProcessOrderTest(){}
[Test]
public void ShipOrderTest(){}
[Test]
public void ShipOrderTest2(){}
}
9. TEST OPPFØRSEL – IKKE METODER
“I found the shift from thinking in tests to
thinking in behaviour so profound that I
started to refer to TDD as BDD”
- Dan North, Introducing BDD blog post
10. TESTDOX NAVNEKONVENSJON
Hver test er en setning med klassen som testes som implisitt
subjekt
• A List holds items in the order they were added
• A List can hold multiple references to the same item
• A List throws an exception when removing an item it doesn’t hold
11. TESTKLASSE SKREVET ETTER TESTDOX KONVENSJON
[TestFixture]
public class ListTest
{
[Test]
public void Holds_items_in_the_order_they_were_added()
{
}
[Test]
public void Can_hold_multiple_references_to_the_same_item()
{
}
[Test]
public void Throws_exception_when_removing_an_item_it_doesnt_hold()
{
}
}
12. KLASSEVARIABLER I BUNNEN AV KLASSEN
[TestFixture]
public class OrderProcessingTest
{
[Test]
public void Should_generate_shipping_statement_for_order()
{
}
[SetUp]
public void Given_we_have_an_order_processor()
{
emailService = new EmailMock();
processor = new OrderProcessing(emailService);
}
private OrderProcessing processor;
private EmailMock emailService;
}
13. ARRANGE, ACT, ASSERT
[Test]
public void Should_read_inbox_location()
{
string[] parameters = {@"c:inbox", ""};
var initParameter = new InitParameters(parameters);
initParameter.Inbox.Should().Be(@"c:inbox");
}
15. NUNIT SHOULD ASSERTS
public class Be : Is { public Be() { } }
public class Have : Has { public Have() { } }
public class Contain : Contains { public Contain() { } }
public static partial class ShouldExtensions
{
public static void Should(this object o, IResolveConstraint constraint)
{
Assert.That(o, constraint);
}
public static void ShouldNot(this object o, Constraint constraint)
{
Assert.That(o, new NotOperator().ApplyPrefix(constraint));
}
}
16. FLUENT ASSERTS
input.Should().Be("Merry Christmas");
public static class MyFluentAsserts
{
public static StringAsserts Should(this string text)
{
return new StringAsserts(text);
}
}
public class StringAsserts
{
private readonly string _text;
public StringAsserts(string text)
{
_text = text;
}
public void Be(string otherString)
{
Assert.That(_text, Is.EqualTo(otherString));
}
}
22. FOKUS PÅ LESBARHET
Som utvikler bruker vi langt mere tid på å lese kode enn å skrive kode
• Lett å forstå hva koden gjør gjennom testnavn
• Lett å forstå hvordan bruke koden gjennom test implementasjon
• Lett å forstå hvorfor en test feiler når koden endrer seg
• Lett å forstå hvordan koden er implementert gjennom kontinuerlig
refaktorering og fokuserte/små klasser
24. LESBARHET
Testkode beskriver hva koden gjør. Produksjonskode er absrrakt i
Konkret i verdiene som brukes som verdiene som brukes, men konkret i
eksempler på hva koden gjør, og til å hvordan den får jobben gjort.
verifisere at den faktisk gjør det, men
abstrakt i hvordan koden fungerer
25. HVOR BEGYNNER VI?
TEST ET
«VANDRENDE SKJELETT»
Tester fra utsiden og inn for å
hele tiden fokusere på
funksjonalitet som gir verdi
Begynner å teste et «skjelet»
som tar for seg den enkleste
funksjonaliteten som gir mening
Skrive en ende-til-ende
akseptansetest som vil «tvinge
oss» til å begynne på den
faktiske implementasjonen
28. UNIT, INTEGRATION &
ACCEPTANCE TEST
Unit Test Integration Test Acceptance Test
• Tester vår kode • Tester hvordan vår kode • Tester systemet ende-til-
integrerer med kode vi ende
• Uavhengig av miljø
ikke kontrollerer
• Fokus på brukerhistorier
• Kjører raskt
• Kan røre miljø (filsystem,
• Bruker så realistisk miljø
meldingskø)
som mulig
29. CASE: SANTA WISHLIST PROCESSOR ITERASJON 1
• Julenissen mottar store mengder ønskelister til jul.
• Ønsker system for å effektivisere behandlingen av disse.
• Systemet skal være batch-basert, og lese innkommende ønskelister
som skal gjøres om til utgående pakke- og leveranselister
• Skal kjøre som en Console Application som f.eks kan settes opp som
en scheduled task
• Første iterasjon gir alle barn det de har ønsket seg aller mest
30. CASE: SANTA WISHLIST PROCESSOR INPUT/OUTPUT FORMAT
Input filformat:
Jonas Follesø (28)
Wesselsgate 19, 7043 Trondheim
Nokia Lumia 800
iPhone 4S
iPad 2
Output filformat:
Nokia Lumia 800 (Jonas Follesø, Wesselsgate 19, 7043 Trondheim)
iPhone 4S (Ola Nordman, Kirkeveien 1, 1000 Oslo)
32. KLASSISK TDD
Kent Beck’s «Test-driven Development By Example»
En algoritme oppdages en test av gangen.
Tester tilstanden til systemet etter
33. ROMERTALL I KLASSISK TDD
Stegene og testene for å finne romertall for et gitt tall kan se slik ut:
• Test 1: I => return «I»
• Test 2: II => if (number == 1) return «I» else return «II»
• Test 3: III => while (number > 0) concat «I» to result and decrement number
Verifisering av
tilstand
34. KLASSISK TDD
Vi lager en mer generell løsning en test av gangen, slik at vi
til slutt ender opp med en enkel, elegant løsning som
håndterer alle testene opp til nå
35. «LONDON STYLE»-TDD / MOCKIST TDD
«Growing Object Oriented Software Guided by Tests» den
definitive boka.
Fokus på:
• Roller
• Ansvarsområder
• Interaksjon
Verifisering av
oppførsel
40. TESTING MED MOCK OBJEKTER
[Test]
public void Should_send_email_when_order_is_processed()
{
var emailMock = new Mock<ISendEmail>();
emailMock.Setup(e => e.SendMessage(It.Is<Message>(m => m.To == "jonas@follesoe.no")))
.Returns(true)
.Verifiable("Did not send e-mail to customer as expected");
var orderProcessing = new OrderProcessing(emailMock.Object);
var order = new Order {CustomerEmail = "jonas@follesoe.no"};
orderProcessing.ProcessOrder(order);
emailMock.Verify();
}
41. TESTING MED MOCK OBJEKTER
Test 'Should_send_email_when_order_is_processed' failed:
Moq.MockVerificationException :
The following setups were not matched:
Did not send e-mail to customer as expected:
ISendEmail e => e.SendMessage(It.Is<Message>(m => m.To ==
"jonas@follesoe.no"))
42. TESTING MED HÅNDSKREVET MOCK
[Test]
public void Should_send_email_when_order_is_processed()
{
var emailMock = new EmailMock();
var orderProcessing = new OrderProcessing(emailMock);
var order = new Order {CustomerEmail = "jonas@follesoe.no"};
orderProcessing.ProcessOrder(order);
emailMock.SendMessageWasCalled.Should().BeTrue("Should send e-mail to customer");
emailMock.MessageThatWasSent.To.Should().Be("jonas@follesoe.no");
}
43. TESTING MED HÅNDSKREVET MOCK
Test ‘Should_send_email_when_order_is_processed' failed: Expected
True because Should send e-mail to customer, but found False.
44. EMAILMOCK KLASSE
public class EmailMock : ISendEmail
{
public Message MessageThatWasSent;
public bool SendMessageWasCalled;
public Exception ExceptionToThrow;
public bool ReturnValue;
public bool SendMessage(Message message)
{
SendMessageWasCalled = true;
MessageThatWasSent = message;
if(ExceptionToThrow != null)
throw ExceptionToThrow;
return ReturnValue;
}
}
45. MOCK RAMMEVERK ELLER
HÅNDSKREVNE MOCKS?
Mock Rammeverk Håndskrevne Mocks
• Mulighet for avansert matching på • Lett å forstå for «nybegynnere»
parametere
• Lett å debugge gjennom koden
• Slipper å skrive enge mock klasser
• Presise feilmeldinger på hvilke
expectations som ikke ble møtt
46. TESTING MED MOCK OBJEKTER
Tester hvordan vårt objekt sammarbeider med objektene rundt seg
Gjennom å skrive tester på denne måten designer vi både det inngående og
utgående grensesnittet til klassen
Inngående grensesnitt er metoder på klassen
Utgående grensesnitt er avhengigheter,
og metodene vi kaller på disse klassene
Verifisering av
oppførsel
52. OPPSETT AV TESTDATA
[Test]
public void Should_generate_shipping_statement_for_order()
{
var order = new Order {
Customer = new Customer {
Name = "Jonas Follesø",
Address = new Address {
City = "Lakselv",
PostalCode = 9700
}
},
Lines = new List<OrderLine> {
new OrderLine { Product = "Some product", Quantity = 1 },
new OrderLine { Product = "Some other product", Quantity = 2 }
}
};
var orderProcessing = new OrderProcessing();
orderProcessing.ProcessOrder(order);
}
53. PROBLEMER MED TESTDATA
• Vanskelig å se hvilke felter som har betyrning for testen
• Testene blir mer skjøre mot endringer i datastruktur
• Testene ikke like enkle å lese
• Bruker vi «immutable value types» (DDD) må feltene settes i konstruktør
54. TESTDATA MED BUILDER PATTERN
[Test]
public void Should_generate_shipping_statement_for_order()
{
Order order = Build
.AnOrder()
.ForCustomer(CustomerBuilder.ACustomer())
.WithLine("Some product", quantity: 1)
.WithLine("Some other product", quantity: 2);
var orderProcessing = new OrderProcessing();
orderProcessing.ProcessOrder(order);
}
55. BUILDER PATTERN IMPLEMENTASJON
public class CustomerBuilder
{
private Customer customer;
public static CustomerBuilder ACustomer()
{
return new CustomerBuilder();
}
private CustomerBuilder()
{
customer = new Customer();
customer.Name = "John Doe";
}
public static implicit operator Customer(CustomerBuilder builder)
{
return builder.customer;
}
}
57. BUILDER PATTERN FACTORY
public static class Build
{
public static CustomerBuilder ACustomer()
{
return CustomerBuilder.ACustomer();
}
public static OrderBuilder AnOrder()
{
return OrderBuilder.AnOrder();
}
}
58. TESTDATA MED BUILDER PATTERN
[Test]
public void Should_generate_shipping_statement_for_order()
{
Order order = Build
.AnOrder()
.ForCustomer(CustomerBuilder.AnCustomer())
.WithLine("Some product", quantity: 1)
.WithLine("Some other product", quantity: 2);
var orderProcessing = new OrderProcessing();
orderProcessing.ProcessOrder(order);
}
60. CASE: SANTA WISHLIST PROCESSOR ITERASJON 2
• Svært kostbart at alle skal få det de har ønsket seg
• Ønsker å sjekke om barn har vært snille eller ikke. Denne
informasjonen ligger i SC(R)UM (Santa Clause Relationship and
Uber Management System)
• Integration med SC(R)UM er outsourcet til Finland, så vi skal kun
definere grensesnittet mot deres tjenester.
• Kun barn under 18 som har vært snill får pakken sin
61. CASE: SANTA WISHLIST PROCESSOR ITERASJON 3
• Ønsker å fordele hvilke gaver som skal gis basert på lagerstatus. Skal
derfor velge den gaven fra ønskelisten som det er flest av på lager
• Har hatt problemer med å finne adressen. Ønsker derfor å bruke RPS
(Rudolf Positioning System) som finner en nøyaktig posisjon for en
gitt adresse.
A better alternative is to name tests in terms of the features that the target object provides.We use a TestDox convention (invented by Chris Stevenson) where each test name reads like a sentence, with the target class as the implicit subject.