Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Effective Unit Testing

josh_robb
September 13, 2014

Effective Unit Testing

josh_robb

September 13, 2014
Tweet

More Decks by josh_robb

Other Decks in Technology

Transcript

  1. Effective Unit Testing? Effective Unit Testing? “ I need to

    write tests? “ This is hard to test! “ These tests are fragile! “ These tests are slow/suck
  2. Test Smells Test Smells Slow tests Tests which don't test

    what they claim to Fragile tests Tests which make refactoring hard
  3. Slow tests are Slow tests are useless tests useless tests

    In February we had: 1686 "unit" tests ran in 5:11s 5.4 tests/sec estimated if nothing changed - our test suite would be taking > 20 minutes by the end of this year.
  4. Slow tests are Slow tests are useless tests useless tests

    In February we had: 1686 "unit" tests in 5:11s 5.4 tests/sec estimated if nothing changed - our test suite would be taking > 20 minutes by the end of this year. In the BEST CASE
  5. WTH DOES THIS EVEN TEST? [Test] public void Can_Retrive_Active_MediaSources() {

    MediaSource[] m = MediaSource.FindAll(MediaSource.Active); Assert.Greater(m.Length, 0); }
  6. How do I get better How do I get better

    at writing tests? at writing tests?
  7. Find someone to Find someone to read your tests read

    your tests Thanks @robfe @bittercoder @hammett and the Castle Project for reading my tests
  8. “ The only way to get The only way to

    get better is to get better is to get constructive constructive feedback on your feedback on your tests tests
  9. [Test] public void when_candidates_are_newer_and_older_then_count_should_be_correct() { JobApp.DeleteAll(); Candidate.DeleteAll(); Candidate candidate; var

    aWeekAgo = DateTime.Now.AddDays(-7); for (int i = 0; i < 5; i++) { candidate = ObjectMother.NewCandidate(); candidate.User.LastLogin = aWeekAgo; //login a week ago candidate.Save(); } candidate = ObjectMother.NewCandidate(); candidate.Save(); var yesterday = DateTime.Now.AddDays(-1); DataPurgeSummary query = new DataPurgeSummary() .WithLoginBefore(yesterday); var results = query.BuildCriteria(); results[0].Candidates.ShouldBe(5); results[0].LastLogin.Value.Date.ShouldBe(aWeekAgo.Date); }
  10. [Test] public void TestCandidateSavesandLoads() { Candidate c = ObjectMother.NewCandidate(); string

    email = c.User.Email; Candidate c2 = Candidate.Find(c.Id); User u2 = c.User; Assert.IsNotNull(u2); Assert.IsNotNull(c2); Assert.AreEqual(u2.FullName, c.FirstName + ' ' + c.LastName); Assert.AreEqual(u2.Passwd, "pass"); Assert.AreEqual(u2.Email, email); Assert.AreEqual(u2.UserType, UserTypes.Candidate); Assert.AreEqual(c2.FirstName, "first"); Assert.AreEqual(c2.LastName, "last"); Assert.AreEqual(c2.Title, "mr"); Assert.AreEqual(c2.Address1, "address1"); Assert.AreEqual(c2.Address2, "address2"); Assert.AreEqual(c2.Address3, "address3"); Assert.AreEqual(c2.Town, "town"); Assert.AreEqual(c2.PostCode, "postcode"); Assert.AreEqual(c2.Country, "country"); Assert.AreEqual(c2.DaytimePhone, "dayphone"); Assert.AreEqual(c2.EveningPhone, "eveningphone"); Assert.AreEqual(c2.Mobile, "mobile"); Assert.AreEqual(c2.CandidateType, Candidate.Types.Previous); }
  11. Warning! Warning! You will write bad You will write bad

    tests using these tests using these tools! tools!
  12. [Test] public void TestCandidateSavesandLoads() { Candidate c = ObjectMother.NewCandidate(); string

    email = c.User.Email; Candidate c2 = Candidate.Find(c.Id); User u2 = c.User; Assert.IsNotNull(u2); Assert.IsNotNull(c2); Assert.AreEqual(u2.FullName, c.FirstName + ' ' + c.LastName); Assert.AreEqual(u2.Passwd, "pass"); Assert.AreEqual(u2.Email, email); Assert.AreEqual(u2.UserType, UserTypes.Candidate); Assert.AreEqual(c2.FirstName, "first"); Assert.AreEqual(c2.LastName, "last"); Assert.AreEqual(c2.Title, "mr"); Assert.AreEqual(c2.Address1, "address1"); Assert.AreEqual(c2.Address2, "address2"); Assert.AreEqual(c2.Address3, "address3"); Assert.AreEqual(c2.Town, "town"); Assert.AreEqual(c2.PostCode, "postcode"); Assert.AreEqual(c2.Country, "country"); Assert.AreEqual(c2.DaytimePhone, "dayphone"); Assert.AreEqual(c2.EveningPhone, "eveningphone"); Assert.AreEqual(c2.Mobile, "mobile"); Assert.AreEqual(c2.CandidateType, Candidate.Types.Previous); }
  13. SpecLight SpecLight [Test] public void name_is_irrelevant() { new Spec("Properties of

    a candidate should persist and hydrate correctly") .Given(TheCandidateService) .And(ASavedValidCandidate) .When(IFetchTheCandidate) .Then(TheFetchedCandidateShouldBeProperlyInitalized) .Execute(); }
  14. SpecLight SpecLight // Given void TheCandidateService() { _candidateService = new

    CandidateService(); } void ASavedValidCandidate() { _savedCandidate = _candidateService.Save(new Candidate()); } // When void IFetchTheCandidate() { _fetchedCandidate = _candidateService.Fetch(_savedCandidate.Id); } // Then void TheFetchedCandidateShouldBeProperlyInitalized() { PAssert.IsTrue(()=> _fetchedCandidate.FullName == "Some sample" && _fetchedCandidate.Create }
  15. Power Assert Power Assert [Test] public void SequenceEqualButNotOperatorEquals() { object

    list = new List<int> { 1, 2, 3 }; object array = new[] { 1, 2, 3 }; PAssert.IsTrue(() => list == array); } System.Exception : IsTrue failed, expression was: list == array . . __ . . \ _/ | \_ _/ | | [1, 2, 3] | False, but would have been True with .SequenceEqual() [1, 2, 3]
  16. Approval Tests Approval Tests 1. Originally a legacy testing framework

    2. We use for state based testing of complex structures
  17. Approval Tests Approval Tests [Test] public void Authenticate_When_Failure_Should_ReturnInvalidAuthTokenResponse() { var

    badResult = (JsonResult)ControllerToTest.Authenticate(new Guid("EA917962-6E1B-4064-828E var badApiResponse = (ApiResponse)badResult.Data; var badAuthResponse = (AuthenticateResponse)badApiResponse.Data; Assert.IsNotNull(badResult); Assert.IsNotNull(badApiResponse); Assert.IsNotNull(badAuthResponse); Assert.IsNull(badAuthResponse.SessionToken); Assert.AreEqual(ApiResponseCode.InvalidAuthToken, badApiResponse.Status.Code); }
  18. [Test] public void Authenticate_When_Failure_Should_ReturnInvalidAuthTokenResponse() { var result = ControllerToTest.Authenticate(new Guid("EA917962-6E1B-4064-828E-941B9C40687D")

    Approvals.Verify(result); } Approval Tests Approval Tests { "Status": { "Code": 7, "Message": "An unexpected error occurred." }, "Data": null }
  19. Approval Tests Approval Tests Downsides: 1. Terrible CI failure messages

    2. Over specified tests 3. Separates test from "state assertion" (single line approvals)
  20. Not a framework problem! Not a framework problem! [Test] public

    void ExpiredGuestPayment_Should_NotBeFetched() { var paymentQueryService = new Mock<IPaymentsQueryService>(); var payment = PaymentFor(PaymentSource.Kiosk); payment.SetStatusForTesting(PaymentStatus.Error); payment.SetExpired(); paymentQueryService.Setup(q => q.GetPaymentByToken(Guid.Empty)).Returns(payment); var blackboard = Blackboard(); var action = new FetchExistingNewOrRetryableCardPaymentAction(paymentQueryService.Object); //+Act var outcome = action.Execute(blackboard); //+Assert PAssert.IsTrue(() => blackboard.Payment == null && outcome == PaymentActionOutcome.Halt); }
  21. Not a framework problem! Not a framework problem! [SetUp] public

    void Setup() { var webhooksService = new Mock<IWebhooksService>(); calls = webhooksService.StoreInvocations<IWebhooksService, string, WebhookEvent>(d => d.Sen action = new SendPaymentCreatedWebhookAction(webhooksService.Object); blackboard = new PaymentBlackboard(null) { Payment = new Payment(CardPaymentGateway.PushpayTest, null) { Token = new Guid("5469FCCA-AE63-43E5-B548-9834436485EC"), Recipient = new Merchant { Id=456, Handle = "spca"} } }; } [Test] public void When_NotAch_Should_Send_Webhook_Event() { var result = action.Execute(blackboard); PAssert.IsTrue(() => result == PaymentActionOutcome.Continue && calls.Count == 1); } [Test] public void When_Ach_should_Not_Send_Webhook_Event() { blackboard.Method = new AchBankAccount(); var result = action.Execute(blackboard); PAssert.IsTrue(() => result == PaymentActionOutcome.Continue && calls.Count == 0); }