Top Banner
Dror Helper [email protected] | http://blog.drorhelper.com | @dhelper Building unit tests - correctly
40
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Building unit tests correctly

Dror Helper

[email protected] | http://blog.drorhelper.com | @dhelper

Building unit tests - correctly

Page 2: Building unit tests correctly

About.Me

• Senior consultant @CodeValue

• Developing software (professionally) since 2002

• Clean coder

• Test Driven Developer

• Blogger: http://blog.drorhelper.com

Page 3: Building unit tests correctly

/** 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//

Page 4: Building unit tests correctly

//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.

Page 5: Building unit tests correctly

Legacy code

Page 6: Building unit tests correctly

“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”

Page 7: Building unit tests correctly

This is a unit test

[Test]

public void CheckPassword_ValidUser_ReturnTrue()

{

bool result = CheckPassword(“user”, “pass”);

Assert.That(result, Is.True);

}

D

Page 8: Building unit tests correctly

This is also a unit test

[@Test]

public void CheckPassword_ValidUser_ReturnTrue()

{

boolean result = CheckPassword(“user”, “pass”);

Assert.IsTrue(result);

}

D

Page 9: Building unit tests correctly

A unit test is…

A method (Code)

that

tests specific functionality,

Has

clear pass/fail criteria

and

runs in isolation

Page 10: Building unit tests correctly

Unit testing is an iterative effort

Unit testing is an iterative effort

Page 11: Building unit tests correctly

Supporting environment

• The team

• Development environment

Page 12: Building unit tests correctly

The Team

• The team commitment is important

• Learn from test reviews

• Track results

Page 13: Building unit tests correctly

Server

Dev Machine

Source ControlBuild Server

Test Runner

Code Coverage

Build Script

Unit Testing Framework

Isolation Framework

Tools of the trade

Build Failed!

Page 14: Building unit tests correctly

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

Page 15: Building unit tests correctly

Development environment

• Make it easy to write and run tests

– Unit test framework

– Test Runner

– Isolation framework

• Know where you stand

– Code coverage

Page 16: Building unit tests correctly

Unit testing frameworks

• Test fixtures

• Assertions

• Runners

• More…

http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks

Page 17: Building unit tests correctly

[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

Page 18: Building unit tests correctly

Real code has dependencies

Unit test

Code under test

Dependency Dependency

Page 19: Building unit tests correctly

The solution - Mocking

Fake object(s)

Unit test

Code under test

Dependency

Page 20: Building unit tests correctly

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

Page 21: Building unit tests correctly

What Mocking framework can do for you?

• Create Fake objects

• Set behavior on fake objects

• Verify method was called

• And more...

Page 22: Building unit tests correctly

[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(""));

}

Page 23: Building unit tests correctly

@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);

}

Page 24: Building unit tests correctly

Code Coverage

So, What is a good code coverage?

Page 25: Building unit tests correctly

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

Page 26: Building unit tests correctly

A good unit test should be:

• Easy to understand

• Trustworthy

• Robust

Trust Your Tests!

Page 27: Building unit tests correctly

Ideally A test would only fail if a bug was introduced

ORRequirements changed

Page 28: Building unit tests correctly

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.

Page 29: Building unit tests correctly

Assert.IsTrue(user.Equals(expected)Assert.AreEqual(expected, user)

Page 30: Building unit tests correctly

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

Page 31: Building unit tests correctly

Writing a good unit test

[Test]

public void

Multiply_PassTwoPositiveNumbers_ReturnCorrectResult()

{

var calculator = CreateMultiplyCalculator();

var result = calculator.Multiply(1, 3);

Assert.AreEqual(result, 3);

}

Page 32: Building unit tests correctly

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

Page 33: Building unit tests correctly

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)

Page 34: Building unit tests correctly

Can you spot the problem in these Tests?

Page 35: Building unit tests correctly

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

Page 36: Building unit tests correctly

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;}

Page 37: Building unit tests correctly

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");

}

}

Page 38: Building unit tests correctly

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);

}

Page 39: Building unit tests correctly

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

Page 40: Building unit tests correctly

Dror Helper

C: 972.05.7668543

e: [email protected]

B: blog.drorhelper.com