Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Unit Testing Best Practices

302 views

Published on

  • Be the first to comment

  • Be the first to like this

Unit Testing Best Practices

  1. 1. Unit Testing Best Practices Tomaš Maconko
  2. 2. What do we know about UT? • What is it? • Why do we need it? • Who use it? • Who knows how to use it?
  3. 3. Poor statistics • What is the good average code-coverage? • 70-80% • What is the actual average code-coverage? • 10%
  4. 4. Unit Tests • Small chunk of code • Automatized test • Determines if specific module is fit for use • Run fast http://martinfowler.com/bliki/images/unitTest
  5. 5. Purpose • Finds problems early • Facilitates change • Documentation • Simplifies integration • Design
  6. 6. Common issues • Overdesign • Run slowly • Hard to understand • Tests the same thing repeatedly
  7. 7. #1: Issue
  8. 8. #1: Issue • Huge projects • Many references • Hundreds of files • Thousands of tests • Hard to focus • Takes time to build the whole solution
  9. 9. #1: Solution • Group up the files by certain criteria • Put each tests projects beside logic project • Consider hard cohesion criteria
  10. 10. #1: Benefits • Small tests projects • Quick builds and tests runs • Easier to understand
  11. 11. #2: Problem Seems to be not so bad?
  12. 12. #2: Problem • Non-common classes in common unit tests project • Redundant dependencies through common unit tests project • It references several projects, that also reference several projects, that ... So after every change on referenced project we have to build the whole chain. • It takes time to build
  13. 13. #2: Solution • Remove the code from common unit tests project that is not- common and put it to each tests project that uses that code • Consider some code duplication – you can always copy the certain code to each project – you don’t always have to be DRY • Remove common unit tests project to remove the additional reference build chain
  14. 14. #2: Benefits • Less projects to build = less time to build • Utilities are in the same project with tests that use it • Changes used for specific tests projects utility does not affect other projects
  15. 15. #3: Issue If the tests passed, then it would be ok. But what if it fails? Do you understand what the test should do? Or you want to go to look at code..?
  16. 16. #3: Issue Can you understand it?
  17. 17. #3: Issue • Classes contain “Test” postfix when there are many tests in class • Not always obvious what are the conditions and expected results • Soooo hard to analyze the problem if the test is reeaaally big
  18. 18. #3: Solution • Write what you test or why you test • Use naming conventions for test methods: • [Class]_[Method]_[Conditions]_[ExpectedResults] • [Api]_[Conditions]_[ExpectedResults] • Use naming conventions for test classes: • [Testable]Tests • In some cases write comments • Add simple comments “Arrange, Act, Assert” to outline the test sections
  19. 19. #3: Benefits • Clear conditions and results • Better understanding of test
  20. 20. #4: Issue SETUP
  21. 21. #4: Issue
  22. 22. #4: Issue • Mocks add a lot of confusion to code • Many lines of useless and repeated code • You have to put effort to mock the data in a right way • And…
  23. 23. #4: Issue • WTF exceptions Moq.MockException: Wrong external request. Expected invocation on the mock once, but was 0 times: r => r.AddToLog(It.Is<DefaultLogData>(d => (((((((((((d.CorrelationId == String.Empty && d.Identifier == String.Empty) && d.OperationId == Guid.Empty) && d.Method == ._method) && (Int32)d.ObjectType == 0) && d.StatusCode == "0") && d.StatusCodeDescription == String.Empty) && d.SubStatusCode == null) && d.SubStatusCodeDescription == null) && d.ExternalType == .ExternalRequest.GetType().FullName) && d.Milliseconds == 0) && .ContainsAllStrings(d.SerializedExternalObject, new[] { .ExternalRequest.SampleData })) && d.Url == "http://dummyservice.payex.com/")) Configured setups: r => r.AddToLog(It.IsAny<DefaultLogData>()), Times.Exactly(4) Performed invocations: IDefaultLogRepository.AddToLog(Infrastructure.MPS.Common.Logging.TransportClasses.DefaultLogData) IDefaultLogRepository.AddToLog(Infrastructure.MPS.Common.Logging.TransportClasses.DefaultLogData) IDefaultLogRepository.AddToLog(Infrastructure.MPS.Common.Logging.TransportClasses.DefaultLogData) IDefaultLogRepository.AddToLog(Infrastructure.MPS.Common.Logging.TransportClasses.DefaultLogData)
  24. 24. #4: Issue • Do not combine Times.Never with argument matching – tests can pass the verification by because of value mismatch _logRepository.Verify(r => r.LogRequest(It.Is<FinancialRequestLogData>( l => l.CurrentTransactionNumber == 0 && l.OriginalTransactionNumber == 0 && l.WorkflowExtension == string.Empty && l.OrderId == null && l.OrderRef == null && l.Operation == default(FinancialOperation) && string.IsNullOrWhiteSpace(l.SerializedExternal) && !string.IsNullOrEmpty(l.SerializedInternal) && l.Url == Url)), Times.Never, "Wrong request.");
  25. 25. #4: Solution • Do not use entity mocks – write you own overridden class with predefined data • You can always use builder pattern (object mother pattern) or fixture to build needed class for testing, either using mocking framework or overridden classes • If you still want to use mocking frameworks, prefer the explicit assert instead of complicated verification
  26. 26. #4: Solution
  27. 27. #4: Benefits • Less redundant code • Code is more clear • Asserts are more readable
  28. 28. #5: Problem Cool little test isn’t it? Lets look inside…
  29. 29. #5: Problem
  30. 30. #5: Solution • Do not overcommit to reduce the code • Keep code clean, but clear • Remember, other people can have to debug or refactor you code
  31. 31. #6: Problem // In BusinessLogic: var hasher = new MyHasher(); var hashBuilder = new StringBuilder(); hashBuilder.Append(clientId); hashBuilder.Append(clientFirstName); hashBuilder.Append(secretKey); var hash = hasher.GetHash(hashBuilder.ToString()); // In UnitTests var hasher = new SimilarMyHasher(); string hash = clientId + secretKey; var hash = hasher.GetUtHash(hash); • Duplicated logic creates tests instability
  32. 32. #6: Solution • Do not test the logic that is tested in another test • Reuse the logic for specific cases from real business logic • Duplicate the stuff only if you want to check if method returns the right data • Last example could be useful when checking if business logic hash calculation is correct
  33. 33. Conclusion • Group tests depending on their cohesion – integration, API, requirement, ... • Create self-documenting tests • Do not test several things in the same test • If you feel your test will create many WTFs/min – refactor it! – REFACTOR TILL YOU DROP
  34. 34. Keep #EnjoyIT Contact me, if you have questions: • t.maconko@ba.lt • www.ba.lt

×