Date post: | 17-Jul-2015 |
Category: |
Software |
Upload: | dror-helper |
View: | 685 times |
Download: | 0 times |
Dror Helper
[email protected] | http://blog.drorhelper.com | @dhelper
Building unit tests - correctly
About.Me
• Senior consultant @CodeValue
• Developing software (professionally) since 2002
• Clean coder
• Test Driven Developer
• Blogger: http://blog.drorhelper.com
/** You may think you know what the following code does.* But you dont. Trust me.* Fiddle with it, and youll spend many a sleepless* night cursing the moment you thought youd be clever* enough to "optimize" the code below.* Now close this file and go play with something else.*/
// // Dear maintainer:// // Once you are done trying to 'optimize' this routine,// and have realized what a terrible mistake that was,// please increment the following counter as a warning// to the next guy:// // total_hours_wasted_here = 42//
//This code sucks, you know it and I know it. //Move on and call me an idiot later.
http://stackoverflow.com/questions/184618/what-is-the-best-comment-in-source-code-you-have-ever-encountered
We fear our code!
//Abandon all hope ye who enter beyond this point
//When I wrote this, only God and I understood what I was doing//Now, God only knows
// I dedicate all this code, all my work, to my wife, Darlene, who will // have to support me and our three children and the dog once it gets // released into the public.
//The following 1056 lines of code in this next method //is a line by line port from VB.NET to C#.//I ported this code but did not write the original code.//It remains to me a mystery as to what//the business logic is trying to accomplish here other than to serve as//some sort of a compensation shell game invented by a den of thieves.//Oh well, everyone wants this stuff to work the same as before.//I guess the devil you know is better than the devil you don't.
“Code without tests is bad code...
With tests, we can change the behavior of our code quickly and verifiably...”
Michael Feathers - “Working effectively with legacy code”
This is a unit test
[Test]
public void CheckPassword_ValidUser_ReturnTrue()
{
bool result = CheckPassword(“user”, “pass”);
Assert.That(result, Is.True);
}
D
This is also a unit test
[@Test]
public void CheckPassword_ValidUser_ReturnTrue()
{
boolean result = CheckPassword(“user”, “pass”);
Assert.IsTrue(result);
}
D
A unit test is…
A method (Code)
that
tests specific functionality,
Has
clear pass/fail criteria
and
runs in isolation
Server
Dev Machine
Source ControlBuild Server
Test Runner
Code Coverage
Build Script
Unit Testing Framework
Isolation Framework
Tools of the trade
Build Failed!
Unit tests without a CI server are a waste of time - if you're running all of the tests all of the time locally you're a better man then me
Development environment
• Make it easy to write and run tests
– Unit test framework
– Test Runner
– Isolation framework
• Know where you stand
– Code coverage
Unit testing frameworks
• Test fixtures
• Assertions
• Runners
• More…
http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks
[Test]
public void AddTest()
{
var cut = new Calculator();
var result = cut.Add(2, 3);
Assert.AreEqual(5, result);
}
This is not a real unit test
Isolation
• Replace production logic with custom logic
• We do this in order to
– Focus the test on one class only
– Test Interaction
– Simplify Unit test writing
What Mocking framework can do for you?
• Create Fake objects
• Set behavior on fake objects
• Verify method was called
• And more...
[Test]
public void IsLoginOK_LoggerThrowsException_CallsWebService()
{
var fakeLogger = Isolate.Fake.Instance<ILogger>();
Isolate
.WhenCalled(() => fakeLogger.Write(""))
.WillThrow(new LoggerException());
var fakeWebService = Isolate.Fake.Instance<IWebService>();
var lm = new LoginManagerWithMockAndStub(fakeLogger,fakeWebService);
lm.IsLoginOK("", "");
Isolate.Verify.WasCalledWithAnyArguments(() => fakeWebService.Write(""));
}
@Test
public void testBookAddtion(){
string isbn = "1234";
TitleCatalog fakeCatalog = mock(TitleCatalog.class);
Book book = new Book("Moby dick", isbn);
when(fakeCatalog.getTitleByISBN(anystring())).thenReturn(book);
BookInventory inventory = new BookInventory();
inventory.registerCopy(isbn, "1");
verify(fakeCatalog, times(1)).getTitleByISBN(isbn);
}
How I failed unit testing my code
Unit testing is great!
Everything should be tested
I can test “almost” everything
Tests break all the time
Unit testing is a waste of time
Be explicit and deterministic
Problem• I cannot re-run the exact same test if a test fails
Solution• Don’t use Random elements in tests
– If you care about the values set them– If you don’t care about the values put defaults– Do not use Sleep & time related logic – fake it
• Use fake objects to “force” determinism into the test• When possible avoid threading in tests.
Test namingTest names should be descriptive and explicit.Test names should express a specific requirement
I like to use:Method_Scenario_Expecteda.k.aUnitOfWork_StateUnderTest_ExpectedBehavior
Public void Sum_NegativeNumberAs1stParam_ExceptionThrown()Public void Sum_simpleValues_Calculated ()Public void Parse_SingleToken_ReturnsEqualTokenValue ()
http://osherove.com/blog/2005/4/3/naming-standards-for-unit-tests.html
Writing a good unit test
[Test]
public void
Multiply_PassTwoPositiveNumbers_ReturnCorrectResult()
{
var calculator = CreateMultiplyCalculator();
var result = calculator.Multiply(1, 3);
Assert.AreEqual(result, 3);
}
It’s an over-specification that creates fragile tests
Test the “what”, not the “How”
• So that refactoring won’t break your tests
• Testing private methods is a smell!
Avoid testing private methods
How to avoid fragile tests
• Don’t test private/internal (most of the time)
• Fake as little as necessary
• Test only one thing (most of the time)
So what about code reuse
Readability is more important than code reuse
• Create objects using factories
• Put common and operations in helper methods
• Use inheritance – sparsely
Avoid logic in the test (if, switch etc.)
• Test is not readable
• Has several possible paths
• High maintain cost
@Testpublic void Test1(){
// Here be codeswitch(i){
case(a):...break;
case(b):...break;
default:Assert.Fail();
}}
try{
Assert.IsNull(…);Assert.AreEqual(…);
}catch (Exception e){
thrownException = e;}
Learn to write “clean tests”
[Test]
public void CalculatorSimpleTest()
{
calc.ValidOperation = Calculator.Operation.Multiply;
calc.ValidType = typeof (int);
result = calc.Multiply(1, 3);
Assert.IsTrue(result == 3);
if (calc.ValidOperation == Calculator.Operation.Invalid)
{
throw new Exception("Operation should be valid");
}
}
Or suffer the consequences![Test]
public void CalculatorSimpleTest()
{
var calc = new Calculator();
calc.ValidOperation = Calculator.Operation.Multiply;
calc.ValidType = typeof (int);
var result = calc.Multiply(-1, 3);
Assert.AreEqual(result, -3);
calc.ValidOperation = Calculator.Operation.Multiply;
calc.ValidType = typeof (int);
result = calc.Multiply(1, 3);
Assert.IsTrue(result == 3);
if (calc.ValidOperation == Calculator.Operation.Invalid)
{
throw new Exception("Operation should be valid");
}
calc.ValidOperation = Calculator.Operation.Multiply;
calc.ValidType = typeof (int);
result = calc.Multiply(10, 3);
Assert.AreEqual(result, 30);
}
How to start with unit tests
1. Test what you’re working on – right now!
2. Write tests to reproduce reported bug
3. When refactoring existing code – use unit tests to make sure it still works.
1. All tests must run as part of CI build
Dror Helper
C: 972.05.7668543
B: blog.drorhelper.com