TDD / BDD kickstart
Lars ThorupZeaLake Software Consulting
March, 2016
Lars Thorup
● Software developer/architect● JavaScript, C#● Test Driven Development
● Coach● Agile engineering practices
and tools
● Founder● BestBrains● ZeaLake
● @larsthorup
Basic unit testing● Assertions
● Fixtures
● Runners
● Debugging
● Exercise
Assertions - Equality
Assert.AreEqual("San Francisco, California", location.ToDisplayString());
expect(location.toDisplayString()).toEqual('San Francisco, California');
assertThat(location.toDisplayString(), equalTo("San Francisco, California"));
Assertions - Sameness● Microsoft
● Jasmine
● JUnit
Assert.AreSame(expectedLocation, actualLocation);
expect(actualLocation).toBe(expectedLocation);
assertThat(actualLocation, sameInstance(expectedLocation));
Assertions - special values● Microsoft
● Jasmine
● JUnit
Assert.IsNull(location);Assert.IsTrue(location.IsValid());
expect(location).toBeNull();expect(location.isValid()).toBeTruthy();
assertThat(location, nullValue());
Assertions - floating point● Microsoft
● Jasmine
● JUnit
Assert.AreEqual(10.23, location.Diameter(), 0.01);
expect(location.diameter()).toBeCloseTo(10.23, 2);
assertThat(location.diameter(), closeTo(10.23, 0.01));
Assertions - collections● Microsoft
● Jasmine
● JUnit
CollectionAssert.AreEquivalent(new [] {"a", "b"}, names);
expect(names).toEqual(['a', 'b']);
assertThat(names, containsInAnyOrder("a", "b"))
Assertions - exceptions● Microsoft
● Jasmine
● JUnit
[ExpectedException(typeof(ArgumentException))]public void SomeTest () { DivideBy(0); }
expect(function () { divideBy(0); }).toThrow(ArgumentError);
@Test(expected = ArgumentException.class) public void someTest() { divideBy(0); }
Assertions - exceptions● Microsoft
try{ DivideBy(0); Assert.Fail();}catch(ArgumentException ex){ Assert.AreEqual("Cannot divide by 0", ex.Message);}
Fixtures● Microsoft
● Jasmine
● JUnit
[TestInitialize] public void Initialize() { ... }
[TestCleanup] public void Cleanup() { ... }
beforeEach(function () { ... });
afterEach(function () { ... });
@Before public void before() { ... }
@After public void after() { ... }
Runners● Microsoft
● Jasmine
● JUnit
Visual Studio
Test | Run | All Tests
Grunt & Karma
npm test
IntelliJ IDEA
Run | Configurations | JUnit
Debugging● Microsoft
● Jasmine
● JUnit
Visual Studio
Test | Debug | All Tests
Grunt & Karma & Chrome
npm run test:chrome
IntelliJ IDEA
Debug | Configurations | JUnit
Exercise - unit testing● Try out assertions
● Equality and sameness● Floating point● Collections● Exceptions
● Verify that fixture methods run for every test
● Debug a test
Workflow of TDD / BDD
Failingtest
Succeedingtest
Gooddesign Refactor
Code
Test
BehaviorThink, talk
TDD Demo - IP# -> Location display● Convert this JSON ● To a display string
San Francisco, California
Farum, Denmark
Philippines
{ CountryCode = "US", Country = "USA", Region = "California", City = "San Francisco" }
{ CountryCode = "DK", Country = "Denmark", Region = "Hovedstaden", City = "Farum" }
{ CountryCode = "PH", Country = "Philippines", Region = "", City = "" }
Why TDD?
Well-designedJust Works™
Refactorable
No debugger
Documented
Productive!
Exercise - TDD● Input: a date
● Output: the time since that date, human readable● "2 minutes ago"● "1week ago"● "3 months ago"
● Think first about the precise behavior
● Then write one test for the simplest case not covered yet● and make it fail for the right reason
● Then write the code to make the test pass
● ...repeat :)
● Enjoy more efficient and predictable course of development
● Find and fix bugs faster
● Prevent bugs from reappearing
● Improve the design of our software
● Reliable documentation
● Way more fun :)
Why is TDD a good thing?
Questions!
TDD - questions● How fine grained is the TDD cycle?
● Do we always write tests before code?
● Why can't we just write the tests afterwards?
● How many tests are enough?
● How do we test private methods?
● Do we have to setup all dependencies?
● What about our legacy code?
What is good design?● One element of good design is loose coupling
● Use interfaces (for static languages)● Inject dependencies
● Avoid using new:
● Inject dependencies instead:
private IEmailSvc emailSvc;public Notifier(IEmailSvc emailSvc){ this.emailSvc = emailSvc;}
public void Trigger(){ emailSvc.SendEmail();
public void Trigger(){ var emailSvc = new EmailSvc(); emailSvc.SendEmail();}
Stubs and mocks● When testing an object X, that depends on an object Y
● replace the real Y with a fake Y
● Benefits● Only test one thing (X) at a time● Faster tests (Y may be slow)● Simpler (Y may depend on Z etc)
● Examples:● Time● Database● Email● HttpContext
Notifier
EmailSvc
IEmailSvc
EmailSvcStub
NotifierTest
Stubs● Hand crafted
● More effort to write
● Easier to maintain
● Can be more "black box" than mocks
Mocks● Mocks are automatically generated stubs
● Easy to use
● More "magical", may be slower
● More effort to maintain
● Will be more "white-box" than stubs
● Example frameworks:● C#: NSubstitute● JavaScript: Sinon.JS● Java: Easymock / Powermock
Stubs - example
public class EmailSvcStub : IEmailSvc{ public int NumberOfEmailsSent { get; set; }
public void SendEmail() { ++NumberOfEmailsSent; }}
[Test]public void Trigger(){ // setup var emailSvc = new EmailSvcStub(); var notifier = new Notifier(emailSvc);
// invoke notifier.Trigger();
// verify Assert.That(emailSvc.NumberOfEmailsSent, Is.EqualTo(1));}
Mocks - example
[Test]public void Trigger(){ // setup var emailSvc = Substitute.For<IEmailSvc>(); var notifier = new Notifier(emailSvc);
// invoke notifier.Trigger();
// verify emailSvc.Received(1).SendEmail();}
Test data builder - example[Test]public void GetResponseMedia(){ // given var stub = new StubBuilder { Questions = new [] { new QuestionBuilder { Name = "MEDIA" }, }, Participants = new[] { new ParticipantBuilder { Name = "Lars", Votes = new [] { new VoteBuilder { Question = "MEDIA", Responses = new ResponseBuilder(new byte [] {1, 2, 3}) }, }}, }, }.Build(); var voteController = new VoteController(stub.Session);
// when var result = voteController.GetResponseMedia(vote.Id, true) as MediaResult;
// then Assert.That(result.Download, Is.True); Assert.That(result.MediaLength, Is.EqualTo(3)); Assert.That(TfResponse.ReadAllBytes(result.MediaStream), Is.EqualTo(new byte[] {1, 2, 3}));}
Legacy code● Add pinning tests
● special kinds of unit tests for legacy code
● verifies existing behaviour● acts as a safety net
● Can be driven by change requests
● Refactor the code to be able to write unit tests
● Add unit test for the change request
● Track coverage trend for existing code
● and make sure it grows