SlideShare une entreprise Scribd logo
1  sur  170
WRITING 
GOOD 
TESTS 
@matteobaglini #IAD14
TEST-DRIVEN 
DEVELOPMENT
@iacoware
TDD MECHANICS
TDD IS A 
MULTIFACETED 
SKILL
SKILL ON 
CLEAN 
CODE
SKILL ON 
REFACTORING 
SAFELY
SKILL ON 
DISCOVER 
TEST CASES
SKILL ON 
CHOOSE 
NEXT TESTS
SKILL ON 
WRITING 
TESTS
SKILL ON 
WRITING GOOD 
TESTS
FOCUS ON 
WRITING GOOD 
TESTS
PROPERTIES
TESTS MUST BE 
ISOLATED
TESTS MUST BE 
INDIPENDENT
TESTS MUST BE 
DETERMINISTIC
AFFECT 
EXECUTION
WHAT ABOUT 
TEST CODE?
BAD 
CODE 
SMELLS
BAD 
TEST CODE 
SMELLS
LEAD TO 
FRAGILITY 
ON REFACTORING
WHY SHOULD 
WE CARE?
WHAT‘S AGILE 
PRACTICES’ 
ULTIMATE GOAL?
REDUCE 
THE COST 
AND RISK 
OF CHANGE
FRAGILE TESTS 
INCREASE 
COSTS
FRAGILE TESTS 
LOSS OF 
CONFIDENCE
FRAGILE TESTS 
REDUCE 
SUSTAINABILITY
PAY OFF 
YOUR 
TECHNICAL DEBT
WHERE IS 
COMPLEXITY?
[Fact] 
public void ScenarioUnderTest() 
{ 
// Arrange 
// Act 
// Assert 
}
START 
FROM 
ASSERT
01 
COMPARING 
OBJECTS
[Fact] 
public void Subtract() 
{ 
var one = TimeRange.Parse("09:00-10:00"); 
var two = TimeRange.Parse("09:15-09:45"); 
var result = one.Subtract(two); 
Assert.Equal(2, result.Length); 
Assert.Equal(TimeSpan.Parse("09:00"), result[0].Begin); 
Assert.Equal(TimeSpan.Parse("09:15"), result[0].End); 
Assert.Equal(TimeSpan.Parse("09:45"), result[1].Begin); 
Assert.Equal(TimeSpan.Parse("10:00"), result[1].End); 
}
[Fact] 
public void Subtract() 
{ 
var one = TimeRange.Parse("09:00-10:00"); 
var two = TimeRange.Parse("09:15-09:45"); 
var result = one.Subtract(two); 
Assert.Equal(2, result.Length); 
Assert.Equal(TimeSpan.Parse("09:00"), result[0].Begin); 
Assert.Equal(TimeSpan.Parse("09:15"), result[0].End); 
Assert.Equal(TimeSpan.Parse("09:45"), result[1].Begin); 
Assert.Equal(TimeSpan.Parse("10:00"), result[1].End); 
}
WHICH KIND 
OF OBJECTS?
VALUE OBJECTS
VALUE OBJECTS 
MODEL FIXED 
QUANTITIES
VALUE OBJECTS 
IMMUTABLE
VALUE OBJECTS 
NO IDENTITY
VALUE OBJECTS 
EQUAL IF THEY 
HAVE SAME STATE
[Fact] 
public void Subtract() 
{ 
var one = TimeRange.Parse("09:00-10:00"); 
var two = TimeRange.Parse("09:15-09:45"); 
var result = one.Subtract(two); 
Assert.Equal(2, result.Length); 
Assert.Equal(TimeSpan.Parse("09:00"), result[0].Begin); 
Assert.Equal(TimeSpan.Parse("09:15"), result[0].End); 
Assert.Equal(TimeSpan.Parse("09:45"), result[1].Begin); 
Assert.Equal(TimeSpan.Parse("10:00"), result[1].End); 
}
[Fact] 
public void Equality() 
{ 
var slot = TimeRange.Parse("09:00-10:00"); 
var same = TimeRange.Parse("09:00-10:00"); 
var differentBegin = TimeRange.Parse("08:00-10:00"); 
var differentEnd = TimeRange.Parse("09:00-13:00"); 
Assert.Equal(slot, same); 
Assert.NotEqual(slot, differentBegin); 
Assert.NotEqual(slot, differentEnd); 
}
[Fact] 
public void Subtract() 
{ 
var one = TimeRange.Parse("09:00-10:00"); 
var two = TimeRange.Parse("09:15-09:45"); 
var result = one.Subtract(two); 
Assert.Equal(2, result.Length); 
Assert.Equal(TimeSpan.Parse("09:00"), result[0].Begin); 
Assert.Equal(TimeSpan.Parse("09:15"), result[0].End); 
Assert.Equal(TimeSpan.Parse("09:45"), result[1].Begin); 
Assert.Equal(TimeSpan.Parse("10:00"), result[1].End); 
}
[Fact] 
public void Subtract() 
{ 
var one = TimeRange.Parse("09:00-10:00"); 
var two = TimeRange.Parse("09:15-09:45"); 
var result = one.Subtract(two); 
Assert.Contains(TimeRange.Parse("09:00-09:15"), result); 
Assert.Contains(TimeRange.Parse("09:45-10:00"), result); 
}
OBJECTS
OBJECTS 
MODEL STATE 
AND PROCESSING 
OVER TIME
OBJECTS 
MUTABLE
OBJECTS 
HAS IDENTITY
OBJECTS 
EQUAL IF THEY 
HAVE SAME 
IDENTITY
[Fact] 
public void GetAllMessages() 
{ 
var messageId1 = Guid.NewGuid(); 
var messageId2 = Guid.NewGuid(); 
var store = new MessageStore(); 
store.Add(DummyMessage(messageId1)); 
store.Add(DummyMessage(messageId2)); 
var result = store.GetAll(); 
Assert.Equal(2, result.Count); 
Assert.NotNull(result.Single(x => x.Id == messageId1)); 
Assert.NotNull(result.Single(x => x.Id == messageId2)); 
}
[Fact] 
public void GetAllMessages() 
{ 
var messageId1 = Guid.NewGuid(); 
var messageId2 = Guid.NewGuid(); 
var store = new MessageStore(); 
store.Add(DummyMessage(messageId1)); 
store.Add(DummyMessage(messageId2)); 
var result = store.GetAll(); 
Assert.Equal(2, result.Count); 
Assert.NotNull(result.Single(x => x.Id == messageId1)); 
Assert.NotNull(result.Single(x => x.Id == messageId2)); 
}
[Fact] 
public void Equality() 
{ 
var message = DummyMessage(Guid.NewGuid()); 
var same = DummyMessage(message.Id); 
var diffent = DummyMessage(Guid.NewGuid()); 
Assert.Equal(message, same); 
Assert.NotEqual(message, diffent); 
}
[Fact] 
public void GetAllMessages() 
{ 
var messageId1 = Guid.NewGuid(); 
var messageId2 = Guid.NewGuid(); 
var store = new MessageStore(); 
store.Add(DummyMessage(messageId1)); 
store.Add(DummyMessage(messageId2)); 
var result = store.GetAll(); 
Assert.Equal(2, result.Count); 
Assert.NotNull(result.Single(x => x.Id == messageId1)); 
Assert.NotNull(result.Single(x => x.Id == messageId2)); 
}
[Fact] 
public void GetAllMessages() 
{ 
var messageId1 = Guid.NewGuid(); 
var messageId2 = Guid.NewGuid(); 
var store = new MessageStore(); 
store.Add(DummyMessage(messageId1)); 
store.Add(DummyMessage(messageId2)); 
var result = store.GetAll(); 
Assert.Contains(DummyMessage(messageId1), result); 
Assert.Contains(DummyMessage(messageId2), result); 
}
WHEN YOU 
CAN’T CHANGE 
THE OBJECT?
[Fact] 
public void GetContactsDetails() 
{ 
var controller = new ContactsController(); 
var result = controller.Details(Guid.NewGuid()); 
var viewResult = Assert.IsType<ViewResult>(result); 
Assert.Equal(String.Empty, viewResult.ViewName); 
Assert.IsAssignableFrom<ContactModel>(viewResult.Model); 
}
[Fact] 
public void GetContactsDetails() 
{ 
var controller = new ContactsController(); 
var result = controller.Details(Guid.NewGuid()); 
var viewResult = Assert.IsType<ViewResult>(result); 
Assert.Equal(String.Empty, viewResult.ViewName); 
Assert.IsAssignableFrom<ContactModel>(viewResult.Model); 
}
[Fact] 
public void GetContactsDetails() 
{ 
var controller = new ContactsController(); 
var result = controller.Details(Guid.NewGuid()); 
MvcAssert.IsViewResult<ContactModel>(result); 
}
02 
TESTING ONLY 
ONE CONCERN
[Fact] 
public void SingleElement() 
{ 
var input = "<element />"; 
var tag = Tag.Parse(input); 
Assert.True(tag.IsEmpty()); 
Assert.Equal("element", tag.Name()); 
Assert.Empty(tag.ChildNodes()); 
}
[Fact] 
public void SingleElement() 
{ 
var input = "<element />"; 
var tag = Tag.Parse(input); 
Assert.True(tag.IsEmpty()); 
Assert.Equal("element", tag.Name()); 
Assert.Empty(tag.ChildNodes()); 
}
TEST CAN 
FAIL FOR 
TOO MANY 
REASONS
[Fact] 
public void SingleElement() 
{ 
var input = "<element />"; 
var tag = Tag.Parse(input); 
Assert.True(tag.IsEmpty()); 
Assert.Equal("element", tag.Name()); 
Assert.Empty(tag.ChildNodes()); 
}
[Fact] 
public void SingleElementNoContent() 
{ 
var input = "<element />"; 
var tag = Tag.Parse(input); 
Assert.True(tag.IsEmpty()); 
}
[Fact] 
public void SingleElementName() 
{ 
var input = "<element />"; 
var tag = Tag.Parse(input); 
Assert.Equal("element", tag.Name()); 
}
[Fact] 
public void SingleElementNoChildren() 
{ 
var input = "<element />"; 
var tag = Tag.Parse(input); 
Assert.Empty(tag.ChildNodes()); 
}
03 
TESTING 
BEHAVIOUR
STATE 
VS 
BEHAVIOUR
FOCUS ON 
TESTING STATE
[Fact] 
public void NewStackIsEmpty() 
{ 
var stack = new Stack(); 
Assert.True(stack.IsEmpty()); 
}
[Fact] 
public void StackIsNotEmptyAfterAddItem() 
{ 
var stack = new Stack(); 
stack.Push(5); 
Assert.False(stack.IsEmpty()); 
}
[Fact] 
public void StackCountIncreaseAfterAddItems() 
{ 
var items = new[] { 1, 2, 3 }; 
var stack = new Stack(); 
stack.Push(items[0]); 
stack.Push(items[1]); 
stack.Push(items[2]); 
Assert.Equal(items.Length, stack.Count()); 
}
FOCUS ON 
TESTING 
BEHAVIOUR
[Fact] 
public void PopOneItem() 
{ 
var item = 5; 
var stack = new Stack(); 
stack.Push(item); 
Assert.Equal(item, stack.Pop()); 
}
[Fact] 
public void PopManyItems() 
{ 
var items = new[] { 1, 2, 3 }; 
var stack = new Stack(); 
stack.Push(items[0]); 
stack.Push(items[1]); 
stack.Push(items[2]); 
Assert.Equal(items.Reverse(), 
new[] { stack.Pop(), stack.Pop(), stack.Pop() }); 
}
[Fact] 
public void PopEmptyStack() 
{ 
var stack = new Stack(); 
Assert 
.Throws<InvalidOperationException>(() => stack.Pop()); 
}
04 
AVOID TESTING 
PRIVATE METHODS
public class Invoice 
{ 
// ... 
public Eur Total() 
{ 
var gross = ComputeGrossAmount(/* args */); 
var tax = ComputeTaxAmount(/* args */); 
return gross.Add(tax); 
} 
}
public class Invoice 
{ 
// ... 
Eur ComputeGrossAmount(/* args */) 
{ 
/* many lines of complex 
* and commented code */ 
} 
}
public class Invoice 
{ 
// ... 
Eur ComputeTaxAmount(/* args */) 
{ 
/* many lines of complex 
* and commented code */ 
} 
}
[Fact] 
public void GrossAmountCalculation() 
{ 
// We want invoke private ComputeGrossAmount method 
} 
[Fact] 
public void TaxAmountCalculation() 
{ 
// We want invoke private ComputeTaxAmount method 
}
DON’T DO IT!
DON’T DO IT!
DON’T DO IT!
CONSIDER APPLYING 
METHOD 
OBJECT
MAKE AN 
OBJECT 
OUT OF THE 
METHOD
public class Invoice 
{ 
// ... 
public Eur Total() 
{ 
var gross = new GrossAmount(/* args */).Compute(); 
var tax = new TaxAmount(/* args */).Compute(); 
return gross.Add(tax); 
} 
}
NOW TEST 
EXTRACTED 
OBJECTS
05 
SAME LEVEL 
OF ABSTRACTION
[Fact] 
public void SetCommand() 
{ 
var cache = new Cache(); 
var dispatcher = new CommandDispatcher(cache); 
dispatcher.Process("SET foo bar"); 
var actual = cache.Get("foo"); 
Assert.Equal("bar", actual); 
}
[Fact] 
public void SetCommand() 
{ 
var cache = new Cache(); 
var dispatcher = new CommandDispatcher(cache); 
dispatcher.Process("SET foo bar"); 
var actual = cache.Get("foo"); 
Assert.Equal("bar", actual); 
}
ACT AT HIGHER 
ASSERT AT LOWER
REMEMBER DIP?
DON’T CROSS 
THE STREAM
[Fact] 
public void SetCommand() 
{ 
var cache = new Cache(); 
var dispatcher = new CommandDispatcher(cache); 
dispatcher.Process("SET foo bar"); 
var actual = cache.Get("foo"); 
Assert.Equal("bar", actual); 
}
[Fact] 
public void SetCommand() 
{ 
var cache = new Cache(); 
var dispatcher = new CommandDispatcher(cache); 
dispatcher.Process("SET foo bar"); 
var actual = dispatcher.Process("GET foo"); 
Assert.Equal("bar", actual); 
}
WHEN IT 
ISN’T YOUR 
RESPONSIBILITY?
[Fact] 
public void SetCommand() 
{ 
var log = new TransactionLog(); 
var cache = new PersistentCache(log); 
cache.Set("foo", "bar"); 
var written = log.History().Take(1).Single(); 
Assert.Equal("SET foo barrn", written); 
}
[Fact] 
public void SetCommand() 
{ 
var log = new TransactionLog(); 
var cache = new PersistentCache(log); 
cache.Set("foo", "bar"); 
var written = log.History().Take(1).Single(); 
Assert.Equal("SET foo barrn", written); 
}
PURE 
FABRICATION
[Fact] 
public void SetCommand() 
{ 
var log = new TransactionLog(); 
var cache = new PersistentCache(log); 
cache.Set("foo", "bar"); 
var written = log.History().Take(1).Single(); 
Assert.Equal("SET foo barrn", written); 
}
[Fact] 
public void SetCommand() 
{ 
var log = new TransactionLog(); 
var cache = new PersistentCache(log); 
cache.Set("foo", "bar"); 
var written = log.History().Take(1).Single(); 
Assert.Equal("SET foo barrn", written); 
}
[Fact] 
public void SetCommand() 
{ 
var log = new TransactionLog(); 
var cache = new PersistentCache(log); 
var monitor = new Monitor(log); 
cache.Set("foo", "bar"); 
var written = log.History().Take(1).Single(); 
Assert.Equal("SET foo barrn", written); 
}
[Fact] 
public void SetCommand() 
{ 
var log = new TransactionLog(); 
var cache = new PersistentCache(log); 
var monitor = new Monitor(log); 
cache.Set("foo", "bar"); 
var written = monitor.LastLog(); 
Assert.Equal("SET foo barrn", written); 
}
WHEN 
SIDE EFFECTS 
AREN’T DIRECTLY 
RELATED?
[Fact] 
public void CommandStatistics() 
{ 
var channel = new StatisticsChannel(); 
var cache = new Cache(channel); 
var dashboard = new RealTimeDashboard(channel); 
cache.Set("foo", "bar"); 
var received = dashboard.LastReceived(); 
Assert.Equal("NAME: SET; ARGS: foo bar", received); 
}
[Fact] 
public void CommandStatistics() 
{ 
var channel = new StatisticsChannel(); 
var cache = new Cache(channel); 
var dashboard = new RealTimeDashboard(channel); 
cache.Set("foo", "bar"); 
var received = dashboard.LastReceived(); 
Assert.Equal("NAME: SET; ARGS: foo bar", received); 
}
BREAK 
THE FLOW
OBSERVER 
PLUS 
SELF SHUNT
public interface ICacheSubscriber 
{ 
void NotifyCommandExecuted(string name, string args); 
}
public class CacheTests : ICacheSubscriber 
{ 
string ExecutedCommandName; 
string ExecutedCommandArgs; 
public void NotifyCommandExecuted(string name, 
string args) 
{ 
ExecutedCommandName = name; 
ExecutedCommandArgs = args; 
} 
// ... 
}
[Fact] 
public void CommandStatistics() 
{ 
var channel = new StatisticsChannel(); 
var cache = new Cache(channel); 
var dashboard = new RealTimeDashboard(channel); 
cache.Set("foo", "bar"); 
var received = dashboard.LastReceived(); 
Assert.Equal("NAME: SET; ARGS: foo bar", received); 
}
[Fact] 
public void CommandStatistics() 
{ 
var cache = new Cache(channel); 
cache.Set("foo", "bar"); 
Assert.Equal("NAME: SET; ARGS: foo bar", received); 
}
[Fact] 
public void CommandStatistics() 
{ 
var cache = new Cache(channel); 
cache.Set("foo", "bar"); 
Assert.Equal("NAME: SET; ARGS: foo bar", received); 
}
[Fact] 
public void CommandStatistics() 
{ 
ICacheSubscriber subscriber = this; 
var cache = new Cache(subscriber); 
cache.Set("foo", "bar"); 
Assert.Equal("NAME: SET; ARGS: foo bar", received); 
}
[Fact] 
public void NotifyCommandExecution() 
{ 
ICacheSubscriber subscriber = this; 
var cache = new Cache(subscriber); 
cache.Set("foo", "bar"); 
Assert.Equal("SET", this.ExecutedCommandName); 
Assert.Equal("foo bar", this.ExecutedCommandArgs); 
}
06 
RESPECT 
ABSTRACTION
[Fact] 
public void Overcrowding() 
{ 
var moreThanThreeNeighbors = 6; 
var cell = new Cell(alive: true); 
var alive = cell.StayAlive(moreThanThreeNeighbors); 
Assert.False(alive); 
}
[Fact] 
public void Overcrowding() 
{ 
var moreThanThreeNeighbors = 6; 
var cell = new Cell(alive: true); 
var alive = cell.StayAlive(moreThanThreeNeighbors); 
Assert.False(alive); 
}
SEGREGATE 
DECISIONS
[Fact] 
public void Overcrowding() 
{ 
var moreThanThreeNeighbors = 6; 
var cell = new Cell(alive: true); 
var alive = cell.StayAlive(moreThanThreeNeighbors); 
Assert.False(alive); 
}
[Fact] 
public void Overcrowding() 
{ 
var moreThanThreeNeighbors = 6; 
var cell = Cell.Live(); 
var alive = cell.StayAlive(moreThanThreeNeighbors); 
Assert.False(alive); 
}
INFORMATION 
HIDING
07 
COMPLEX 
OBJECT 
CREATION
[Fact] 
public void OrderCost() 
{ 
var order = new Order(Location.TakeAway, 
new OrderLines( 
new OrderLine("Latte", 
new Quantity(1), 
Size.Small, 
new Pounds(1, 50)), 
new OrderLine("Cappuccino", 
new Quantity(2), 
Size.Large, 
new Pounds(2, 50)))); 
var result = order.Cost(); 
Assert.Equal(new Pounds(6, 50), result); 
}
[Fact] 
public void OrderCost() 
{ 
var order = new Order(Location.TakeAway, 
new OrderLines( 
new OrderLine("Latte", 
new Quantity(1), 
Size.Small, 
new Pounds(1, 50)), 
new OrderLine("Cappuccino", 
new Quantity(2), 
Size.Large, 
new Pounds(2, 50)))); 
var result = order.Cost(); 
Assert.Equal(new Pounds(6, 50), result); 
}
EXTRACT 
FACTORY METHOD
[Fact] 
public void OrderCost() 
{ 
var order = CreateOrderWithLatteAndCappuccino(); 
var result = order.Cost(); 
Assert.Equal(new Pounds(6, 50), result); 
}
OBJECT 
MOTHER
[Fact] 
public void OrderCost() 
{ 
var order = TestOrders 
.CreateOrderWithLatteAndCappuccino(); 
var result = order.Cost(); 
Assert.Equal(new Pounds(6, 50), result); 
}
Order CreateEmptyOrder() { /* ... */ }
Order CreateEmptyOrder() { /* ... */ } 
Order CreateOrderWithLatteAndCappuccino() { /* ... */ }
Order CreateEmptyOrder() { /* ... */ } 
Order CreateOrderWithLatteAndCappuccino() { /* ... */ } 
Order CreateOrderWithLatteMultipleQuantities() { /* ... */ }
Order CreateEmptyOrder() { /* ... */ } 
Order CreateOrderWithLatteAndCappuccino() { /* ... */ } 
Order CreateOrderWithLatteMultipleQuantities() { /* ... */ } 
Order CreateOrderWithCaffeSpecialDiscount() { /* ... */ }
POOR NAME
LOSS OF CONTEXT
DOESN’T SCALE
COMMON 
SHARED FIXTURE
[Fact] 
public void OrderCost() 
{ 
var order = CreateCommonOrder(); 
var result = order.Cost(); 
Assert.Equal(new Pounds(6, 50), result); 
}
[Fact] 
public void DiscountedOrderCost() 
{ 
var order = CreateCommonOrder(); 
order.Add(Discounts.ByQuantity(2)); 
var result = order.Cost(); 
Assert.Equal(new Pounds(6, 50), result); 
}
[Fact] 
public void DiscountedOrderCost() 
{ 
var order = CreateCommonOrder(); 
order.Add(Discounts.ByQuantity(2)); 
var result = order.Cost(); 
Assert.Equal(new Pounds(6, 50), result); 
}
SAME FAILURE
[Fact] 
public void OrderCost() 
{ 
var order = new Order(Location.TakeAway, 
new OrderLines( 
new OrderLine("Latte", 
new Quantity(1), 
Size.Small, 
new Pounds(1, 50)), 
new OrderLine("Cappuccino", 
new Quantity(2), 
Size.Large, 
new Pounds(2, 50)))); 
var result = order.Cost(); 
Assert.Equal(new Pounds(6, 50), result); 
}
DECOUPLE 
WHAT IS NEEDED
FROM HOW 
IT IS STRUCTURED
BUILDER 
PATTERN
[Fact] 
public void OrderCost() 
{ 
var order = new Order(Location.TakeAway, 
new OrderLines( 
new OrderLine("Latte", 
new Quantity(1), 
Size.Small, 
new Pounds(1, 50)), 
new OrderLine("Cappuccino", 
new Quantity(2), 
Size.Large, 
new Pounds(2, 50)))); 
var result = order.Cost(); 
Assert.Equal(new Pounds(6, 50), result); 
}
[Fact] 
public void OrderCost() 
{ 
var order = new OrderBuilder() 
.WithLocation(Location.TakeAway) 
.WithLine(new OrderLineBuilder() 
.WithName("Latte") 
.WithPrice(new Pounds(1, 50))) 
.WithLine(new OrderLineBuilder() 
.WithName("Cappuccino") 
.WithQuantity(new Quantity(2)) 
.WithSize(Size.Large) 
.WithPrice(new Pounds(1, 50))); 
var result = order.Cost(); 
Assert.Equal(new Pounds(6, 50), result); 
}
STILL 
EXPOSE 
STRUCTURE
INTRODUCE 
HIGHER LEVEL 
METHODS
[Fact] 
public void OrderCost() 
{ 
var order = new OrderBuilder() 
.WithLocation(Location.TakeAway) 
.WithLine(new OrderLineBuilder() 
.WithName("Latte") 
.WithPrice(new Pounds(1, 50))) 
.WithLine(new OrderLineBuilder() 
.WithName("Cappuccino") 
.WithQuantity(new Quantity(2)) 
.WithSize(Size.Large) 
.WithPrice(new Pounds(1, 50))); 
var result = order.Cost(); 
Assert.Equal(new Pounds(6, 50), result); 
}
[Fact] 
public void OrderCost() 
{ 
var order = new OrderBuilder() 
.TakeAway() 
.WithOne("Latte", "1,50") 
.WithTwoLarge("Cappuccino", "1,50"); 
var result = order.Cost(); 
Assert.Equal(new Pounds(6, 50), result); 
}
DOMAIN 
SPECIFIC 
LANGUAGE
RECAP
STOP THE 
MADNESS
TEST CODE 
NEED LOVE
LISTEN TO 
TEST CODE
TOO 
MUCH 
INTIMACY
EXPRESS INTENT
FOCUS ON 
WHAT
NOT ON 
HOW
REMOVE 
DUPLICATION
INTRODUCE 
ABSTRACTIONS
RESPECT 
ABSTRACTION 
LEVELS
EVOLVE TEST 
INFRASTRUCTURE
BABY STEPS
CONTINUOUS 
REFACTORING
IMPROVE 
YOUR 
SKILLS
MATTEO BAGLINI 
FREELANCE 
SOFTWARE DEVELOPER 
TECHNICAL COACH 
@matteobaglini
https://joind.in/talk/view/12770

Contenu connexe

Tendances

Linq Sanjay Vyas
Linq   Sanjay VyasLinq   Sanjay Vyas
Linq Sanjay Vyas
rsnarayanan
 
C# Delegates, Events, Lambda
C# Delegates, Events, LambdaC# Delegates, Events, Lambda
C# Delegates, Events, Lambda
Jussi Pohjolainen
 
Fee managment system
Fee managment systemFee managment system
Fee managment system
fairy9912
 
C# Advanced L02-Operator Overloading+Indexers+UD Conversion
C# Advanced L02-Operator Overloading+Indexers+UD ConversionC# Advanced L02-Operator Overloading+Indexers+UD Conversion
C# Advanced L02-Operator Overloading+Indexers+UD Conversion
Mohammad Shaker
 
FP 201 Unit 2 - Part 3
FP 201 Unit 2 - Part 3FP 201 Unit 2 - Part 3
FP 201 Unit 2 - Part 3
rohassanie
 

Tendances (20)

C# Starter L06-Delegates, Event Handling and Extension Methods
C# Starter L06-Delegates, Event Handling and Extension MethodsC# Starter L06-Delegates, Event Handling and Extension Methods
C# Starter L06-Delegates, Event Handling and Extension Methods
 
Clean coding-practices
Clean coding-practicesClean coding-practices
Clean coding-practices
 
TDC2016POA | Trilha .NET - C# como você nunca viu: conceitos avançados de pro...
TDC2016POA | Trilha .NET - C# como você nunca viu: conceitos avançados de pro...TDC2016POA | Trilha .NET - C# como você nunca viu: conceitos avançados de pro...
TDC2016POA | Trilha .NET - C# como você nunca viu: conceitos avançados de pro...
 
An intro to cqrs
An intro to cqrsAn intro to cqrs
An intro to cqrs
 
The secret unit testing tools no one ever told you about
The secret unit testing tools no one ever told you aboutThe secret unit testing tools no one ever told you about
The secret unit testing tools no one ever told you about
 
Ip project visual mobile
Ip project visual mobileIp project visual mobile
Ip project visual mobile
 
Clean code slide
Clean code slideClean code slide
Clean code slide
 
MaintainStaffTable
MaintainStaffTableMaintainStaffTable
MaintainStaffTable
 
Linq Sanjay Vyas
Linq   Sanjay VyasLinq   Sanjay Vyas
Linq Sanjay Vyas
 
A Test of Strength
A Test of StrengthA Test of Strength
A Test of Strength
 
Clean Code Development
Clean Code DevelopmentClean Code Development
Clean Code Development
 
Developer Experience i TypeScript. Najbardziej ikoniczne duo
Developer Experience i TypeScript. Najbardziej ikoniczne duoDeveloper Experience i TypeScript. Najbardziej ikoniczne duo
Developer Experience i TypeScript. Najbardziej ikoniczne duo
 
Promise: async programming hero
Promise: async programming heroPromise: async programming hero
Promise: async programming hero
 
C# Delegates, Events, Lambda
C# Delegates, Events, LambdaC# Delegates, Events, Lambda
C# Delegates, Events, Lambda
 
Fee managment system
Fee managment systemFee managment system
Fee managment system
 
C# Advanced L02-Operator Overloading+Indexers+UD Conversion
C# Advanced L02-Operator Overloading+Indexers+UD ConversionC# Advanced L02-Operator Overloading+Indexers+UD Conversion
C# Advanced L02-Operator Overloading+Indexers+UD Conversion
 
FP 201 Unit 2 - Part 3
FP 201 Unit 2 - Part 3FP 201 Unit 2 - Part 3
FP 201 Unit 2 - Part 3
 
Clean code
Clean codeClean code
Clean code
 
Marcus Portfolio
Marcus  PortfolioMarcus  Portfolio
Marcus Portfolio
 
clean code book summary - uncle bob - English version
clean code book summary - uncle bob - English versionclean code book summary - uncle bob - English version
clean code book summary - uncle bob - English version
 

Similaire à Writing Good Tests

Working effectively with legacy code
Working effectively with legacy codeWorking effectively with legacy code
Working effectively with legacy code
ShriKant Vashishtha
 
VISUALIZAR REGISTROS EN UN JTABLE
VISUALIZAR REGISTROS EN UN JTABLEVISUALIZAR REGISTROS EN UN JTABLE
VISUALIZAR REGISTROS EN UN JTABLE
Darwin Durand
 
Keep your Wicket application in production
Keep your Wicket application in productionKeep your Wicket application in production
Keep your Wicket application in production
Martijn Dashorst
 
Enterprise workflow with Apps Script
Enterprise workflow with Apps ScriptEnterprise workflow with Apps Script
Enterprise workflow with Apps Script
ccherubino
 

Similaire à Writing Good Tests (20)

How to write clean tests
How to write clean testsHow to write clean tests
How to write clean tests
 
Symfony (Unit, Functional) Testing.
Symfony (Unit, Functional) Testing.Symfony (Unit, Functional) Testing.
Symfony (Unit, Functional) Testing.
 
Working effectively with legacy code
Working effectively with legacy codeWorking effectively with legacy code
Working effectively with legacy code
 
Google guava
Google guavaGoogle guava
Google guava
 
VISUALIZAR REGISTROS EN UN JTABLE
VISUALIZAR REGISTROS EN UN JTABLEVISUALIZAR REGISTROS EN UN JTABLE
VISUALIZAR REGISTROS EN UN JTABLE
 
How to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy CodeHow to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy Code
 
Combatendo code smells em Java
Combatendo code smells em Java Combatendo code smells em Java
Combatendo code smells em Java
 
Keep your Wicket application in production
Keep your Wicket application in productionKeep your Wicket application in production
Keep your Wicket application in production
 
Is writing performant code too expensive?
Is writing performant code too expensive? Is writing performant code too expensive?
Is writing performant code too expensive?
 
What We Talk About When We Talk About Unit Testing
What We Talk About When We Talk About Unit TestingWhat We Talk About When We Talk About Unit Testing
What We Talk About When We Talk About Unit Testing
 
Enterprise workflow with Apps Script
Enterprise workflow with Apps ScriptEnterprise workflow with Apps Script
Enterprise workflow with Apps Script
 
Matching Game In Java
Matching Game In JavaMatching Game In Java
Matching Game In Java
 
Dartprogramming
DartprogrammingDartprogramming
Dartprogramming
 
2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests
 
05 JavaScript #burningkeyboards
05 JavaScript #burningkeyboards05 JavaScript #burningkeyboards
05 JavaScript #burningkeyboards
 
Bad test, good test
Bad test, good testBad test, good test
Bad test, good test
 
GeeCON 2012 Bad Tests, Good Tests
GeeCON 2012 Bad Tests, Good TestsGeeCON 2012 Bad Tests, Good Tests
GeeCON 2012 Bad Tests, Good Tests
 
2011 nri-pratiques tests-avancees
2011 nri-pratiques tests-avancees2011 nri-pratiques tests-avancees
2011 nri-pratiques tests-avancees
 
SOLID Principles
SOLID PrinciplesSOLID Principles
SOLID Principles
 
Apache PIG - User Defined Functions
Apache PIG - User Defined FunctionsApache PIG - User Defined Functions
Apache PIG - User Defined Functions
 

Plus de Matteo Baglini

Legacy Coderetreat Bologna @ CodersTUG
Legacy Coderetreat Bologna @ CodersTUGLegacy Coderetreat Bologna @ CodersTUG
Legacy Coderetreat Bologna @ CodersTUG
Matteo Baglini
 
Approval Tests @ MiniIAD
Approval Tests @ MiniIADApproval Tests @ MiniIAD
Approval Tests @ MiniIAD
Matteo Baglini
 
Approval Tests @ CodersTUG
Approval Tests @ CodersTUGApproval Tests @ CodersTUG
Approval Tests @ CodersTUG
Matteo Baglini
 
Coderetreat @ CodersTUG
Coderetreat @ CodersTUGCoderetreat @ CodersTUG
Coderetreat @ CodersTUG
Matteo Baglini
 
Modern JavaScript Development @ DotNetToscana
Modern JavaScript Development @ DotNetToscanaModern JavaScript Development @ DotNetToscana
Modern JavaScript Development @ DotNetToscana
Matteo Baglini
 
The NoSQL movement @ DotNetToscana
The NoSQL movement @ DotNetToscanaThe NoSQL movement @ DotNetToscana
The NoSQL movement @ DotNetToscana
Matteo Baglini
 
Key-value databases in practice Redis @ DotNetToscana
Key-value databases in practice Redis @ DotNetToscanaKey-value databases in practice Redis @ DotNetToscana
Key-value databases in practice Redis @ DotNetToscana
Matteo Baglini
 

Plus de Matteo Baglini (11)

Legacy Coderetreat Bologna @ CodersTUG
Legacy Coderetreat Bologna @ CodersTUGLegacy Coderetreat Bologna @ CodersTUG
Legacy Coderetreat Bologna @ CodersTUG
 
Approval Tests @ MiniIAD
Approval Tests @ MiniIADApproval Tests @ MiniIAD
Approval Tests @ MiniIAD
 
VS13 - Approval Tests: cosa, come, quando, perché? @ CDays
VS13 - Approval Tests: cosa, come, quando, perché? @ CDaysVS13 - Approval Tests: cosa, come, quando, perché? @ CDays
VS13 - Approval Tests: cosa, come, quando, perché? @ CDays
 
Approval Tests @ CodersTUG
Approval Tests @ CodersTUGApproval Tests @ CodersTUG
Approval Tests @ CodersTUG
 
Coderetreat @ CodersTUG
Coderetreat @ CodersTUGCoderetreat @ CodersTUG
Coderetreat @ CodersTUG
 
RESTFul Web API Services @ DotNetToscana
RESTFul Web API Services @ DotNetToscanaRESTFul Web API Services @ DotNetToscana
RESTFul Web API Services @ DotNetToscana
 
Modern JavaScript Development @ DotNetToscana
Modern JavaScript Development @ DotNetToscanaModern JavaScript Development @ DotNetToscana
Modern JavaScript Development @ DotNetToscana
 
Async: scalabilità e responsiveness senza pari! @ CDays
Async: scalabilità e responsiveness senza pari! @ CDaysAsync: scalabilità e responsiveness senza pari! @ CDays
Async: scalabilità e responsiveness senza pari! @ CDays
 
The NoSQL movement @ DotNetToscana
The NoSQL movement @ DotNetToscanaThe NoSQL movement @ DotNetToscana
The NoSQL movement @ DotNetToscana
 
Key-value databases in practice Redis @ DotNetToscana
Key-value databases in practice Redis @ DotNetToscanaKey-value databases in practice Redis @ DotNetToscana
Key-value databases in practice Redis @ DotNetToscana
 
Asynchronous Programming Lab @ DotNetToscana
Asynchronous Programming Lab @ DotNetToscanaAsynchronous Programming Lab @ DotNetToscana
Asynchronous Programming Lab @ DotNetToscana
 

Dernier

+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
Health
 
The title is not connected to what is inside
The title is not connected to what is insideThe title is not connected to what is inside
The title is not connected to what is inside
shinachiaurasa2
 

Dernier (20)

%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
 
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) SolutionIntroducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
The Guide to Integrating Generative AI into Unified Continuous Testing Platfo...
 
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
 
The title is not connected to what is inside
The title is not connected to what is insideThe title is not connected to what is inside
The title is not connected to what is inside
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
LEVEL 5 - SESSION 1 2023 (1).pptx - PDF 123456
LEVEL 5   - SESSION 1 2023 (1).pptx - PDF 123456LEVEL 5   - SESSION 1 2023 (1).pptx - PDF 123456
LEVEL 5 - SESSION 1 2023 (1).pptx - PDF 123456
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 

Writing Good Tests

  • 1. WRITING GOOD TESTS @matteobaglini #IAD14
  • 5. TDD IS A MULTIFACETED SKILL
  • 8. SKILL ON DISCOVER TEST CASES
  • 9. SKILL ON CHOOSE NEXT TESTS
  • 11. SKILL ON WRITING GOOD TESTS
  • 12. FOCUS ON WRITING GOOD TESTS
  • 14. TESTS MUST BE ISOLATED
  • 15. TESTS MUST BE INDIPENDENT
  • 16. TESTS MUST BE DETERMINISTIC
  • 20. BAD TEST CODE SMELLS
  • 21. LEAD TO FRAGILITY ON REFACTORING
  • 22. WHY SHOULD WE CARE?
  • 23. WHAT‘S AGILE PRACTICES’ ULTIMATE GOAL?
  • 24. REDUCE THE COST AND RISK OF CHANGE
  • 26. FRAGILE TESTS LOSS OF CONFIDENCE
  • 27. FRAGILE TESTS REDUCE SUSTAINABILITY
  • 28. PAY OFF YOUR TECHNICAL DEBT
  • 30. [Fact] public void ScenarioUnderTest() { // Arrange // Act // Assert }
  • 33. [Fact] public void Subtract() { var one = TimeRange.Parse("09:00-10:00"); var two = TimeRange.Parse("09:15-09:45"); var result = one.Subtract(two); Assert.Equal(2, result.Length); Assert.Equal(TimeSpan.Parse("09:00"), result[0].Begin); Assert.Equal(TimeSpan.Parse("09:15"), result[0].End); Assert.Equal(TimeSpan.Parse("09:45"), result[1].Begin); Assert.Equal(TimeSpan.Parse("10:00"), result[1].End); }
  • 34. [Fact] public void Subtract() { var one = TimeRange.Parse("09:00-10:00"); var two = TimeRange.Parse("09:15-09:45"); var result = one.Subtract(two); Assert.Equal(2, result.Length); Assert.Equal(TimeSpan.Parse("09:00"), result[0].Begin); Assert.Equal(TimeSpan.Parse("09:15"), result[0].End); Assert.Equal(TimeSpan.Parse("09:45"), result[1].Begin); Assert.Equal(TimeSpan.Parse("10:00"), result[1].End); }
  • 35. WHICH KIND OF OBJECTS?
  • 37. VALUE OBJECTS MODEL FIXED QUANTITIES
  • 39. VALUE OBJECTS NO IDENTITY
  • 40. VALUE OBJECTS EQUAL IF THEY HAVE SAME STATE
  • 41. [Fact] public void Subtract() { var one = TimeRange.Parse("09:00-10:00"); var two = TimeRange.Parse("09:15-09:45"); var result = one.Subtract(two); Assert.Equal(2, result.Length); Assert.Equal(TimeSpan.Parse("09:00"), result[0].Begin); Assert.Equal(TimeSpan.Parse("09:15"), result[0].End); Assert.Equal(TimeSpan.Parse("09:45"), result[1].Begin); Assert.Equal(TimeSpan.Parse("10:00"), result[1].End); }
  • 42. [Fact] public void Equality() { var slot = TimeRange.Parse("09:00-10:00"); var same = TimeRange.Parse("09:00-10:00"); var differentBegin = TimeRange.Parse("08:00-10:00"); var differentEnd = TimeRange.Parse("09:00-13:00"); Assert.Equal(slot, same); Assert.NotEqual(slot, differentBegin); Assert.NotEqual(slot, differentEnd); }
  • 43. [Fact] public void Subtract() { var one = TimeRange.Parse("09:00-10:00"); var two = TimeRange.Parse("09:15-09:45"); var result = one.Subtract(two); Assert.Equal(2, result.Length); Assert.Equal(TimeSpan.Parse("09:00"), result[0].Begin); Assert.Equal(TimeSpan.Parse("09:15"), result[0].End); Assert.Equal(TimeSpan.Parse("09:45"), result[1].Begin); Assert.Equal(TimeSpan.Parse("10:00"), result[1].End); }
  • 44. [Fact] public void Subtract() { var one = TimeRange.Parse("09:00-10:00"); var two = TimeRange.Parse("09:15-09:45"); var result = one.Subtract(two); Assert.Contains(TimeRange.Parse("09:00-09:15"), result); Assert.Contains(TimeRange.Parse("09:45-10:00"), result); }
  • 46. OBJECTS MODEL STATE AND PROCESSING OVER TIME
  • 49. OBJECTS EQUAL IF THEY HAVE SAME IDENTITY
  • 50. [Fact] public void GetAllMessages() { var messageId1 = Guid.NewGuid(); var messageId2 = Guid.NewGuid(); var store = new MessageStore(); store.Add(DummyMessage(messageId1)); store.Add(DummyMessage(messageId2)); var result = store.GetAll(); Assert.Equal(2, result.Count); Assert.NotNull(result.Single(x => x.Id == messageId1)); Assert.NotNull(result.Single(x => x.Id == messageId2)); }
  • 51. [Fact] public void GetAllMessages() { var messageId1 = Guid.NewGuid(); var messageId2 = Guid.NewGuid(); var store = new MessageStore(); store.Add(DummyMessage(messageId1)); store.Add(DummyMessage(messageId2)); var result = store.GetAll(); Assert.Equal(2, result.Count); Assert.NotNull(result.Single(x => x.Id == messageId1)); Assert.NotNull(result.Single(x => x.Id == messageId2)); }
  • 52. [Fact] public void Equality() { var message = DummyMessage(Guid.NewGuid()); var same = DummyMessage(message.Id); var diffent = DummyMessage(Guid.NewGuid()); Assert.Equal(message, same); Assert.NotEqual(message, diffent); }
  • 53. [Fact] public void GetAllMessages() { var messageId1 = Guid.NewGuid(); var messageId2 = Guid.NewGuid(); var store = new MessageStore(); store.Add(DummyMessage(messageId1)); store.Add(DummyMessage(messageId2)); var result = store.GetAll(); Assert.Equal(2, result.Count); Assert.NotNull(result.Single(x => x.Id == messageId1)); Assert.NotNull(result.Single(x => x.Id == messageId2)); }
  • 54. [Fact] public void GetAllMessages() { var messageId1 = Guid.NewGuid(); var messageId2 = Guid.NewGuid(); var store = new MessageStore(); store.Add(DummyMessage(messageId1)); store.Add(DummyMessage(messageId2)); var result = store.GetAll(); Assert.Contains(DummyMessage(messageId1), result); Assert.Contains(DummyMessage(messageId2), result); }
  • 55. WHEN YOU CAN’T CHANGE THE OBJECT?
  • 56. [Fact] public void GetContactsDetails() { var controller = new ContactsController(); var result = controller.Details(Guid.NewGuid()); var viewResult = Assert.IsType<ViewResult>(result); Assert.Equal(String.Empty, viewResult.ViewName); Assert.IsAssignableFrom<ContactModel>(viewResult.Model); }
  • 57. [Fact] public void GetContactsDetails() { var controller = new ContactsController(); var result = controller.Details(Guid.NewGuid()); var viewResult = Assert.IsType<ViewResult>(result); Assert.Equal(String.Empty, viewResult.ViewName); Assert.IsAssignableFrom<ContactModel>(viewResult.Model); }
  • 58. [Fact] public void GetContactsDetails() { var controller = new ContactsController(); var result = controller.Details(Guid.NewGuid()); MvcAssert.IsViewResult<ContactModel>(result); }
  • 59. 02 TESTING ONLY ONE CONCERN
  • 60. [Fact] public void SingleElement() { var input = "<element />"; var tag = Tag.Parse(input); Assert.True(tag.IsEmpty()); Assert.Equal("element", tag.Name()); Assert.Empty(tag.ChildNodes()); }
  • 61. [Fact] public void SingleElement() { var input = "<element />"; var tag = Tag.Parse(input); Assert.True(tag.IsEmpty()); Assert.Equal("element", tag.Name()); Assert.Empty(tag.ChildNodes()); }
  • 62. TEST CAN FAIL FOR TOO MANY REASONS
  • 63. [Fact] public void SingleElement() { var input = "<element />"; var tag = Tag.Parse(input); Assert.True(tag.IsEmpty()); Assert.Equal("element", tag.Name()); Assert.Empty(tag.ChildNodes()); }
  • 64. [Fact] public void SingleElementNoContent() { var input = "<element />"; var tag = Tag.Parse(input); Assert.True(tag.IsEmpty()); }
  • 65. [Fact] public void SingleElementName() { var input = "<element />"; var tag = Tag.Parse(input); Assert.Equal("element", tag.Name()); }
  • 66. [Fact] public void SingleElementNoChildren() { var input = "<element />"; var tag = Tag.Parse(input); Assert.Empty(tag.ChildNodes()); }
  • 70. [Fact] public void NewStackIsEmpty() { var stack = new Stack(); Assert.True(stack.IsEmpty()); }
  • 71. [Fact] public void StackIsNotEmptyAfterAddItem() { var stack = new Stack(); stack.Push(5); Assert.False(stack.IsEmpty()); }
  • 72. [Fact] public void StackCountIncreaseAfterAddItems() { var items = new[] { 1, 2, 3 }; var stack = new Stack(); stack.Push(items[0]); stack.Push(items[1]); stack.Push(items[2]); Assert.Equal(items.Length, stack.Count()); }
  • 73. FOCUS ON TESTING BEHAVIOUR
  • 74. [Fact] public void PopOneItem() { var item = 5; var stack = new Stack(); stack.Push(item); Assert.Equal(item, stack.Pop()); }
  • 75. [Fact] public void PopManyItems() { var items = new[] { 1, 2, 3 }; var stack = new Stack(); stack.Push(items[0]); stack.Push(items[1]); stack.Push(items[2]); Assert.Equal(items.Reverse(), new[] { stack.Pop(), stack.Pop(), stack.Pop() }); }
  • 76. [Fact] public void PopEmptyStack() { var stack = new Stack(); Assert .Throws<InvalidOperationException>(() => stack.Pop()); }
  • 77. 04 AVOID TESTING PRIVATE METHODS
  • 78. public class Invoice { // ... public Eur Total() { var gross = ComputeGrossAmount(/* args */); var tax = ComputeTaxAmount(/* args */); return gross.Add(tax); } }
  • 79. public class Invoice { // ... Eur ComputeGrossAmount(/* args */) { /* many lines of complex * and commented code */ } }
  • 80. public class Invoice { // ... Eur ComputeTaxAmount(/* args */) { /* many lines of complex * and commented code */ } }
  • 81. [Fact] public void GrossAmountCalculation() { // We want invoke private ComputeGrossAmount method } [Fact] public void TaxAmountCalculation() { // We want invoke private ComputeTaxAmount method }
  • 86. MAKE AN OBJECT OUT OF THE METHOD
  • 87. public class Invoice { // ... public Eur Total() { var gross = new GrossAmount(/* args */).Compute(); var tax = new TaxAmount(/* args */).Compute(); return gross.Add(tax); } }
  • 89. 05 SAME LEVEL OF ABSTRACTION
  • 90. [Fact] public void SetCommand() { var cache = new Cache(); var dispatcher = new CommandDispatcher(cache); dispatcher.Process("SET foo bar"); var actual = cache.Get("foo"); Assert.Equal("bar", actual); }
  • 91. [Fact] public void SetCommand() { var cache = new Cache(); var dispatcher = new CommandDispatcher(cache); dispatcher.Process("SET foo bar"); var actual = cache.Get("foo"); Assert.Equal("bar", actual); }
  • 92. ACT AT HIGHER ASSERT AT LOWER
  • 95. [Fact] public void SetCommand() { var cache = new Cache(); var dispatcher = new CommandDispatcher(cache); dispatcher.Process("SET foo bar"); var actual = cache.Get("foo"); Assert.Equal("bar", actual); }
  • 96. [Fact] public void SetCommand() { var cache = new Cache(); var dispatcher = new CommandDispatcher(cache); dispatcher.Process("SET foo bar"); var actual = dispatcher.Process("GET foo"); Assert.Equal("bar", actual); }
  • 97. WHEN IT ISN’T YOUR RESPONSIBILITY?
  • 98. [Fact] public void SetCommand() { var log = new TransactionLog(); var cache = new PersistentCache(log); cache.Set("foo", "bar"); var written = log.History().Take(1).Single(); Assert.Equal("SET foo barrn", written); }
  • 99. [Fact] public void SetCommand() { var log = new TransactionLog(); var cache = new PersistentCache(log); cache.Set("foo", "bar"); var written = log.History().Take(1).Single(); Assert.Equal("SET foo barrn", written); }
  • 101. [Fact] public void SetCommand() { var log = new TransactionLog(); var cache = new PersistentCache(log); cache.Set("foo", "bar"); var written = log.History().Take(1).Single(); Assert.Equal("SET foo barrn", written); }
  • 102. [Fact] public void SetCommand() { var log = new TransactionLog(); var cache = new PersistentCache(log); cache.Set("foo", "bar"); var written = log.History().Take(1).Single(); Assert.Equal("SET foo barrn", written); }
  • 103. [Fact] public void SetCommand() { var log = new TransactionLog(); var cache = new PersistentCache(log); var monitor = new Monitor(log); cache.Set("foo", "bar"); var written = log.History().Take(1).Single(); Assert.Equal("SET foo barrn", written); }
  • 104. [Fact] public void SetCommand() { var log = new TransactionLog(); var cache = new PersistentCache(log); var monitor = new Monitor(log); cache.Set("foo", "bar"); var written = monitor.LastLog(); Assert.Equal("SET foo barrn", written); }
  • 105. WHEN SIDE EFFECTS AREN’T DIRECTLY RELATED?
  • 106. [Fact] public void CommandStatistics() { var channel = new StatisticsChannel(); var cache = new Cache(channel); var dashboard = new RealTimeDashboard(channel); cache.Set("foo", "bar"); var received = dashboard.LastReceived(); Assert.Equal("NAME: SET; ARGS: foo bar", received); }
  • 107. [Fact] public void CommandStatistics() { var channel = new StatisticsChannel(); var cache = new Cache(channel); var dashboard = new RealTimeDashboard(channel); cache.Set("foo", "bar"); var received = dashboard.LastReceived(); Assert.Equal("NAME: SET; ARGS: foo bar", received); }
  • 110. public interface ICacheSubscriber { void NotifyCommandExecuted(string name, string args); }
  • 111. public class CacheTests : ICacheSubscriber { string ExecutedCommandName; string ExecutedCommandArgs; public void NotifyCommandExecuted(string name, string args) { ExecutedCommandName = name; ExecutedCommandArgs = args; } // ... }
  • 112. [Fact] public void CommandStatistics() { var channel = new StatisticsChannel(); var cache = new Cache(channel); var dashboard = new RealTimeDashboard(channel); cache.Set("foo", "bar"); var received = dashboard.LastReceived(); Assert.Equal("NAME: SET; ARGS: foo bar", received); }
  • 113. [Fact] public void CommandStatistics() { var cache = new Cache(channel); cache.Set("foo", "bar"); Assert.Equal("NAME: SET; ARGS: foo bar", received); }
  • 114. [Fact] public void CommandStatistics() { var cache = new Cache(channel); cache.Set("foo", "bar"); Assert.Equal("NAME: SET; ARGS: foo bar", received); }
  • 115. [Fact] public void CommandStatistics() { ICacheSubscriber subscriber = this; var cache = new Cache(subscriber); cache.Set("foo", "bar"); Assert.Equal("NAME: SET; ARGS: foo bar", received); }
  • 116. [Fact] public void NotifyCommandExecution() { ICacheSubscriber subscriber = this; var cache = new Cache(subscriber); cache.Set("foo", "bar"); Assert.Equal("SET", this.ExecutedCommandName); Assert.Equal("foo bar", this.ExecutedCommandArgs); }
  • 118. [Fact] public void Overcrowding() { var moreThanThreeNeighbors = 6; var cell = new Cell(alive: true); var alive = cell.StayAlive(moreThanThreeNeighbors); Assert.False(alive); }
  • 119. [Fact] public void Overcrowding() { var moreThanThreeNeighbors = 6; var cell = new Cell(alive: true); var alive = cell.StayAlive(moreThanThreeNeighbors); Assert.False(alive); }
  • 121. [Fact] public void Overcrowding() { var moreThanThreeNeighbors = 6; var cell = new Cell(alive: true); var alive = cell.StayAlive(moreThanThreeNeighbors); Assert.False(alive); }
  • 122. [Fact] public void Overcrowding() { var moreThanThreeNeighbors = 6; var cell = Cell.Live(); var alive = cell.StayAlive(moreThanThreeNeighbors); Assert.False(alive); }
  • 124. 07 COMPLEX OBJECT CREATION
  • 125. [Fact] public void OrderCost() { var order = new Order(Location.TakeAway, new OrderLines( new OrderLine("Latte", new Quantity(1), Size.Small, new Pounds(1, 50)), new OrderLine("Cappuccino", new Quantity(2), Size.Large, new Pounds(2, 50)))); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  • 126. [Fact] public void OrderCost() { var order = new Order(Location.TakeAway, new OrderLines( new OrderLine("Latte", new Quantity(1), Size.Small, new Pounds(1, 50)), new OrderLine("Cappuccino", new Quantity(2), Size.Large, new Pounds(2, 50)))); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  • 128. [Fact] public void OrderCost() { var order = CreateOrderWithLatteAndCappuccino(); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  • 130. [Fact] public void OrderCost() { var order = TestOrders .CreateOrderWithLatteAndCappuccino(); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  • 132. Order CreateEmptyOrder() { /* ... */ } Order CreateOrderWithLatteAndCappuccino() { /* ... */ }
  • 133. Order CreateEmptyOrder() { /* ... */ } Order CreateOrderWithLatteAndCappuccino() { /* ... */ } Order CreateOrderWithLatteMultipleQuantities() { /* ... */ }
  • 134. Order CreateEmptyOrder() { /* ... */ } Order CreateOrderWithLatteAndCappuccino() { /* ... */ } Order CreateOrderWithLatteMultipleQuantities() { /* ... */ } Order CreateOrderWithCaffeSpecialDiscount() { /* ... */ }
  • 139. [Fact] public void OrderCost() { var order = CreateCommonOrder(); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  • 140. [Fact] public void DiscountedOrderCost() { var order = CreateCommonOrder(); order.Add(Discounts.ByQuantity(2)); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  • 141. [Fact] public void DiscountedOrderCost() { var order = CreateCommonOrder(); order.Add(Discounts.ByQuantity(2)); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  • 143. [Fact] public void OrderCost() { var order = new Order(Location.TakeAway, new OrderLines( new OrderLine("Latte", new Quantity(1), Size.Small, new Pounds(1, 50)), new OrderLine("Cappuccino", new Quantity(2), Size.Large, new Pounds(2, 50)))); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  • 144. DECOUPLE WHAT IS NEEDED
  • 145. FROM HOW IT IS STRUCTURED
  • 147. [Fact] public void OrderCost() { var order = new Order(Location.TakeAway, new OrderLines( new OrderLine("Latte", new Quantity(1), Size.Small, new Pounds(1, 50)), new OrderLine("Cappuccino", new Quantity(2), Size.Large, new Pounds(2, 50)))); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  • 148. [Fact] public void OrderCost() { var order = new OrderBuilder() .WithLocation(Location.TakeAway) .WithLine(new OrderLineBuilder() .WithName("Latte") .WithPrice(new Pounds(1, 50))) .WithLine(new OrderLineBuilder() .WithName("Cappuccino") .WithQuantity(new Quantity(2)) .WithSize(Size.Large) .WithPrice(new Pounds(1, 50))); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  • 151. [Fact] public void OrderCost() { var order = new OrderBuilder() .WithLocation(Location.TakeAway) .WithLine(new OrderLineBuilder() .WithName("Latte") .WithPrice(new Pounds(1, 50))) .WithLine(new OrderLineBuilder() .WithName("Cappuccino") .WithQuantity(new Quantity(2)) .WithSize(Size.Large) .WithPrice(new Pounds(1, 50))); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  • 152. [Fact] public void OrderCost() { var order = new OrderBuilder() .TakeAway() .WithOne("Latte", "1,50") .WithTwoLarge("Cappuccino", "1,50"); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  • 154. RECAP
  • 156. TEST CODE NEED LOVE
  • 157. LISTEN TO TEST CODE
  • 169. MATTEO BAGLINI FREELANCE SOFTWARE DEVELOPER TECHNICAL COACH @matteobaglini