Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.
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"); 
v...
[Fact] 
public void Subtract() 
{ 
var one = TimeRange.Parse("09:00-10:00"); 
var two = TimeRange.Parse("09:15-09:45"); 
v...
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"); 
v...
[Fact] 
public void Equality() 
{ 
var slot = TimeRange.Parse("09:00-10:00"); 
var same = TimeRange.Parse("09:00-10:00"); ...
[Fact] 
public void Subtract() 
{ 
var one = TimeRange.Parse("09:00-10:00"); 
var two = TimeRange.Parse("09:15-09:45"); 
v...
[Fact] 
public void Subtract() 
{ 
var one = TimeRange.Parse("09:00-10:00"); 
var two = TimeRange.Parse("09:15-09:45"); 
v...
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 = n...
[Fact] 
public void GetAllMessages() 
{ 
var messageId1 = Guid.NewGuid(); 
var messageId2 = Guid.NewGuid(); 
var store = n...
[Fact] 
public void Equality() 
{ 
var message = DummyMessage(Guid.NewGuid()); 
var same = DummyMessage(message.Id); 
var ...
[Fact] 
public void GetAllMessages() 
{ 
var messageId1 = Guid.NewGuid(); 
var messageId2 = Guid.NewGuid(); 
var store = n...
[Fact] 
public void GetAllMessages() 
{ 
var messageId1 = Guid.NewGuid(); 
var messageId2 = Guid.NewGuid(); 
var store = n...
WHEN YOU 
CAN’T CHANGE 
THE OBJECT?
[Fact] 
public void GetContactsDetails() 
{ 
var controller = new ContactsController(); 
var result = controller.Details(G...
[Fact] 
public void GetContactsDetails() 
{ 
var controller = new ContactsController(); 
var result = controller.Details(G...
[Fact] 
public void GetContactsDetails() 
{ 
var controller = new ContactsController(); 
var result = controller.Details(G...
02 
TESTING ONLY 
ONE CONCERN
[Fact] 
public void SingleElement() 
{ 
var input = "<element />"; 
var tag = Tag.Parse(input); 
Assert.True(tag.IsEmpty()...
[Fact] 
public void SingleElement() 
{ 
var input = "<element />"; 
var tag = Tag.Parse(input); 
Assert.True(tag.IsEmpty()...
TEST CAN 
FAIL FOR 
TOO MANY 
REASONS
[Fact] 
public void SingleElement() 
{ 
var input = "<element />"; 
var tag = Tag.Parse(input); 
Assert.True(tag.IsEmpty()...
[Fact] 
public void SingleElementNoContent() 
{ 
var input = "<element />"; 
var tag = Tag.Parse(input); 
Assert.True(tag....
[Fact] 
public void SingleElementName() 
{ 
var input = "<element />"; 
var tag = Tag.Parse(input); 
Assert.Equal("element...
[Fact] 
public void SingleElementNoChildren() 
{ 
var input = "<element />"; 
var tag = Tag.Parse(input); 
Assert.Empty(ta...
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....
FOCUS ON 
TESTING 
BEHAVIOUR
[Fact] 
public void PopOneItem() 
{ 
var item = 5; 
var stack = new Stack(); 
stack.Push(item); 
Assert.Equal(item, stack....
[Fact] 
public void PopManyItems() 
{ 
var items = new[] { 1, 2, 3 }; 
var stack = new Stack(); 
stack.Push(items[0]); 
st...
[Fact] 
public void PopEmptyStack() 
{ 
var stack = new Stack(); 
Assert 
.Throws<InvalidOperationException>(() => stack.P...
04 
AVOID TESTING 
PRIVATE METHODS
public class Invoice 
{ 
// ... 
public Eur Total() 
{ 
var gross = ComputeGrossAmount(/* args */); 
var tax = ComputeTaxA...
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 vo...
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 ...
NOW TEST 
EXTRACTED 
OBJECTS
05 
SAME LEVEL 
OF ABSTRACTION
[Fact] 
public void SetCommand() 
{ 
var cache = new Cache(); 
var dispatcher = new CommandDispatcher(cache); 
dispatcher....
[Fact] 
public void SetCommand() 
{ 
var cache = new Cache(); 
var dispatcher = new CommandDispatcher(cache); 
dispatcher....
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....
[Fact] 
public void SetCommand() 
{ 
var cache = new Cache(); 
var dispatcher = new CommandDispatcher(cache); 
dispatcher....
WHEN IT 
ISN’T YOUR 
RESPONSIBILITY?
[Fact] 
public void SetCommand() 
{ 
var log = new TransactionLog(); 
var cache = new PersistentCache(log); 
cache.Set("fo...
[Fact] 
public void SetCommand() 
{ 
var log = new TransactionLog(); 
var cache = new PersistentCache(log); 
cache.Set("fo...
PURE 
FABRICATION
[Fact] 
public void SetCommand() 
{ 
var log = new TransactionLog(); 
var cache = new PersistentCache(log); 
cache.Set("fo...
[Fact] 
public void SetCommand() 
{ 
var log = new TransactionLog(); 
var cache = new PersistentCache(log); 
cache.Set("fo...
[Fact] 
public void SetCommand() 
{ 
var log = new TransactionLog(); 
var cache = new PersistentCache(log); 
var monitor =...
[Fact] 
public void SetCommand() 
{ 
var log = new TransactionLog(); 
var cache = new PersistentCache(log); 
var monitor =...
WHEN 
SIDE EFFECTS 
AREN’T DIRECTLY 
RELATED?
[Fact] 
public void CommandStatistics() 
{ 
var channel = new StatisticsChannel(); 
var cache = new Cache(channel); 
var d...
[Fact] 
public void CommandStatistics() 
{ 
var channel = new StatisticsChannel(); 
var cache = new Cache(channel); 
var d...
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 Notif...
[Fact] 
public void CommandStatistics() 
{ 
var channel = new StatisticsChannel(); 
var cache = new Cache(channel); 
var d...
[Fact] 
public void CommandStatistics() 
{ 
var cache = new Cache(channel); 
cache.Set("foo", "bar"); 
Assert.Equal("NAME:...
[Fact] 
public void CommandStatistics() 
{ 
var cache = new Cache(channel); 
cache.Set("foo", "bar"); 
Assert.Equal("NAME:...
[Fact] 
public void CommandStatistics() 
{ 
ICacheSubscriber subscriber = this; 
var cache = new Cache(subscriber); 
cache...
[Fact] 
public void NotifyCommandExecution() 
{ 
ICacheSubscriber subscriber = this; 
var cache = new Cache(subscriber); 
...
06 
RESPECT 
ABSTRACTION
[Fact] 
public void Overcrowding() 
{ 
var moreThanThreeNeighbors = 6; 
var cell = new Cell(alive: true); 
var alive = cel...
[Fact] 
public void Overcrowding() 
{ 
var moreThanThreeNeighbors = 6; 
var cell = new Cell(alive: true); 
var alive = cel...
SEGREGATE 
DECISIONS
[Fact] 
public void Overcrowding() 
{ 
var moreThanThreeNeighbors = 6; 
var cell = new Cell(alive: true); 
var alive = cel...
[Fact] 
public void Overcrowding() 
{ 
var moreThanThreeNeighbors = 6; 
var cell = Cell.Live(); 
var alive = cell.StayAliv...
INFORMATION 
HIDING
07 
COMPLEX 
OBJECT 
CREATION
[Fact] 
public void OrderCost() 
{ 
var order = new Order(Location.TakeAway, 
new OrderLines( 
new OrderLine("Latte", 
new...
[Fact] 
public void OrderCost() 
{ 
var order = new Order(Location.TakeAway, 
new OrderLines( 
new OrderLine("Latte", 
new...
EXTRACT 
FACTORY METHOD
[Fact] 
public void OrderCost() 
{ 
var order = CreateOrderWithLatteAndCappuccino(); 
var result = order.Cost(); 
Assert.E...
OBJECT 
MOTHER
[Fact] 
public void OrderCost() 
{ 
var order = TestOrders 
.CreateOrderWithLatteAndCappuccino(); 
var result = order.Cost...
Order CreateEmptyOrder() { /* ... */ }
Order CreateEmptyOrder() { /* ... */ } 
Order CreateOrderWithLatteAndCappuccino() { /* ... */ }
Order CreateEmptyOrder() { /* ... */ } 
Order CreateOrderWithLatteAndCappuccino() { /* ... */ } 
Order CreateOrderWithLatt...
Order CreateEmptyOrder() { /* ... */ } 
Order CreateOrderWithLatteAndCappuccino() { /* ... */ } 
Order CreateOrderWithLatt...
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(...
[Fact] 
public void DiscountedOrderCost() 
{ 
var order = CreateCommonOrder(); 
order.Add(Discounts.ByQuantity(2)); 
var r...
[Fact] 
public void DiscountedOrderCost() 
{ 
var order = CreateCommonOrder(); 
order.Add(Discounts.ByQuantity(2)); 
var r...
SAME FAILURE
[Fact] 
public void OrderCost() 
{ 
var order = new Order(Location.TakeAway, 
new OrderLines( 
new OrderLine("Latte", 
new...
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...
[Fact] 
public void OrderCost() 
{ 
var order = new OrderBuilder() 
.WithLocation(Location.TakeAway) 
.WithLine(new OrderL...
STILL 
EXPOSE 
STRUCTURE
INTRODUCE 
HIGHER LEVEL 
METHODS
[Fact] 
public void OrderCost() 
{ 
var order = new OrderBuilder() 
.WithLocation(Location.TakeAway) 
.WithLine(new OrderL...
[Fact] 
public void OrderCost() 
{ 
var order = new OrderBuilder() 
.TakeAway() 
.WithOne("Latte", "1,50") 
.WithTwoLarge(...
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
Nächste SlideShare
Wird geladen in …5
×

Writing Good Tests

1.958 Aufrufe

Veröffentlicht am

Se guardiamo oltre la meccanica, il TDD è una tecnica complessa perché richiede molteplici skill. Da principiante dopo l’implementazione di poche storie ti imbatti nel problema dei test che si rompono ad ogni refactoring, è arrivato il momento di migliorare i propri skill di scrittura dei test. Nel talk analizzeremo la struttura dei test, quali sono le bad smell più comuni e come porvi rimedio.

Veröffentlicht in: Software
  • Als Erste(r) kommentieren

Writing Good Tests

  1. 1. WRITING GOOD TESTS @matteobaglini #IAD14
  2. 2. TEST-DRIVEN DEVELOPMENT
  3. 3. @iacoware
  4. 4. TDD MECHANICS
  5. 5. TDD IS A MULTIFACETED SKILL
  6. 6. SKILL ON CLEAN CODE
  7. 7. SKILL ON REFACTORING SAFELY
  8. 8. SKILL ON DISCOVER TEST CASES
  9. 9. SKILL ON CHOOSE NEXT TESTS
  10. 10. SKILL ON WRITING TESTS
  11. 11. SKILL ON WRITING GOOD TESTS
  12. 12. FOCUS ON WRITING GOOD TESTS
  13. 13. PROPERTIES
  14. 14. TESTS MUST BE ISOLATED
  15. 15. TESTS MUST BE INDIPENDENT
  16. 16. TESTS MUST BE DETERMINISTIC
  17. 17. AFFECT EXECUTION
  18. 18. WHAT ABOUT TEST CODE?
  19. 19. BAD CODE SMELLS
  20. 20. BAD TEST CODE SMELLS
  21. 21. LEAD TO FRAGILITY ON REFACTORING
  22. 22. WHY SHOULD WE CARE?
  23. 23. WHAT‘S AGILE PRACTICES’ ULTIMATE GOAL?
  24. 24. REDUCE THE COST AND RISK OF CHANGE
  25. 25. FRAGILE TESTS INCREASE COSTS
  26. 26. FRAGILE TESTS LOSS OF CONFIDENCE
  27. 27. FRAGILE TESTS REDUCE SUSTAINABILITY
  28. 28. PAY OFF YOUR TECHNICAL DEBT
  29. 29. WHERE IS COMPLEXITY?
  30. 30. [Fact] public void ScenarioUnderTest() { // Arrange // Act // Assert }
  31. 31. START FROM ASSERT
  32. 32. 01 COMPARING OBJECTS
  33. 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. 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. 35. WHICH KIND OF OBJECTS?
  36. 36. VALUE OBJECTS
  37. 37. VALUE OBJECTS MODEL FIXED QUANTITIES
  38. 38. VALUE OBJECTS IMMUTABLE
  39. 39. VALUE OBJECTS NO IDENTITY
  40. 40. VALUE OBJECTS EQUAL IF THEY HAVE SAME STATE
  41. 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. 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. 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. 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); }
  45. 45. OBJECTS
  46. 46. OBJECTS MODEL STATE AND PROCESSING OVER TIME
  47. 47. OBJECTS MUTABLE
  48. 48. OBJECTS HAS IDENTITY
  49. 49. OBJECTS EQUAL IF THEY HAVE SAME IDENTITY
  50. 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. 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. 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. 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. 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. 55. WHEN YOU CAN’T CHANGE THE OBJECT?
  56. 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. 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. 58. [Fact] public void GetContactsDetails() { var controller = new ContactsController(); var result = controller.Details(Guid.NewGuid()); MvcAssert.IsViewResult<ContactModel>(result); }
  59. 59. 02 TESTING ONLY ONE CONCERN
  60. 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. 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. 62. TEST CAN FAIL FOR TOO MANY REASONS
  63. 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. 64. [Fact] public void SingleElementNoContent() { var input = "<element />"; var tag = Tag.Parse(input); Assert.True(tag.IsEmpty()); }
  65. 65. [Fact] public void SingleElementName() { var input = "<element />"; var tag = Tag.Parse(input); Assert.Equal("element", tag.Name()); }
  66. 66. [Fact] public void SingleElementNoChildren() { var input = "<element />"; var tag = Tag.Parse(input); Assert.Empty(tag.ChildNodes()); }
  67. 67. 03 TESTING BEHAVIOUR
  68. 68. STATE VS BEHAVIOUR
  69. 69. FOCUS ON TESTING STATE
  70. 70. [Fact] public void NewStackIsEmpty() { var stack = new Stack(); Assert.True(stack.IsEmpty()); }
  71. 71. [Fact] public void StackIsNotEmptyAfterAddItem() { var stack = new Stack(); stack.Push(5); Assert.False(stack.IsEmpty()); }
  72. 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. 73. FOCUS ON TESTING BEHAVIOUR
  74. 74. [Fact] public void PopOneItem() { var item = 5; var stack = new Stack(); stack.Push(item); Assert.Equal(item, stack.Pop()); }
  75. 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. 76. [Fact] public void PopEmptyStack() { var stack = new Stack(); Assert .Throws<InvalidOperationException>(() => stack.Pop()); }
  77. 77. 04 AVOID TESTING PRIVATE METHODS
  78. 78. public class Invoice { // ... public Eur Total() { var gross = ComputeGrossAmount(/* args */); var tax = ComputeTaxAmount(/* args */); return gross.Add(tax); } }
  79. 79. public class Invoice { // ... Eur ComputeGrossAmount(/* args */) { /* many lines of complex * and commented code */ } }
  80. 80. public class Invoice { // ... Eur ComputeTaxAmount(/* args */) { /* many lines of complex * and commented code */ } }
  81. 81. [Fact] public void GrossAmountCalculation() { // We want invoke private ComputeGrossAmount method } [Fact] public void TaxAmountCalculation() { // We want invoke private ComputeTaxAmount method }
  82. 82. DON’T DO IT!
  83. 83. DON’T DO IT!
  84. 84. DON’T DO IT!
  85. 85. CONSIDER APPLYING METHOD OBJECT
  86. 86. MAKE AN OBJECT OUT OF THE METHOD
  87. 87. public class Invoice { // ... public Eur Total() { var gross = new GrossAmount(/* args */).Compute(); var tax = new TaxAmount(/* args */).Compute(); return gross.Add(tax); } }
  88. 88. NOW TEST EXTRACTED OBJECTS
  89. 89. 05 SAME LEVEL OF ABSTRACTION
  90. 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. 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. 92. ACT AT HIGHER ASSERT AT LOWER
  93. 93. REMEMBER DIP?
  94. 94. DON’T CROSS THE STREAM
  95. 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. 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. 97. WHEN IT ISN’T YOUR RESPONSIBILITY?
  98. 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. 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); }
  100. 100. PURE FABRICATION
  101. 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. 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. 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. 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. 105. WHEN SIDE EFFECTS AREN’T DIRECTLY RELATED?
  106. 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. 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); }
  108. 108. BREAK THE FLOW
  109. 109. OBSERVER PLUS SELF SHUNT
  110. 110. public interface ICacheSubscriber { void NotifyCommandExecuted(string name, string args); }
  111. 111. public class CacheTests : ICacheSubscriber { string ExecutedCommandName; string ExecutedCommandArgs; public void NotifyCommandExecuted(string name, string args) { ExecutedCommandName = name; ExecutedCommandArgs = args; } // ... }
  112. 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. 113. [Fact] public void CommandStatistics() { var cache = new Cache(channel); cache.Set("foo", "bar"); Assert.Equal("NAME: SET; ARGS: foo bar", received); }
  114. 114. [Fact] public void CommandStatistics() { var cache = new Cache(channel); cache.Set("foo", "bar"); Assert.Equal("NAME: SET; ARGS: foo bar", received); }
  115. 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. 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); }
  117. 117. 06 RESPECT ABSTRACTION
  118. 118. [Fact] public void Overcrowding() { var moreThanThreeNeighbors = 6; var cell = new Cell(alive: true); var alive = cell.StayAlive(moreThanThreeNeighbors); Assert.False(alive); }
  119. 119. [Fact] public void Overcrowding() { var moreThanThreeNeighbors = 6; var cell = new Cell(alive: true); var alive = cell.StayAlive(moreThanThreeNeighbors); Assert.False(alive); }
  120. 120. SEGREGATE DECISIONS
  121. 121. [Fact] public void Overcrowding() { var moreThanThreeNeighbors = 6; var cell = new Cell(alive: true); var alive = cell.StayAlive(moreThanThreeNeighbors); Assert.False(alive); }
  122. 122. [Fact] public void Overcrowding() { var moreThanThreeNeighbors = 6; var cell = Cell.Live(); var alive = cell.StayAlive(moreThanThreeNeighbors); Assert.False(alive); }
  123. 123. INFORMATION HIDING
  124. 124. 07 COMPLEX OBJECT CREATION
  125. 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. 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); }
  127. 127. EXTRACT FACTORY METHOD
  128. 128. [Fact] public void OrderCost() { var order = CreateOrderWithLatteAndCappuccino(); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  129. 129. OBJECT MOTHER
  130. 130. [Fact] public void OrderCost() { var order = TestOrders .CreateOrderWithLatteAndCappuccino(); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  131. 131. Order CreateEmptyOrder() { /* ... */ }
  132. 132. Order CreateEmptyOrder() { /* ... */ } Order CreateOrderWithLatteAndCappuccino() { /* ... */ }
  133. 133. Order CreateEmptyOrder() { /* ... */ } Order CreateOrderWithLatteAndCappuccino() { /* ... */ } Order CreateOrderWithLatteMultipleQuantities() { /* ... */ }
  134. 134. Order CreateEmptyOrder() { /* ... */ } Order CreateOrderWithLatteAndCappuccino() { /* ... */ } Order CreateOrderWithLatteMultipleQuantities() { /* ... */ } Order CreateOrderWithCaffeSpecialDiscount() { /* ... */ }
  135. 135. POOR NAME
  136. 136. LOSS OF CONTEXT
  137. 137. DOESN’T SCALE
  138. 138. COMMON SHARED FIXTURE
  139. 139. [Fact] public void OrderCost() { var order = CreateCommonOrder(); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  140. 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. 141. [Fact] public void DiscountedOrderCost() { var order = CreateCommonOrder(); order.Add(Discounts.ByQuantity(2)); var result = order.Cost(); Assert.Equal(new Pounds(6, 50), result); }
  142. 142. SAME FAILURE
  143. 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. 144. DECOUPLE WHAT IS NEEDED
  145. 145. FROM HOW IT IS STRUCTURED
  146. 146. BUILDER PATTERN
  147. 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. 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); }
  149. 149. STILL EXPOSE STRUCTURE
  150. 150. INTRODUCE HIGHER LEVEL METHODS
  151. 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. 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); }
  153. 153. DOMAIN SPECIFIC LANGUAGE
  154. 154. RECAP
  155. 155. STOP THE MADNESS
  156. 156. TEST CODE NEED LOVE
  157. 157. LISTEN TO TEST CODE
  158. 158. TOO MUCH INTIMACY
  159. 159. EXPRESS INTENT
  160. 160. FOCUS ON WHAT
  161. 161. NOT ON HOW
  162. 162. REMOVE DUPLICATION
  163. 163. INTRODUCE ABSTRACTIONS
  164. 164. RESPECT ABSTRACTION LEVELS
  165. 165. EVOLVE TEST INFRASTRUCTURE
  166. 166. BABY STEPS
  167. 167. CONTINUOUS REFACTORING
  168. 168. IMPROVE YOUR SKILLS
  169. 169. MATTEO BAGLINI FREELANCE SOFTWARE DEVELOPER TECHNICAL COACH @matteobaglini
  170. 170. https://joind.in/talk/view/12770

×