secret unit testing tools

58
The secret unit testing tools no one has ever told you about Dror Helper | blog.drorhelper.com | @dhelper Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Upload: dror-helper

Post on 23-Jan-2017

196 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Secret unit testing tools

The secret unit testing tools no one has ever told you about

Dror Helper | blog.drorhelper.com | @dhelper

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Page 2: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

About.MEConsultant @CodeValue

Developing software (professionally) since 2002Mocking code since 2008Clean coder & Test Driven Developer

OzCode (a.k.a “Magical debugging”) Evangelist

Blogger: http://blog.drorhelper.com

Page 3: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

But it was not always like that1st Attempt Failed!

2nd Attempt Failed!!!

New job + UT + Mentor Success

Page 4: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Why should I care about tools?

Page 5: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Background: unit testing tools

Page 6: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Well known unit testing tools

Build Failed!

Page 7: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Server

Dev Machine

Source ControlBuild Server

Test Runner

Code Coverage

Build Agent

Unit Testing Framework

Isolation Framework

?

Page 8: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

xUnit test framework

Test Suite

Fixture

Test Case

Test Case

Test Case

Test Case

Test Case

FixtureTest Case

Test Case

Fixture

Test Case

Test Case

Test Case

public class BeforeAndAfter { [SetUp] public void Initialize() { }

[TearDown] public void Cleanup() { }

[Test] public void test1() {

}

[Test] public void test2() { }}

Test Fixture

Page 9: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Mocking FrameworksUnit test

Code under test

DependencyFake object(s)

Page 10: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

What Mocking framework can do for you?• Create Fake objects

• Set behavior on fake objects

• Verify method was called

• And more...

Page 11: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

[Test]public void Calculate_ReturnTwoValidNumbers_ServerCalled(){ IDataAccess fakeDataAccess = A.Fake<IDataAccess>(); A.CallTo(() => fakeDataAccess.GetData(A<string>.Ignored))

.Returns(new Tuple<int, int>(2, 3));

var fakeCalculatorService = A.Fake<ICalculatorService>();

var cut = new DistrobutedCalculator(fakeDataAccess, fakeCalculatorService);

cut.Calculate();

A.CallTo(() => fakeCalculatorService.Add(2,3)).MustHaveHappened();}

Page 12: Secret unit testing tools

These tools do not help us write good unit tests

In fact, sometimes they prevent us from

writing good unit tests!

Page 13: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Definition: unit testsAutomated piece of code that invokes a unit of work in the system and thenchecks a single assumption about thebehavior of that unit of work

[Roy Osherove, The Art Of Unit Testing]

Page 14: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Unit test structure[Test]public void MyTest(){

}

Page 15: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

No guidance Fragile tests Stop unit testing

Page 16: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

What about AAA?[Test]public async void GetUserFromUrl() { var clientFake = A.Fake<IJsonClient>(); A.CallTo(() => clientFake.HttpGetUncompressedAsync(A<string>.Ignored))

.Returns(Task.FromResult(JsonResult));

var userRepository = new UserRepository(clientFake);

var user = await userRepository.GetUser(11361);

var expected = new User { Id=11361, DisplayName = "Dror Helper", ImageUrl=DefaultAvatar, Reputation=13904 };

Assert.That(user, Is.EqualTo(expected));}

Arrange

Act

Assert

Page 17: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Problem solved? Hardly!• Where to start?• How to test existing code?• What about test structure?• Integration tests vs. unit tests• What is a “unit of work”?

Page 18: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Test setup (Arrange)

Page 19: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Arrange issues[TestMethod]public async Task LoadUser_ReputationStaysTheSame_ReputationTrendSame() { var user1 = new User {ImageUrl = "http://dummy.jpg", Reputation = 10}; var user2 = new User {ImageUrl = "http://dummy.jpg", Reputation = 10};

var fakeUserRepository = new FakeUserRepository(new[] { user1, user2 }); var viewModel = await InvokeAsync(() => new UserDetailsViewModel(fakeUserRepository));

await viewModel.LoadUser(); await viewModel.LoadUser();

var result = await InvokeAsync(() => ((SolidColorBrush)viewModel.ReputationTrend).Color); Assert.AreEqual(Colors.White, result);}

Page 20: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Solution: Setup/TearDownprivate UserDetailsViewModel _viewModel;

[TestInitialize]public async Task InitilizeUserViewModel() { var user1 = new User { ImageUrl = "http://dummy.jpg", Reputation = 10 }; var user2 = new User { ImageUrl = "http://dummy.jpg", Reputation = 10 };

var fakeUserRepository = new FakeUserRepository(new[] { user1, user2 }); _viewModel = await InvokeAsync(() => new UserDetailsViewModel(fakeUserRepository));}

[TestMethod]public async Task LoadUser_ReputationStaysTheSame_ReputationTrendSame() { await _viewModel.LoadUser(); await _viewModel.LoadUser();

var result = InvokeAsync(() => ((SolidColorBrush)_viewModel.ReputationTrend).Color); Assert.AreEqual(Colors.White, result);}

Page 21: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Why you shouldn’t use Setup in unit testsprivate UserDetailsViewModel _viewModel;

[TestInitialize]public async Task InitilizeUserViewModel() { var user1 = new User { ImageUrl = "http://dummy.jpg", Reputation = 10 }; var user2 = new User { ImageUrl = "http://dummy.jpg", Reputation = 10 };

var fakeUserRepository = new FakeUserRepository(new[] { user1, user2 }); _viewModel = await InvokeAsync(() => new UserDetailsViewModel(fakeUserRepository));}

[TestMethod]public async Task LoadUser_ReputationStaysTheSame_ReputationTrendSame() { await _viewModel.LoadUser(); await _viewModel.LoadUser();

var result = InvokeAsync(() => ((SolidColorBrush)_viewModel.ReputationTrend).Color); Assert.AreEqual(Colors.White, result);}

Page 22: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Solution: Extract to methods[TestMethod]public async Task LoadUser_ReputationStaysTheSame_ReputationTrendSame() { var user1 = CreateUser(reputation: 10); var user2 = CreateUser(reputation: 10);

var fakeUserRepository = new FakeUserRepository(new[] { user1, user2 }); var viewModel = await InvokeAsync(() => new UserDetailsViewModel(fakeUserRepository));

await viewModel.LoadUser(); await viewModel.LoadUser();

var result = await InvokeAsync(() => ((SolidColorBrush)viewModel.ReputationTrend).Color); Assert.AreEqual(Colors.White, result);}

Page 23: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Over extraction[TestMethod]public async Task LoadUser_ReputationStaysTheSame() { var viewModel = InitializeSystem(10, 10);

await viewModel.LoadUser(); await viewModel.LoadUser();

CheckColor(Colors.White, viewModel);}

Page 24: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

The problem with factoriesprivate User CreateUser(int reputation) { return new User { ImageUrl = "http://dummy.jpg", Reputation = reputation };}

private User CreateUser(int reputation, string imageUrl) { return new User { ImageUrl = imageUrl, Reputation = reputation };}

Page 25: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Builder patternclass UserBuilder{ private int _id, _reputation; private string _displayName, _imageUrl;

public UserBuilder() { _id = 1; _displayName = "dummy"; _imageUrl = "http://dummy.jpg"; }

User Build() { return new User { Id = _id, DisplayName = _displayName, ImageUrl = _imageUrl, Reputation = _reputation }; }}

Page 26: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Builder pattern (cont.)class UserBuilder{ ... public UserBuilder WithName(string displayName) { _displayName = displayName;

return this; }

public UserBuilder WithReputation(int reputation) { _reputation = reputation;

return this; } ...}

var user1 = new UserBuilder() .WithReputation(10) .Build();

Page 27: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Tool: AutoMocking ContainersTest Container SUT

http://blog.ploeh.dk/2013/03/11/auto-mocking-container/

New()

Configure

CreateCreate SUT

Act

Page 28: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Automocking with AutoFixture[Fact]public void YellIfTouchHotIron() { var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization());

Fake<IMouth> fakeMouth = fixture.Freeze<Fake<IMouth>>();

Fake<IHand> fakeHand = fixture.Freeze<Fake<IHand>>(); A.CallTo(() => fakeHand.FakedObject.TouchIron(A<Iron>._)).Throws<BurnException>();

var brain = fixture.Create<Brain>();

brain.TouchIron(new Iron {IsHot = true});

A.CallTo(() => fakeMouth.FakedObject.Yell()).MustHaveHappened();}

Page 29: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Automocking with Typemock Isolator[TestMethod]public void FakeAllDependencies_ChangeBehavior(){ var real = Isolate.Fake.Dependencies<ClassUnderTest>(); var fake = Isolate.GetFake<Dependency>(real); Isolate.WhenCalled(() => fake.Multiplier).WillReturn(2); var result = real.Calculate(1, 2); Assert.AreEqual(6, result);}

Page 30: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Problem: Complex inputs• Big objects• Deep objects• Need for precision• Lack of knowledge

Page 31: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Solution: use trivial inputsSometimes missing the point

Not always enough for “real test”

Page 32: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Solution: SerializationSupported in most programming languages (XML, json).

• Need development testing delayed• Production code change indefinitely delayed

Page 33: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Tool: Export using OzCode

Page 34: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Verification (Assert)

Page 35: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Can you spot the problem?[TestMethod] public void PerformSomeActionReturns42() { var myClass = ... bool initOk = myClass.Initialize();

var result = myClass.PerformSomeAction(); Assert.IsTrue(initOk); Assert.AreEqual(42, result);}

http://stackoverflow.com/q/26400537/11361

Page 36: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Can you spot the problem?[TestMethod] public void TestPasswordComplexity() { var result = _UserManager.ChangePasswordAsync(_TestUser.Id, "Password123!", "1!").Result; //Changes the password. Assert.IsFalse(result.Succeeded);

result = _UserManager.ChangePasswordAsync(_TestUser.Id, "Password123!", "123456789").Result; //Changes the password. Assert.IsFalse(result.Succeeded);

result = _UserManager.ChangePasswordAsync(_TestUser.Id, "Password123!", "123456789!").Result; //Changes the password. Assert.IsFalse(result.Succeeded);

result = _UserManager.ChangePasswordAsync(_TestUser.Id, "Password123!", "abcdefghijk").Result; //Changes the password. Assert.IsFalse(result.Succeeded);

result = _UserManager.ChangePasswordAsync(_TestUser.Id, "Password123!", "abcdefghijK1!").Result; //Changes the password. Assert.IsTrue(result.Succeeded); }

http://stackoverflow.com/q/26400537/11361

Page 37: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

How many Assert(s) per test?

One Assert Per Test!

Two Assert == Two Tests Usually ???

”(…the Code is more) what you'd call guidelines

than actual rules”

Page 38: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Sometimes multiple asserts make sense[TestMethod]public void CompareTwoAsserts(){ var actual = GetNextMessage();

Assert.AreEqual(1, actual.Id); Assert.AreEqual("str-1", actual.Content);}

Page 39: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

public class AssertAll{    public static void Execute(params Action[] assertionsToRun)     {        var errorMessages = new List<exception>();        foreach (var action in assertionsToRun)         {            try            {                action.Invoke();            }            catch (Exception exc)            {                errorMessages.Add(exc);            }        }         if(errorMessages.Any())         {            var separator = string.Format("{0}{0}", Environment.NewLine);            string errorMessage = string.Join(separator, errorMessages);                         Assert.Fail(string.Format("The following conditions failed:{0}{1}", Environment.NewLine, errorMessage));        }    }}

http://blog.drorhelper.com/2011/02/multiple-asserts-done-right.html

Page 40: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Using AssertAll[TestMethod]public void CompareTwoAsserts(){ var actual = CreateMessage();

AssertAll.Execute(() => Assert.AreEqual(1, actual.Id),

() => Assert.AreEqual("str-1", actual.Content);}

Page 41: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Some frameworks are catching up!

https://github.com/nunit/docs/wiki/Multiple-Asserts-Spec

Page 42: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Ever had issues choosing the right Assert?• IsTrue vs. AreEqual• Parameter ordering confusion• StringAssert/CollectionAssert

It’s all about proper error messages

Page 43: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Tool: 3rd party assertion libraries

Better error messagesReadabilityMultiple asserts*

×Additional dependency×Limited UT framework support×System.Object “SPAMED” by extension messages

Page 44: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Shouldly[Fact]public void AddTest(){ var calculator = new Calculator(); var result = calculator.Add(2, 3); Assert.Equal(6, result);}

[Fact]public void AddTest_Shouldly(){ var calculator = new Calculator(); var result = calculator.Add(2, 3); result.ShouldBe(6);}

https://github.com/shouldly/shouldly

Shouldly.ShouldAssertException

result should be6 but was5

Xunit.Sdk.EqualException

Assert.Equal() FailureExpected: 6Actual: 5

Page 45: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Shouldly[Fact]public void GetDivisorsTest(){ var calculator = new Calculator(); var result = calculator.GetDivisors(20); Assert.Equal(new[] {2,3,5,7}, result);}

[Fact]public void GetDivisorsTest_Shouldly(){ var calculator = new Calculator(); var result = calculator.GetDivisors(20); result.ShouldBe(new[] { 2, 3, 5, 7 });}

https://github.com/shouldly/shouldly

Shouldly.ShouldAssertException

result should be[2, 3, 5, 7] but was[2, 4, 5, 10] difference[2, *4*, 5, *10*]

Xunit.Sdk.EqualException

Assert.Equal() FailureExpected: Int32[] [2, 3, 5, 7]Actual: WhereEnumerableIterator<Int32> [2, 4, 5, 10]

Page 46: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

FluentAssertions[Fact]public void CompareTwoObjects(){ var customer1 = new Customer("cust-1", "John Doe"); var customer2 = new Customer("cust-2", "John Doe");

customer1.ShouldBeEquivalentTo(customer2, o => o.Excluding(customer => customer.Id));}

http://www.fluentassertions.com/

Page 47: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

AssertHelper[Test]public void CheckCompare(){    var myClass = new MyClass();     Expect.That(() => myClass.ReturnFive() == 10);} [Test]public void CheckTrue(){    var myClass = new MyClass();          Expect.That(() => myClass.ReturnFalse() == true);}

[Test]public void StringStartsWith() {    var s1 = "1234567890";     Expect.That(() => s1.StartsWith("456"));} 

[Test]public void CollectionContains(){    var c1 = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };     Expect.That(() => c1.Contains(41));}

https://github.com/dhelper/AssertHelper

Page 48: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Test organization

Page 49: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Test structure issues• What to call the test?• AAA is not mandatory• What should I test?• How to avoid unreadable, complicated tests?

- Unit testing framework provide no structure

Page 50: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

The BDD approach

Specifications Step Definitions

Page 51: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Specifications == focused testFeature: Addition

In order to avoid silly mistakesAs a math idiotI want to be told the sum of two numbers

Scenario: Add two numbersGiven I have entered 50 into the calculatorAnd I have entered 70 into the calculatorWhen I press addThen the result should be 120 on the screen

Page 52: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

BDD Example: SpecFlow

http://www.specflow.org/

Page 53: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Tool: BDDfy[TestClass]public class CardHasBeenDisabled { private Card _card; private Atm _subject;

void GivenTheCardIsDisabled() {_card = new Card(false, 100);_subject = new Atm(100);

}

void WhenTheAccountHolderRequestsMoney() {_subject.RequestMoney(_card, 20);

}

void ThenTheAtmShouldRetainTheCard() {Assert.IsTrue(_subject.CardIsRetained);

}

void AndTheAtmShouldSayTheCardHasBeenRetained() {Assert.AreEqual(DisplayMessage.CardIsRetained,

_subject.Message); }

[TestMethod] public void Execute() { this.BDDfy(); }}

Page 54: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Test execution

Page 55: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Everybody needs a CI serverUnit 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 I am

Page 56: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Tool: Continuous testing

DotCover

Typemock Runner

nCrunch

Page 57: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

The right tools will help you write good tests

Arrange

Builder Pattern

AutoMocking Containers

Export

Assert

Shouldly

FluentAssertions

AssertHelper

Test Structure

BDDfy

Continuous Testing

Typemock Runner

DotCover

nCrunch

Page 58: Secret unit testing tools

Join the conversation on Twitter: @DevWeek // #DW2016 // #DevWeek

Thank youDror Helper | @dhelper | http://blog.drorhelper.com