Successfully reported this slideshow.
Your SlideShare is downloading. ×

Tdd is not about testing (C++ version)

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad

Check these out next

1 of 61 Ad

Tdd is not about testing (C++ version)

Download to read offline

TDD is now mainstream but a lot people don't know or don't remember what is its purpose. TDD is about software design not testing or catching bug. TDD helps developers to shape and create software with "good" design, what is a "good" design is something that we will discuss in the topic.

TDD is now mainstream but a lot people don't know or don't remember what is its purpose. TDD is about software design not testing or catching bug. TDD helps developers to shape and create software with "good" design, what is a "good" design is something that we will discuss in the topic.

Advertisement
Advertisement

More Related Content

Slideshows for you (20)

Similar to Tdd is not about testing (C++ version) (20)

Advertisement

Recently uploaded (20)

Advertisement

Tdd is not about testing (C++ version)

  1. 1. TDD is not about testing Bad name for good technique ...
  2. 2. GPad Born to be a developer with an interest in distributed system. I have developed with many languages like C++, C#, js and ruby. I had fallen in love with functional programming, especially with elixir, erlang. ● Twitter: https://twitter.com/gpad619 ● Github: https://github.com/gpad/ ● Medium: https://medium.com/@gpad CTO & founder of coders51
  3. 3. Schedule What is TDD? Why TDD? TDD is not (only) Unit Testing TDD in OOP Examples
  4. 4. What is TDD - History Wikipedia - TDD Wikipedia - Extreme Programming Quora - Why does Kent Beck rediscover TDD http://wiki.c2.com/?TestingFramework Wikipedia - C2 System
  5. 5. What is TDD - History The C3 project started in 1993 [...]. Smalltalk development was initiated in 1994. [...] In 1996 Kent Beck was hired [...]; at this point the system had not printed a single paycheck. In March 1996 the development team estimated the system would be ready to go into production around one year later. In 1997 the development team adopted a way of working which is now formalized as Extreme Programming. The one-year delivery target was nearly achieved, with actual delivery being a couple of months late;
  6. 6. What is TDD - History It was developed by Grady Booch, Ivar Jacobson and James Rumbaugh at Rational Software in 1994– 1995, with further development led by them through 1996. In 1997 UML was adopted as a standard by the Object Management Group (OMG) [...]. In 2005 UML was also published by the International Organization for Standardization (ISO) as an approved ISO standard. Since then the standard has been periodically revised to cover the latest revision of UML.
  7. 7. What is TDD - History
  8. 8. What is TDD - History From Wikipedia: Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: requirements are turned into very specific test cases, then the software is improved to pass the new tests, only. This is opposed to software development that allows software to be added that is not proven to meet requirements. American software engineer Kent Beck, who is credited with having developed or "rediscovered" the technique, stated in 2003 that TDD encourages simple designs and inspires confidence.
  9. 9. What is TDD
  10. 10. What is TDD 1. Add a test 2. Run all tests and see if the new test fails (RED) 3. Write the code (Only to make the test pass!!!) 4. Run tests 5. Refactor code 6. Repeat
  11. 11. Why TDD
  12. 12. Why TDD Less bugs I like it … I feel more comfortable … … Helps to create a “GOOD” design
  13. 13. Why TDD TDD changes the point of view. Forces the developer to think about the “behaviour” of the code. Talks to the developer showing what are the “difficult points” of the code. If the tests talk, we should listen them … (often we ignore them).
  14. 14. Listen the test TEST(MailComposerTest, test1) { User gino{"gino", "gino@gino.it", true}; User pino{"pino", "pino@pino.it", false}; auto userRepository = UserRepository::create(); userRepository->CreateUser(gino); userRepository->CreateUser(pino); MailComposer composer; composer.SendEmail("gino@gino.it", "body one"); composer.SendEmail("pino@pino.it", "body two"); auto emailsSent = ExternalLibrary::TestEmailServer::GetEmailsSent(); ASSERT_EQ(emailsSent.size(), 1); ExternalLibrary::Email expected{"gino@gino.it", GlobalSettings::GetFromAddress(), "body one"}; ASSERT_EQ(*emailsSent.begin(), expected); }
  15. 15. Listen the test class MailComposer { public: void SendEmail(std::string email, std::string body) { using namespace ExternalLibrary; auto userRepository = UserRepository::create(); auto user = userRepository->GetUserByEmail(email); if (!user.acceptEmail()) return; EmailServer::GetInstance()->SendEmail(Email{ GlobalSettings::GetFromAddress(), user.email(), body}); } };
  16. 16. Listen the test It isn’t a tool problem. It’s a design problem … What is telling (screaming) the test?
  17. 17. Listen the test TEST(MailComposerTest, test1) { User gino{"gino", "gino@gino.it", true}; User pino{"pino", "pino@pino.it", false}; auto userRepository = UserRepository::create(); userRepository->CreateUser(gino); userRepository->CreateUser(pino); MailComposer composer; composer.SendEmail("gino@gino.it", "body one"); composer.SendEmail("pino@pino.it", "body two"); auto emailsSent = ExternalLibrary::TestEmailServer::GetEmailsSent(); ASSERT_EQ(emailsSent.size(), 1); ExternalLibrary::Email expected{"gino@gino.it", GlobalSettings::GetFromAddress(), "body one"}; ASSERT_EQ(*emailsSent.begin(), expected); } The name doesn’t mean anything We are changing global value Singleton ?!?
  18. 18. Listen the test TEST(MailComposerTest, SendEmailOnlyToUserThatAcceptEmail) { User gino{"gino", "gino@gino.it", true}; User pino{"pino", "pino@pino.it", false}; auto userRepository = UserRepository::create(); userRepository->CreateUser(gino); userRepository->CreateUser(pino); MailComposer composer( userRepository, ExternalLibrary::EmailServer::GetInstance()); composer.SendEmail("gino@gino.it", "body one"); composer.SendEmail("pino@pino.it", "body two"); auto emailsSent = ExternalLibrary::TestEmailServer::GetEmailsSent(); ASSERT_EQ(emailsSent.size(), 1); ExternalLibrary::Email expected{"gino@gino.it", GlobalSettings::GetFromAddress(), "body one"}; ASSERT_EQ(*emailsSent.begin(), expected); } Change name!!! Passing repository as parameter ... Passing Singleton as parameter
  19. 19. Can we do better?!?
  20. 20. TDD doesn’t mean only Unit Testing ...
  21. 21. Type of tests End to End tests - Tests that work on the entire stack. Integration Test - Tests that work on some parts of the application. Unit Test - Tests that work on a single “module/function”. Why is it so important to know which type of test we are writing? Because we get different feedbacks from different types of test.
  22. 22. Type of tests
  23. 23. Cycle of TDD ... 1. Add a test 2. Run all tests and see if the new test fails (RED) 3. Write the code 4. Run tests 5. Refactor code 6. Repeat
  24. 24. Cycle of TDD ...
  25. 25. End To End Test or Acceptance Test This type of test exercises the entire stack of the application. It remains RED until the feature is completed. Don’t write too much E2E. They are slow and fragile. Where is the design?
  26. 26. How application is done (or should be)
  27. 27. How application is done (or should be) Try to create a E2E test that interacts with system from the external. If it’s “impossible” try to move a little inside skipping the adapter.
  28. 28. Outside-In VS Inside-Out
  29. 29. Outside-In We start writing an End to End Test that explains the feature that we want to create. We add some unit tests to shape the behaviour of the object that we want to create. We add some integration tests to verify that some parts works well together.
  30. 30. Inside-Out We start writing some units test to shape the behaviour of the objects that we want to create. We add some integration tests to verify that the parts works well together. We add some End to End Tests that explain the feature that we want to create.
  31. 31. My 2 cents I prefer to use Outside-In when I have to create a new feature. Outside-In requires more experienced developers/teams. I use Inside-Out when I want to introduce the TDD in a new team. I use Inside-Out when I have to add a feature and I know which objects I have to change.
  32. 32. Mock
  33. 33. When to use Mock? When we want to isolate one part from another. Classic example HTTP connections. We have to make some HTTP calls and manipulate the results in some ways. How can we do it?
  34. 34. When to use Mock? TEST(TwitterClientTest, RetriveBioFromUser) { std::string clientId = "cleintId"; std::string clientSecret = "clientSecret"; NiceMock<MockHttpClient> mockHttpClient; EXPECT_CALL(mockHttpClient, Get("https://twitter.com/gpad619")) .WillOnce(Return("gpad_bio")); TwitterClient twitter(clientId, clientSecret, &mockHttpClient); auto bio = twitter.GetBioOf("gpad619"); EXPECT_EQ(bio, "gpad_bio"); }
  35. 35. When to use Mock? We are at the boundaries of our system and we can use the mock to shape the behavior between the CORE and the adapter that talks with the external system. It’s the CORE that choose the shape of the data and how the functions should be done.
  36. 36. When to use Mock? TEST(AggregationTest, SaveTheSumWhenThresholdIsReached) { NiceMock<MockPointRepository> mockPointRepository; EXPECT_CALL(mockPointRepository, Store(4)); Aggregation aggregation(4, &mockPointRepository); aggregation.Add(2); aggregation.Add(2); }
  37. 37. When to use Mock? - Time TEST(AggregationTest, SaveTheEventWhenThresholdIsReached) { auto now = std::chrono::system_clock::now(); NiceMock<MockPointRepository> mockPointRepository; EXPECT_CALL(mockPointRepository, StoreEvent("Threshold Reached", now)); Aggregation aggregation(4, &mockPointRepository); aggregation.Add(2); aggregation.Add(2); }
  38. 38. When to use Mock? - Time
  39. 39. When to use Mock? - Time class Aggregation { int _sum; int _threshold; PointRepository *_pointRepository; public: void Add(int value) { _sum += value; if (_sum >= _threshold) { _pointRepository->Store(_sum); _pointRepository->StoreEvent("Threshold Reached", std::chrono::system_clock::now()); } } }; This is a dependency!!!
  40. 40. When to use Mock? - Time TEST(AggregationTest, SaveTheEventWhenThresholdIsReached) { auto now = std::chrono::system_clock::now(); NiceMock<MockPointRepository> mockPointRepository; EXPECT_CALL(mockPointRepository, StoreEvent("Threshold Reached", now)); NiceMock<MockClock> mockClock; ON_CALL(mockClock, Now()).WillByDefault(Return(now)); Aggregation aggregation(4, &mockPointRepository, &mockClock); aggregation.Add(2); aggregation.Add(2); }
  41. 41. When to use Mock? - Time class Aggregation { ...; public: Aggregation(int threshold, PointRepository *pointReposiotry, Clock *clock) ... void Add(int value) { _sum += value; if (_sum >= _threshold) { _pointRepository->Store(_sum); _pointRepository->StoreEvent("Threshold Reached", _clock->Now()); } } };
  42. 42. When to use Mock? - Time How to manage a periodic task? Divide et impera.
  43. 43. When to use Mock? - Time TEST(Scheduler, ExecuteTaskAfterSomeAmountOfTime) { Scheduler scheduler; bool executed = false; scheduler.ExecuteAfter( std::chrono::milliseconds(500), [&]() { executed = true; }); EXPECT_THAT(executed, WillBe(true, std::chrono::milliseconds(1000))); }
  44. 44. When to use Mock? - Time TEST(Scheduler, DontExecuteTaskImmediatly) { Scheduler scheduler; bool executed = false; scheduler.ExecuteAfter( std::chrono::milliseconds(500), [&]() { executed = true; }); EXPECT_FALSE(executed); }
  45. 45. WHERE to use Mock? Use the mock at the external of CORE/Domain (adapter). Isolate the CORE from the infrastructure not because we want to switch from PG to Mongo but because that libraries are not under our control.
  46. 46. When to use Mock? - Interaction between objects TEST(CartTest, RetrieveDiscoutForUserWhenAddItem) { User user("gino", "g@g.it", true); NiceMock<MockTrackingSystem> mockTrackingSystem; NiceMock<MockDiscountEngine> mockDiscountEngine; EXPECT_CALL(mockDiscountEngine, GetDiscountFor(user)) .WillRepeatedly(Return(0.1)); Cart cart(user, &mockDiscountEngine, &mockTrackingSystem); cart.AddItem(Product(ProductId::New(), "Product", Money(10, "EUR"))); EXPECT_EQ(cart.GetTotal(), Money(9, "EUR")); }
  47. 47. When to use Mock? - Interaction between objects TEST(CartTest, CallTrackingSystemWhenRemoveItemFromCart) { User user("gino", "g@g.it", true); Product product(ProductId::New(), "Product", Money(10, "EUR")); NiceMock<MockTrackingSystem> mockTrackingSystem; NiceMock<MockDiscountEngine> mockDiscountEngine; EXPECT_CALL(mockTrackingSystem, ProductRemoved(product)); Cart cart(user, &mockDiscountEngine, &mockTrackingSystem); cart.AddItem(product); cart.RemoveItem(product); }
  48. 48. WHERE to use Mock? We can use the mock when we want to define how the object talk to each other. We can define the protocol that the object should use to talk to each other.
  49. 49. When not to use Mock? Don’t use mock on Object that don’t own. TEST(MailComposerTest, TestMailComposerMock) { User gino{"gino", "gino@gino.it", true}; User pino{"pino", "pino@pino.it", false}; auto userRepository = UserRepository::create(); userRepository->CreateUser(gino); userRepository->CreateUser(pino); NiceMock<MockExternalEmailServer> mockExternalEmailServer; MailComposer composer(userRepository, &mockExternalEmailServer); ... }
  50. 50. When not to use Mock? Don’t mock value objects!!! TEST(MailComposerTest, TestMailComposerMockUser) { NiceMock<MockUser> gino(); NiceMock<MockUser> pino(); EXPECT_CALL(gino, acceptEmail()).WillRepeatedly(Return(true)); EXPECT_CALL(pino, acceptEmail()).WillRepeatedly(Return(true)); auto userRepository = UserRepository::create(); MailComposer composer(userRepository, ExternalLibrary::EmailServer::GetInstance()); composer.SendEmail(gino, "body one"); composer.SendEmail(pino, "body two"); }
  51. 51. TDD - Mix Everything together What is the first test to do? We could start from outside with an E2E test to enter inside our application. Or we could start inside our application adding some unit tests to some objects. Create at least one E2E for every User Story. Don’t create too much E2E they are slow and fragile.
  52. 52. TDD - Mix Everything together TEST_F(AsLoggedUser, IWantSeeMyCurrentCart) { auto cart = CreateCartFor(_currentUser); auto total = GetMainWindow() ->GetCartWindowFor(_currentUser) ->GetTotal(); EXPECT_EQ(total, cart.GetTotal()); }
  53. 53. TDD - Mix Everything together TEST_F(AsLoggedUser, IWantPayMyCurrentCart) { auto cart = CreateCartFor(_currentUser); auto paymentInfo = CreatePaymentInfoFor(_currentUser); auto paymentResult = GetMainWindow() ->GetPaymentWindowFor(_currentUser) ->Pay(); EXPECT_EQ(paymentResult, PaymentResult::Successful); }
  54. 54. TDD - Mix Everything together The E2E remains RED until all the cycle is completed. After that we have written the E2E we go inside the CORE and start to create some unit tests.
  55. 55. TDD - Mix Everything together TEST(CartTest, CalculateCartTotalWhenAddItem) { User user("gino", "g@g.it", true); NiceMock<MockTrackingSystem> mockTrackingSystem; NiceMock<MockDiscountEngine> mockDiscountEngine; Cart cart(user, &mockDiscountEngine, &mockTrackingSystem); cart.AddItem(Product(ProductId::New(), "Product1", Money(5, "EUR"))); cart.AddItem(Product(ProductId::New(), "Product2", Money(4, "EUR"))); EXPECT_EQ(cart.GetTotal(), Money(9, "EUR")); }
  56. 56. TDD - Mix Everything together After we have written the unit tests for the CORE we could move to the boundaries where we should write tests for the adapter parts. The test for storage part should be written using the DB. IMHO they are more integration than unit.
  57. 57. TDD - Mix Everything together TEST_F(SqlCartRepositoryTest, ReadSavedCartById) { Cart cart = BuildCartWithSomeItemsFor(_currentUser); _cartRepository.Save(cart); auto cartFromDb = _cartRepository.GetById(cart.GetId()); EXPECT_THAT(cart, IsEquivalent(cartFromDb)); }
  58. 58. TDD - Mix Everything together Writing these tests BEFORE the implementation we are doing DESIGN. We are shaping the production code. The code became more “composable”. It’s more clear where are side effects (I/O, Time). It’s more clear what are the different parts of our applications.
  59. 59. Recap TDD is not a Silver Bullet. TDD doesn’t give us a “good” design if we are not able to do it. TDD can help us to find some issues in our design. Listen the test, often they are screaming in pain ...
  60. 60. Reference Modern C++ Programming with Test-Driven Development GOOS Clean Architecture Clean Code WELC The Rspec Book
  61. 61. End - Thanks !!!

×