Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

Structure and Interpretation of Test Cases

852 visualizaciones

Publicado el

Presented at ACCU Cambridge (2018-10-23)

Throw a line of code into many codebases and it's sure to hit one or more testing frameworks. There's no shortage of frameworks for testing, each with their particular spin and set of conventions, but that glut is not always matched by a clear vision of how to structure and use tests — a framework is a vehicle, but you still need to know how to drive. The computer science classic, Structure and Interpretation of Computer Programs, points out that "Programs must be written for people to read, and only incidentally for machines to execute". The same is true of test code.

This talk takes a deep dive into unit testing, looking at examples and counterexamples across a number of languages and frameworks, from naming to nesting, exploring the benefits of data-driven testing, the trade-offs between example-based and property-based testing, how to get the most out of the common given–when–then refrain and knowing how far to follow it.

Publicado en: Software
  • Sé el primero en comentar

Structure and Interpretation of Test Cases

  1. 1. Structure and Interpretation of Test Cases @KevlinHenney
  2. 2. Programs must be written for people to read, and only incidentally for machines to execute.
  3. 3. Write Tests for People Gerard Meszaros
  4. 4. So who should you be writing the tests for? For the person trying to understand your code. Good tests act as documentation for the code they are testing. Gerard Meszaros
  5. 5. Very many people say "TDD" when they really mean, "I have good unit tests" ("I have GUTs"?). Alistair Cockburn The modern programming professional has GUTs
  6. 6. good unit tests" ("I have GUTs
  7. 7. A unit test is a test of behaviour whose success or failure is wholly determined by the correctness of the test and the correctness of the unit under test. Kevlin Henney https://www.theregister.co.uk/2007/07/28/what_are_your_units/
  8. 8. Unit testable Necessarily not unit testable Should be unit testable... but isn’t
  9. 9. The programmer in me made unit testing more about applying and exercising frameworks. I had essentially reduced my concept of unit testing to the basic mechanics of exercising xUnit to verify the behavior of my classes. My mindset had me thinking far too narrowly about what it meant to write good unit tests. Tod Golding Tapping into Testing Nirvana
  10. 10. about what it meant to write good unit tests
  11. 11. From time to time I hear people asking what value of test coverage (also called code coverage) they should aim for, or stating their coverage levels with pride. Such statements miss the point. Martin Fowler http://martinfowler.com/bliki/TestCoverage.html
  12. 12. I expect a high level of coverage. Sometimes managers require one. There's a subtle difference. Brian Marick http://martinfowler.com/bliki/TestCoverage.html
  13. 13. public static bool IsLeapYear(int year) 
  14. 14. [Test] public void Test() 
  15. 15. [Test] public void TestIsLeapYear() 
  16. 16. [Test] public void TestIsLeapYearIsCorrect() 
  17. 17. [Test] public void Test1()  [Test] public void Test2() 
  18. 18. [Test] public void TestLeapYears()  [Test] public void TestNonLeapYears() 
  19. 19. [Test] public void TestLeapYears() { Assert.IsTrue(IsLeapYear(2016)); Assert.IsTrue(IsLeapYear(2000)); } [Test] public void TestNonLeapYears() 
  20. 20. [Test] public void Test2016IsALeapYear()  [Test] public void Test2000IsALeapYear()  [Test] public void Test2018IsNotALeapYear()  [Test] public void Test1900IsNotALeapYear() 
  21. 21. [Test] public void _2016IsALeapYear()  [Test] public void _2000IsALeapYear()  [Test] public void _2018IsNotALeapYear()  [Test] public void _1900IsNotALeapYear() 
  22. 22. [Test] public void _2016_is_a_leap_year()  [Test] public void _2000_is_a_leap_year()  [Test] public void _2018_is_not_a_leap_year()  [Test] public void _1900_is_not_a_leap_year() 
  23. 23. _2016_is_a_leap_year _2000_is_a_leap_year _2018_is_not_a_leap_year _1900_is_not_a_leap_year
  24. 24. _2020_is_a_leap_year _2400_is_a_leap_year _2019_is_not_a_leap_year _2100_is_not_a_leap_year
  25. 25. Years_divisible_by_4_are_leap_years Years_divisible_by_400_are_leap_years Years_not_divisible_by_4_are_not_leap_years Years_divisible_by_100_are_not_leap_years
  26. 26. Years_not_divisible_by_4_are_not_leap_years Years_divisible_by_4_are_leap_years Years_divisible_by_100_are_not_leap_years Years_divisible_by_400_are_leap_years
  27. 27. Years_not_divisible_by_4_are_not_leap_years Years_divisible_by_4_but_not_by_100_are_leap_year Years_divisible_by_100_but_not_by_400_are_not_lea Years_divisible_by_400_are_leap_years
  28. 28. Years_not_divisible_by_4_are_not_leap_years Years_divisible_by_4_but_not_by_100_are_leap_years Years_divisible_by_100_but_not_by_400_are_not_leap_years Years_divisible_by_400_are_leap_years
  29. 29. IsLeapYear_YearNotDivisibleBy4_ReturnsFalse IsLeapYear_YearDivisibleBy4ButNotBy100_ReturnsTrue IsLeapYear_YearDivisibleBy100ButNotBy400_ReturnsFalse IsLeapYear_YearDivisibleBy400_ReturnsTrue
  30. 30. Propositions are vehicles for stating how things are or might be.
  31. 31. Thus only indicative sentences which it makes sense to think of as being true or as being false are capable of expressing propositions.
  32. 32. Years_not_divisible_by_4_are_not_leap_years Years_divisible_by_4_but_not_by_100_are_leap_years Years_divisible_by_100_but_not_by_400_are_not_leap_years Years_divisible_by_400_are_leap_years
  33. 33. public static bool IsLeapYear(int year) { return year % 4 == 0 && year % 100 != 0 || year % 400 == 0; }
  34. 34. Years_not_divisible_by_4_are_not_leap_years Years_divisible_by_4_but_not_by_100_are_leap_years Years_divisible_by_100_but_not_by_400_are_not_leap_years Years_divisible_by_400_are_leap_years
  35. 35. public static bool IsLeapYear(int year) { return year % 4 == 0; }
  36. 36. Years_not_divisible_by_4_are_not_leap_years Years_divisible_by_4_but_not_by_100_are_leap_years Years_divisible_by_100_but_not_by_400_are_not_leap_years Years_divisible_by_400_are_leap_years
  37. 37. For tests to drive development they must do more than just test that code performs its required functionality: they must clearly express that required functionality to the reader. That is, they must be clear specifications of the required functionality. Nat Pryce & Steve Freeman Are your tests really driving your development?
  38. 38. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year { [Test] public void if_it_is_divisible_by_4_but_not_by_100()  [Test] public void if_it_is_divisible_by_400()  } [TestFixture] public class A_year_is_not_a_leap_year { [Test] public void if_it_is_not_divisible_by_4()  [Test] public void if_it_is_divisible_by_100_but_not_by_400()  } }
  39. 39. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year { [Test] public void if_it_is_divisible_by_4_but_not_by_100()  [Test] public void if_it_is_divisible_by_400()  } [TestFixture] public class A_year_is_not_a_leap_year { [Test] public void if_it_is_not_divisible_by_4()  [Test] public void if_it_is_divisible_by_100_but_not_by_400()  } }
  40. 40. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year  [TestFixture] public class A_year_is_not_a_leap_year { [Test] public void if_it_is_not_divisible_by_4() { Assert.IsFalse(IsLeapYear(2018)); } [Test] public void if_it_is_divisible_by_100_but_not_by_400() { Assert.IsFalse(IsLeapYear(1900)); } } }
  41. 41. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year  [TestFixture] public class A_year_is_not_a_leap_year { [Test] public void if_it_is_not_divisible_by_4() { Assert.IsFalse(isLeapYear(2018)); } [Test] public void if_it_is_divisible_by_100_but_not_by_400() { Assert.IsFalse(isLeapYear(1900)); } } }
  42. 42. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year  [TestFixture] public class A_year_is_not_a_leap_year { [Test] public void if_it_is_not_divisible_by_4() { Assert.IsFalse(isLeapYear(42)); } [Test] public void if_it_is_divisible_by_100_but_not_by_400() { Assert.IsFalse(isLeapYear(100)); } } }
  43. 43. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year  [TestFixture] public class A_year_is_not_a_leap_year { [Test] public void if_it_is_not_divisible_by_4() { Assert.IsFalse(IsLeapYear(2018)); Assert.IsFalse(IsLeapYear(2017)); Assert.IsFalse(IsLeapYear(42)); Assert.IsFalse(IsLeapYear(1)); } [Test] public void if_it_is_divisible_by_100_but_not_by_400()  } }
  44. 44. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year  [TestFixture] public class A_year_is_not_a_leap_year { [TestCase(2018)] [TestCase(2017)] [TestCase(42)] [TestCase(1)] public void if_it_is_not_divisible_by_4(int year) { Assert.IsFalse(IsLeapYear(year)); } [Test] public void if_it_is_divisible_by_100_but_not_by_400()  } }
  45. 45. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year  [TestFixture] public class A_year_is_not_a_leap_year { [Test] public void if_it_is_not_divisible_by_4( [Values(2018, 2017, 42, 1)] int year) { Assert.IsFalse(IsLeapYear(year)); } [Test] public void if_it_is_divisible_by_100_but_not_by_400()  } }
  46. 46. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year { [Test] public void if_it_is_divisible_by_4_but_not_by_100( [Values(2016, 1984, 4)] int year)  [Test] public void if_it_is_divisible_by_400( [Range(400, 2400, 400)] int year)  } [TestFixture] public class A_year_is_not_a_leap_year { [Test] public void if_it_is_not_divisible_by_4( [Values(2018, 2017, 42, 1)] int year)  [Test] public void if_it_is_divisible_by_100_but_not_by_400( [Values(2100, 1900, 100)] int year)  } }
  47. 47. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year { [Test] public void if_it_is_divisible_by_4_but_not_by_100( [Values(2016, 1984, 4)] int year)  [Test] public void if_it_is_divisible_by_400( [Range(400, 2400, 400)] int year)  } [TestFixture] public class A_year_is_not_a_leap_year { [Test] public void if_it_is_not_divisible_by_4( [Values(2018, 2017, 42, 1)] int year)  [Test] public void if_it_is_divisible_by_100_but_not_by_400( [Values(2100, 1900, 100)] int year)  } }
  48. 48. namespace Leap_year_spec { [TestFixture] public class A_year { [Test] public void is_either_a_leap_year_or_not([Range(1, 10000)] int year)  } }
  49. 49. namespace Leap_year_spec { [TestFixture] public class A_year { [Test] public void is_either_a_leap_year_or_not([Range(1, 10000)] int year)  } }
  50. 50. namespace Leap_year_spec { [TestFixture] public class A_year { [Test] public void is_either_a_leap_year_or_not([Range(1, 10000)] int year) { Assert.AreEqual( year % 4 == 0 && year % 100 != 0 || year % 400 == 0, IsLeapYear(year)); } } }
  51. 51. namespace Leap_year_spec { [TestFixture] public class A_year { [Test] public void is_either_a_leap_year_or_not([Range(1, 10000)] int year) { Assert.AreEqual(LeapYearExpectation(year), IsLeapYear(year)); } public static bool LeapYearExpectation(int year) { return year % 4 == 0 && year % 100 != 0 || year % 400 == 0; } } }
  52. 52. public static bool IsLeapYear(int year) { return year % 4 == 0 && year % 100 != 0 || year % 400 == 0; }
  53. 53. All happy families are alike; each unhappy family is unhappy in its own way. Leo Tolstoy Anna Karenina
  54. 54. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year  [TestFixture] public class A_year_is_not_a_leap_year  [TestFixture] public class A_year_is_not_supported { [Test] public void if_it_is_0() { Assert.Throws<ArgumentException>(() => IsLeapYear(0)); } } }
  55. 55. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year  [TestFixture] public class A_year_is_not_a_leap_year  [TestFixture] public class A_year_is_not_supported { [Test] public void if_it_is_0() { Assert.Catch<ArgumentException>(() => IsLeapYear(0)); } } }
  56. 56. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year  [TestFixture] public class A_year_is_not_a_leap_year  [TestFixture] public class A_year_is_not_supported { [Test] public void if_it_is_0() { Assert.Catch<ArgumentException>(() => IsLeapYear(0)); } [Test] public void if_it_is_negative( [Values(-1, -4, -100, -400)] int year) { Assert.Catch<ArgumentException>(() => IsLeapYear(year)); } } }
  57. 57. namespace Leap_year_spec { [TestFixture] public class A_year_is_a_leap_year  [TestFixture] public class A_year_is_not_a_leap_year  [TestFixture] public class A_year_is_supported { [Test] public void if_it_is_positive( [Values(1, int.MaxValue)] int year) { Assert.DoesNotThrow(() => IsLeapYear(year)); } } [TestFixture] public class A_year_is_not_supported  }
  58. 58. Stack {new, push, pop, depth, top}
  59. 59. An abstract data type defines a class of abstract objects which is completely characterized by the operations available on those objects. Barbara Liskov Programming with Abstract Data Types
  60. 60.  T •  Stack { new: Stack[T], push: Stack[T]  T → Stack[T], pop: Stack[T] ⇸ Stack[T], depth: Stack[T] → Integer, top: Stack[T] ⇸ T }
  61. 61. template<typename T> class stack { public: stack(); void push(const T & new_top); void pop(); std::size_t depth() const; T top() const; private: ... };
  62. 62. TEST_CASE(“test stack::stack”)  TEST_CASE(“test stack::push”)  TEST_CASE(“test stack::pop”)  TEST_CASE(“test stack::depth”)  TEST_CASE(“test stack::top”) 
  63. 63. TEST_CASE(“stack tests”) { SECTION(“test constructor”)  SECTION(“test push”)  SECTION(“test pop”)  SECTION(“test depth”)  SECTION(“test top”)  }
  64. 64. TEST_CASE(“stack tests”) { SECTION(“constructor”)  SECTION(“push”)  SECTION(“pop”)  SECTION(“depth”)  SECTION(“top”)  }
  65. 65. template<typename T> class stack { public: stack(); void push(const T & new_top); void pop(); std::size_t depth() const; T top() const; private: ... };
  66. 66. template<typename T> class stack { public: stack() = default; void push(const T & new_top); void pop(); std::size_t depth() const; T top() const; private: ... };
  67. 67. TEST_CASE(“stack tests”) { SECTION(“constructor”)  SECTION(“push”)  SECTION(“pop”)  SECTION(“depth”)  SECTION(“top”)  }
  68. 68. TEST_CASE(“stack tests”) { SECTION(“push”)  SECTION(“pop”)  SECTION(“depth”)  SECTION(“top”)  }
  69. 69. TEST_CASE(“stack tests”) { SECTION(“can be constructed”)  SECTION(“can be pushed”)  SECTION(“can be popped”)  SECTION(“has depth”)  SECTION(“has a top”)  }
  70. 70. TEST_CASE(“stack tests”) { SECTION(“can be constructed”)  SECTION(“can be pushed”)  SECTION(“can sometimes be popped”)  SECTION(“has depth”)  SECTION(“sometimes has a top”)  }
  71. 71. https://twitter.com/chrisoldwood/status/918139432447954944
  72. 72. Given When Then an empty stack an item is pushed it should not be empty
  73. 73. Given When Then an empty stack an item is pushed if it's OK with you, I think that, perhaps, it should probably not be empty, don't you think?
  74. 74. Make definite assertions. Avoid tame, colourless, hesitating, noncommittal language. When a sentence is made stronger, it usually becomes shorter. Thus brevity is a by-product of vigour. William Strunk and E B White The Elements of Style
  75. 75. Given When Then an empty stack an item is pushed it must not be empty
  76. 76. Given When Then an empty stack an item is pushed it is not empty
  77. 77. Given When Then And an empty stack an item is pushed it has a depth of 1 the top item is the item that was pushed
  78. 78. GivenAnEmptyStackWhenAnI temIsPushedThenItHasADep thOf1AndTheTopItemIsTheI temThatWasPushed
  79. 79. Given_an_empty_stack_Whe n_an_item_is_pushed_Then _it_has_a_depth_of_1_And _the_top_item_is_the_ite m_that_was_pushed
  80. 80. Given an empty stack When an item is pushed Then it has a depth of 1 And the top item is the item that was pushed
  81. 81. An empty stack acquires depth by retaining a pushed item as its top
  82. 82. Omit needless words. William Strunk and E B White The Elements of Style
  83. 83. TEST_CASE(“Stack specification”) {  SECTION(“An empty stack acquires depth by retaining a pushed item as its top") { stack<std::string> stack; stack.push("ACCU"); REQUIRE(stack.depth() == 1); REQUIRE(stack.top() == "ACCU"); }  }
  84. 84. TEST_CASE(“Stack specification”) { SECTION(“A new stack is empty”)  SECTION(“An empty stack throws when queried for its top item”)  SECTION(“An empty stack throws when popped”)  SECTION(“An empty stack acquires depth by retaining a pushed item as its top”)  SECTION(“A non-empty stack becomes deeper by retaining a pushed item as its top”)  SECTION(“A non-empty stack on popping reveals tops in reverse order of pushing”)  }
  85. 85. TEST_CASE(“Stack specification”) { SECTION(“A new stack is empty”)  SECTION(“An empty stack throws when queried for its top item”)  SECTION(“An empty stack throws when popped”)  SECTION(“An empty stack acquires depth by retaining a pushed item as its top”)  SECTION(“A non-empty stack becomes deeper by retaining a pushed item as its top”)  SECTION(“A non-empty stack on popping reveals tops in reverse order of pushing”)  }
  86. 86. TEST_CASE(“Stack specification”) {  SECTION(“An empty stack acquires depth by retaining a pushed item as its top") { stack<std::string> stack; stack.push("ACCU"); REQUIRE(stack.depth() == 1); REQUIRE(stack.top() == "ACCU"); }  }
  87. 87. For each usage scenario, the test(s): ▪ Describe the context, starting point, or preconditions that must be satisfied ▪ Illustrate how the software is invoked ▪ Describe the expected results or postconditions to be verified Gerard Meszaros
  88. 88. TEST_CASE(“Stack specification”) {  SECTION(“An empty stack acquires depth by retaining a pushed item as its top") { // Arrange: stack<std::string> stack; // Act: stack.push("ACCU"); // Assert: REQUIRE(stack.depth() == 1); REQUIRE(stack.top() == "ACCU"); }  }
  89. 89. TEST_CASE(“Stack specification”) {  SECTION(“An empty stack acquires depth by retaining a pushed item as its top") { // Arrange: stack<std::string> stack; // Act: stack.push("ACCU"); // Assert: REQUIRE(stack.depth() == 1); REQUIRE(stack.top() == "ACCU"); }  }
  90. 90. TEST_CASE(“Stack specification”) {  SECTION(“An empty stack acquires depth by retaining a pushed item as its top") { // Given: stack<std::string> stack; // When: stack.push("ACCU"); // Then: REQUIRE(stack.depth() == 1); REQUIRE(stack.top() == "ACCU"); }  }
  91. 91. https://twitter.com/jasongorman/status/572829496342134784
  92. 92. Communications of the ACM, 12(10), October 1969
  93. 93. P {Q} R
  94. 94. If the assertion P is true before initiation of a program Q, then the assertion R will be true on its completion.
  95. 95. {P} Q {R}
  96. 96. An interface is a contract to deliver a certain amount of service. Clients of the interface depend on the contract, which is usually documented in the interface specification. Butler W Lampson Hints for Computer System Design
  97. 97. template<typename T> class stack { public: stack(); void push(const T & new_top); void pop(); std::size_t depth() const; const T & top() const; private: ... }; postcondition: depth() == 0 where: old_depth = depth() postcondition: depth() == old_depth + 1 && top() == new_top where: old_depth = depth() precondition: depth() > 0 postcondition: depth() == old_depth – 1 where: result = depth() postcondition: result >= 0 precondition: depth() > 0
  98. 98. template<typename T> class stack { public: stack(); void push(const T & new_top); void pop(); std::size_t depth() const; const T & top() const; private: ... }; [[expects: depth() == 0]] [[ensures: depth() > 0]] [[ensures: top() == new_top]] [[expects: depth() > 0]] [[ensures: depth() >= 0]] [[ensures result: result >= 0]] [[expects: depth() > 0]]
  99. 99. alphabet(Stack) = {new, push, pop, depth, top}
  100. 100. trace(Stack) = {⟨new⟩, ⟨new, push⟩, ⟨new, depth⟩, ⟨new, push, pop⟩, ⟨new, push, top⟩, ⟨new, push, depth⟩, ⟨new, push, push⟩, ⟨new, depth, push⟩, ⟨new, depth, depth⟩, ⟨new, push, push, pop⟩, ...}
  101. 101. Non-EmptyEmpty depth depth top push pop [depth > 1] push pop [depth = 1] new
  102. 102. Non-EmptyEmpty depth top / error pop / error depth top push pop [depth > 1] push pop [depth = 1] new
  103. 103. TEST_CASE("Stack specification") { SECTION("A new stack") { SECTION("is empty") ... } SECTION("An empty stack") { SECTION("throws when queried for its top item") ... SECTION("throws when popped") ... SECTION("acquires depth by retaining a pushed item as its top") ... } SECTION("A non empty stack") { SECTION("becomes_deeper_by_retaining_a_pushed_item_as_its_top") ... SECTION("on popping reveals tops in reverse order of pushing") ... } }
  104. 104. TEST_CASE("Stack specification") { SECTION("A new stack") { SECTION("is empty") ... } SECTION("An empty stack") { SECTION("throws when queried for its top item") ... SECTION("throws when popped") ... SECTION("acquires depth by retaining a pushed item as its top") ... } SECTION("A non empty stack") { SECTION("becomes_deeper_by_retaining_a_pushed_item_as_its_top") ... SECTION("on popping reveals tops in reverse order of pushing") ... } }
  105. 105. TEST_CASE("Stack specification") { SECTION("A new stack") { ... } SECTION("An empty stack") { ... SECTION("acquires depth by retaining a pushed item as its top") { stack<std::string> stack; stack.push("ACCU"); REQUIRE(stack.depth() == 1); REQUIRE(stack.top() == "ACCU"); } } SECTION("A non empty stack") { ... } }
  106. 106. TEST_CASE("Stack specification") { SECTION("A new stack") { ... } SECTION("An empty stack") { stack<std::string> stack; ... SECTION("acquires depth by retaining a pushed item as its top") { stack.push("ACCU"); REQUIRE(stack.depth() == 1); REQUIRE(stack.top() == "ACCU"); } } SECTION("A non empty stack") { ... } }
  107. 107. SCENARIO("Stack specification") { ... GIVEN("An empty stack") { stack<std::string> stack; ... WHEN("an item is pushed") { stack.push("ACCU"); THEN(“acquires depth by retaining the new item as its top”) { REQUIRE(stack.depth() == 1); REQUIRE(stack.top() == "ACCU"); } } } ... }
  108. 108. SCENARIO("Stack specification") { ... GIVEN("An empty stack") { stack<std::string> stack; ... WHEN("an item is pushed") { stack.push("ACCU"); THEN(“acquires depth”) { REQUIRE(stack.depth() == 1); } THEN(“retains the new item as its top”) { REQUIRE(stack.top() == "ACCU"); } } } ... }
  109. 109. SCENARIO("Stack specification") { ... GIVEN("An empty stack") { stack<std::string> stack; ... WHEN("an item is pushed") { stack.push("ACCU"); THEN(“acquires depth”) REQUIRE(stack.depth() == 1); THEN(“retains the new item as its top”) REQUIRE(stack.top() == "ACCU"); } } ... }
  110. 110. Given can be used to group tests for operations with respect to common initial state
  111. 111. When can be used to group tests by operation, regardless of initial state or outcome
  112. 112. Then can be used to group tests by common outcome, regardless of operation or initial state
  113. 113. For each usage scenario, the test(s): ▪ Describe the context, starting point, or preconditions that must be satisfied ▪ Illustrate how the software is invoked ▪ Describe the expected results or postconditions to be verified Gerard Meszaros
  114. 114. For each usage scenario, the test(s): ▪ Describe the context, starting point, or preconditions that must be satisfied ▪ Illustrate how the software is invoked ▪ Describe the expected results or postconditions to be verified Gerard Meszaros
  115. 115. TEST_CASE("Stack specification") { SECTION("A new stack") { ... } SECTION("An empty stack") { stack<std::string> stack; ... SECTION("acquires depth by retaining a pushed item as its top") { stack.push("ACCU"); REQUIRE(stack.depth() == 1); REQUIRE(stack.top() == "ACCU"); } } SECTION("A non empty stack") { ... } }
  116. 116. TEST_CASE("Stack specification") { SECTION("A new stack") { ... } SECTION("An empty stack") { stack<std::string> stack; ... SECTION("acquires depth by retaining a pushed item as its top") { stack.push("ACCU"); REQUIRE(stack.depth() == 1); REQUIRE(stack.top() == "ACCU"); } } SECTION("A non empty stack") { ... } }
  117. 117. TEST_CASE("Stack specification") { SECTION("A new stack") { ... } SECTION("An empty stack") { stack<std::string> stack; ... SECTION("acquires depth by retaining a pushed item as its top") { stack.push("ACCU"); REQUIRE(stack.items.size() == 1); REQUIRE(stack.items[0] == "ACCU"); } } SECTION("A non empty stack") { ... } }
  118. 118. TEST_CASE("Stack specification") { SECTION("A new stack") { ... } SECTION("An empty stack") { stack<std::string> stack; ... SECTION("acquires depth by retaining a pushed item as its top") { stack.push("ACCU"); REQUIRE(stack.get_items().size() == 1); REQUIRE(stack.get_items()[0] == "ACCU"); } } SECTION("A non empty stack") { ... } }
  119. 119. TEST_CASE("Stack specification") { SECTION("A new stack") { ... } SECTION("An empty stack") { stack<std::string> stack; ... SECTION("acquires depth by retaining a pushed item as its top") { stack.push("ACCU"); REQUIRE(stack.items().size() == 1); REQUIRE(stack.items()[0] == "ACCU"); } } SECTION("A non empty stack") { ... } }
  120. 120. TEST_CASE("Stack specification") { SECTION("A new stack") { ... } SECTION("An empty stack") { stack<std::string> stack; ... SECTION("acquires depth by retaining a pushed item as its top") { stack.push("ACCU"); REQUIRE(stack.depth() == 1); REQUIRE(stack.top() == "ACCU"); } } SECTION("A non empty stack") { ... } }
  121. 121. A programmer [...] is concerned only with the behavior which that object exhibits but not with any details of how that behavior is achieved by means of an implementation. Barbara Liskov Programming with Abstract Data Types
  122. 122. template<typename T> class stack { public: std::size_t depth() const { return items.size(); } const T & top() const { if (depth() == 0) throw std::logic_error("stack has no top"); return items.back(); } void pop() { if (depth() == 0) throw std::logic_error("cannot pop empty stack"); items.pop_back(); } void push(const T & new_top) { items.push_back(new_top); } private: std::vector<T> items; };
  123. 123. template<typename T> class stack { public: std::size_t depth() const { return size; } const T & top() const { if (depth() == 0) throw std::logic_error("stack has no top"); return items.front(); } void pop() { if (depth() == 0) throw std::logic_error("cannot pop empty stack"); items.pop_front(); --size; } void push(const T & new_top) { items.push_front(new_top); ++size; } private: std::size_t size = 0; std::forward_list<T> items; };
  124. 124. Queue {new, length, capacity, enqueue, dequeue}
  125. 125. producer consumerspacetime decoupling
  126. 126. N buffered bounded asynchronous
  127. 127. N = buffered unbounded asynchronous ∞
  128. 128. N = 1 buffered bounded asynchronous futurepromise
  129. 129. N = 0 unbuffered bounded synchronous
  130. 130. public class Queue<T> { ... public Queue(int capacity) ... public int length() ... public int capacity() ... public boolean enqueue(T last) ... public Optional<T> dequeue() ... }
  131. 131. Non-EmptyEmpty length capacity dequeue length capacity enqueue dequeue [length > 1] enqueue dequeue [length = 1] new [capacity > 0]
  132. 132. Non-Full Empty length capacity dequeue length capacity enqueue dequeue [length > 1] enqueue dequeue [length = 1] new [capacity > 0] Full Non-Empty [length = capacity] [length < capacity]
  133. 133. public class Queue_spec  public class A_new_queue  public void is_empty()  public void preserves_positive_bounding_capacity(int capacity)  public void cannot_be_created_with_non_positive_bounding_capacity(int capacity)  public class An_empty_queue  public void dequeues_an_empty_value()  public void remains_empty_when_null_enqueued()  public void becomes_non_empty_when_non_null_value_enqueued(String value)  public class A_non_empty_queue  public class that_is_not_full  public void becomes_longer_when_non_null_value_enqueued(String value)  public void becomes_full_when_enqueued_up_to_capacity()  public class that_is_full  public void ignores_further_enqueued_values()  public void becomes_non_full_when_dequeued()  public void dequeues_values_in_order_enqueued()  public void remains_unchanged_when_null_enqueued() 
  134. 134. public class Queue_spec  public class A_new_queue  public void is_empty()  public void preserves_positive_bounding_capacity(int capacity)  public void cannot_be_created_with_non_positive_bounding_capacity(int capacity)  public class An_empty_queue  public void dequeues_an_empty_value()  public void remains_empty_when_null_enqueued()  public void becomes_non_empty_when_non_null_value_enqueued(String value)  public class A_non_empty_queue  public class that_is_not_full  public void becomes_longer_when_non_null_value_enqueued(String value)  public void becomes_full_when_enqueued_up_to_capacity()  public class that_is_full  public void ignores_further_enqueued_values()  public void becomes_non_full_when_dequeued()  public void dequeues_values_in_order_enqueued()  public void remains_unchanged_when_null_enqueued() 
  135. 135. public class Queue<T> { private Deque<T> items = new LinkedList<>(); private final int capacity; public Queue(int boundingCapacity) { if (boundingCapacity < 1) throw new IllegalArgumentException(); capacity = boundingCapacity; } public int length() { return items.size(); } public int capacity() { return capacity; } public boolean enqueue(T last) { boolean enqueuing = last != null && length() < capacity(); if (enqueuing) items.addLast(last); return enqueuing; } public Optional<T> dequeue() { return Optional.ofNullable(items.pollFirst()); } }

×