Upload
tomas-maconko
View
50
Download
1
Embed Size (px)
Citation preview
What do we know about UT?
• What is it?
• Why do we need it?
• Who use it?
• Who knows how to use it?
Poor statistics
• What is the good average code-coverage?
• 70-80%
• What is the actual average code-coverage?
• 10%
Unit Tests
• Small chunk of code
• Automatized test
• Determines if specific module is fit for use
• Run fast
http://martinfowler.com/bliki/images/unitTest
Purpose
• Finds problems early
• Facilitates change
• Documentation
• Simplifies integration
• Design
#1: Issue
• Huge projects
• Many references
• Hundreds of files
• Thousands of tests
• Hard to focus
• Takes time to build the whole solution
#1: Solution
• Group up the files by certain criteria
• Put each tests projects beside logic project
• Consider hard cohesion criteria
#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
#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
#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
#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..?
#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
#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
#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…
#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)
#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.");
#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
#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
#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
#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
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
Keep #EnjoyITContact me, if you have questions:
• www.ba.lt