Look, no Mocks! Functional TDD with...
Transcript of Look, no Mocks! Functional TDD with...
![Page 1: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/1.jpg)
Look, no Mocks! Functional TDD with F#
Mark Seemann
http://blog.ploeh.dk
@ploeh
![Page 2: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/2.jpg)
TDD is dead!
http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html
![Page 3: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/3.jpg)
Is TDD dead?
![Page 4: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/4.jpg)
A system you can’t test
![Page 5: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/5.jpg)
Detestable
http://martinfowler.com/bliki/Detestable.html
![Page 6: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/6.jpg)
public IHttpActionResult Post(ReservationRendition rendition) { DateTimeOffset requestedDate; if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate)) return this.BadRequest("Invalid date."); var min = requestedDate.Date; var max = requestedDate.Date.AddDays(1); using (var ctx = new ReservationsContext()) { var reservedSeats = (from r in ctx.Reservations where min <= r.Date && r.Date < max select r.Quantity) .DefaultIfEmpty(0) .Sum(); if (rendition.Quantity + reservedSeats > capacity) return this.StatusCode(HttpStatusCode.Forbidden);
![Page 7: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/7.jpg)
public IHttpActionResult Post(ReservationRendition rendition) { DateTimeOffset requestedDate; if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate)) return this.BadRequest("Invalid date."); var min = requestedDate.Date; var max = requestedDate.Date.AddDays(1); using (var ctx = new ReservationsContext()) { var reservedSeats = (from r in ctx.Reservations where min <= r.Date && r.Date < max select r.Quantity) .DefaultIfEmpty(0) .Sum(); if (rendition.Quantity + reservedSeats > capacity) return this.StatusCode(HttpStatusCode.Forbidden); ctx.Reservations.Add(new Reservation
![Page 8: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/8.jpg)
public IHttpActionResult Post(ReservationRendition rendition) { DateTimeOffset requestedDate; if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate)) return this.BadRequest("Invalid date."); var min = requestedDate.Date; var max = requestedDate.Date.AddDays(1); using (var ctx = new ReservationsContext()) { var reservedSeats = (from r in ctx.Reservations where min <= r.Date && r.Date < max select r.Quantity) .DefaultIfEmpty(0) .Sum(); if (rendition.Quantity + reservedSeats > capacity) return this.StatusCode(HttpStatusCode.Forbidden); ctx.Reservations.Add(new Reservation {
![Page 9: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/9.jpg)
public IHttpActionResult Post(ReservationRendition rendition) { DateTimeOffset requestedDate; if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate)) return this.BadRequest("Invalid date."); var min = requestedDate.Date; var max = requestedDate.Date.AddDays(1); using (var ctx = new ReservationsContext()) { var reservedSeats = (from r in ctx.Reservations where min <= r.Date && r.Date < max select r.Quantity) .DefaultIfEmpty(0) .Sum(); if (rendition.Quantity + reservedSeats > capacity) return this.StatusCode(HttpStatusCode.Forbidden); ctx.Reservations.Add(new Reservation { Date = requestedDate,
![Page 10: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/10.jpg)
public IHttpActionResult Post(ReservationRendition rendition) { DateTimeOffset requestedDate; if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate)) return this.BadRequest("Invalid date."); var min = requestedDate.Date; var max = requestedDate.Date.AddDays(1); using (var ctx = new ReservationsContext()) { var reservedSeats = (from r in ctx.Reservations where min <= r.Date && r.Date < max select r.Quantity) .DefaultIfEmpty(0) .Sum(); if (rendition.Quantity + reservedSeats > capacity) return this.StatusCode(HttpStatusCode.Forbidden); ctx.Reservations.Add(new Reservation { Date = requestedDate, Name = rendition.Name,
![Page 11: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/11.jpg)
public IHttpActionResult Post(ReservationRendition rendition) { DateTimeOffset requestedDate; if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate)) return this.BadRequest("Invalid date."); var min = requestedDate.Date; var max = requestedDate.Date.AddDays(1); using (var ctx = new ReservationsContext()) { var reservedSeats = (from r in ctx.Reservations where min <= r.Date && r.Date < max select r.Quantity) .DefaultIfEmpty(0) .Sum(); if (rendition.Quantity + reservedSeats > capacity) return this.StatusCode(HttpStatusCode.Forbidden); ctx.Reservations.Add(new Reservation { Date = requestedDate, Name = rendition.Name, Email = rendition.Email,
![Page 12: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/12.jpg)
public IHttpActionResult Post(ReservationRendition rendition) { DateTimeOffset requestedDate; if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate)) return this.BadRequest("Invalid date."); var min = requestedDate.Date; var max = requestedDate.Date.AddDays(1); using (var ctx = new ReservationsContext()) { var reservedSeats = (from r in ctx.Reservations where min <= r.Date && r.Date < max select r.Quantity) .DefaultIfEmpty(0) .Sum(); if (rendition.Quantity + reservedSeats > capacity) return this.StatusCode(HttpStatusCode.Forbidden); ctx.Reservations.Add(new Reservation { Date = requestedDate, Name = rendition.Name, Email = rendition.Email, Quantity = rendition.Quantity
![Page 13: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/13.jpg)
{ DateTimeOffset requestedDate; if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate)) return this.BadRequest("Invalid date."); var min = requestedDate.Date; var max = requestedDate.Date.AddDays(1); using (var ctx = new ReservationsContext()) { var reservedSeats = (from r in ctx.Reservations where min <= r.Date && r.Date < max select r.Quantity) .DefaultIfEmpty(0) .Sum(); if (rendition.Quantity + reservedSeats > capacity) return this.StatusCode(HttpStatusCode.Forbidden); ctx.Reservations.Add(new Reservation { Date = requestedDate, Name = rendition.Name, Email = rendition.Email, Quantity = rendition.Quantity });
![Page 14: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/14.jpg)
DateTimeOffset requestedDate; if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate)) return this.BadRequest("Invalid date."); var min = requestedDate.Date; var max = requestedDate.Date.AddDays(1); using (var ctx = new ReservationsContext()) { var reservedSeats = (from r in ctx.Reservations where min <= r.Date && r.Date < max select r.Quantity) .DefaultIfEmpty(0) .Sum(); if (rendition.Quantity + reservedSeats > capacity) return this.StatusCode(HttpStatusCode.Forbidden); ctx.Reservations.Add(new Reservation { Date = requestedDate, Name = rendition.Name, Email = rendition.Email, Quantity = rendition.Quantity }); ctx.SaveChanges();
![Page 15: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/15.jpg)
if (!DateTimeOffset.TryParse(rendition.Date, out requestedDate)) return this.BadRequest("Invalid date."); var min = requestedDate.Date; var max = requestedDate.Date.AddDays(1); using (var ctx = new ReservationsContext()) { var reservedSeats = (from r in ctx.Reservations where min <= r.Date && r.Date < max select r.Quantity) .DefaultIfEmpty(0) .Sum(); if (rendition.Quantity + reservedSeats > capacity) return this.StatusCode(HttpStatusCode.Forbidden); ctx.Reservations.Add(new Reservation { Date = requestedDate, Name = rendition.Name, Email = rendition.Email, Quantity = rendition.Quantity }); ctx.SaveChanges(); }
![Page 16: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/16.jpg)
return this.BadRequest("Invalid date."); var min = requestedDate.Date; var max = requestedDate.Date.AddDays(1); using (var ctx = new ReservationsContext()) { var reservedSeats = (from r in ctx.Reservations where min <= r.Date && r.Date < max select r.Quantity) .DefaultIfEmpty(0) .Sum(); if (rendition.Quantity + reservedSeats > capacity) return this.StatusCode(HttpStatusCode.Forbidden); ctx.Reservations.Add(new Reservation { Date = requestedDate, Name = rendition.Name, Email = rendition.Email, Quantity = rendition.Quantity }); ctx.SaveChanges(); }
![Page 17: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/17.jpg)
var min = requestedDate.Date; var max = requestedDate.Date.AddDays(1); using (var ctx = new ReservationsContext()) { var reservedSeats = (from r in ctx.Reservations where min <= r.Date && r.Date < max select r.Quantity) .DefaultIfEmpty(0) .Sum(); if (rendition.Quantity + reservedSeats > capacity) return this.StatusCode(HttpStatusCode.Forbidden); ctx.Reservations.Add(new Reservation { Date = requestedDate, Name = rendition.Name, Email = rendition.Email, Quantity = rendition.Quantity }); ctx.SaveChanges(); } return this.Ok();
![Page 18: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/18.jpg)
var min = requestedDate.Date; var max = requestedDate.Date.AddDays(1); using (var ctx = new ReservationsContext()) { var reservedSeats = (from r in ctx.Reservations where min <= r.Date && r.Date < max select r.Quantity) .DefaultIfEmpty(0) .Sum(); if (rendition.Quantity + reservedSeats > capacity) return this.StatusCode(HttpStatusCode.Forbidden); ctx.Reservations.Add(new Reservation { Date = requestedDate, Name = rendition.Name, Email = rendition.Email, Quantity = rendition.Quantity }); ctx.SaveChanges(); } return this.Ok(); }
![Page 19: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/19.jpg)
![Page 20: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/20.jpg)
Test-Induced Damage
![Page 21: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/21.jpg)
public IHttpActionResult Post(ReservationRendition rendition) { if (!this.apiValidator.Validate(rendition)) return this.BadRequest("Invalid date."); var r = this.mapper.Map(rendition); var reservedSeats = this.repository.GetReservedSeats(r.Date); if (!this.maîtreD.CanAccept(rendition.Quantity, reservedSeats)) return this.StatusCode(HttpStatusCode.Forbidden); this.repository.Save(r); return this.Ok(); } }
![Page 22: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/22.jpg)
public interface IApiValidator { bool Validate(ReservationRendition rendition); } public interface IReservationMapper { Reservation Map(ReservationRendition rendition); } public interface IReservationRepository { int GetReservedSeats(DateTimeOffset date); void Save(Reservation reservation); } public interface IMaîtreD { bool CanAccept(int requestedSeats, int reservedSeats); }
![Page 23: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/23.jpg)
public interface IApiValidator { bool Validate(ReservationRendition rendition); } public interface IReservationMapper { Reservation Map(ReservationRendition rendition); } public interface IReservationRepository { int GetReservedSeats(DateTimeOffset date); void Save(Reservation reservation); } public interface IMaîtreD { bool CanAccept(int requestedSeats, int reservedSeats); }
public class ApiValidator : IApiValidator { public bool Validate(ReservationRendition rendition)
![Page 24: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/24.jpg)
public interface IApiValidator { bool Validate(ReservationRendition rendition); } public interface IReservationMapper { Reservation Map(ReservationRendition rendition); } public interface IReservationRepository { int GetReservedSeats(DateTimeOffset date); void Save(Reservation reservation); } public interface IMaîtreD { bool CanAccept(int requestedSeats, int reservedSeats); }
public class ApiValidator : IApiValidator { public bool Validate(ReservationRendition rendition)
![Page 25: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/25.jpg)
Unit Tests!
![Page 26: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/26.jpg)
public class ApiValidatorTests { [Fact] public void ValidateReturnsTrueWhenRenditionHasValidDate() { var date = new DateTimeOffset( new DateTime(2014, 7, 27, 21, 17, 35), TimeSpan.FromHours(2)); var rendition = new ReservationRendition { Date = date.ToString("o"), Name = "Foo Baz", Email = "[email protected]", Quantity = 9 }; IApiValidator sut = new ApiValidator(); var actual = sut.Validate(rendition); Assert.True(actual); } [Fact] public void ValidateReturnsFalseWhenRenditionHasInvalidDate() {
![Page 27: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/27.jpg)
public class ReservationMapperTests { [Fact] public void MapReturnsCorrectResult() { var date = new DateTimeOffset( new DateTime(2014, 7, 27, 21, 25, 3), TimeSpan.FromHours(2)); var rendition = new ReservationRendition { Date = date.ToString("o"), Name = "Ndøh Ploeh", Email = "ndø[email protected]", Quantity = 6 }; IReservationMapper sut = new ReservationMapper(); var actual = sut.Map(rendition); Assert.Equal(date, actual.Date); Assert.Equal(rendition.Name, actual.Name); Assert.Equal(rendition.Email, actual.Email); Assert.Equal(rendition.Quantity, actual.Quantity); } }
![Page 28: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/28.jpg)
public class MaîtreDTests { [Fact] public void CanAcceptReturnsTrueForFirstRequestUnderCapacity() { var capacity = 10; var sut = new MaîtreD(capacity); bool actual = sut.CanAccept(5, 0); Assert.True(actual); } [Fact] public void CanAcceptReturnsFalseForRequestAboveCapacity() { var capacity = 20; var sut = new MaîtreD(capacity); var actual = sut.CanAccept(30, 0); Assert.False(actual); } [Fact]
![Page 29: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/29.jpg)
public class ReservationsControllerTests { [Fact] public void SutIsApiController() { var sut = new ReservationsController( new Mock<IApiValidator>().Object, new Mock<IReservationRepository>().Object, new Mock<IMaîtreD>().Object, new Mock<IReservationMapper>().Object); Assert.IsAssignableFrom<ApiController>(sut); } [Fact] public void PostReturnsCorrectResponseForInvalidRendition() { var invalidRendition = new ReservationRendition { Date = "Not a valid date", Name = "Foo Bar", Email = "[email protected]", Quantity = 3 }; var validatorStub = new Mock<IApiValidator>();
![Page 30: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/30.jpg)
[Fact] public void PostDoesNotSaveReservationWhenMaîtreCannotAccept() { var date = new DateTimeOffset( new DateTime(2014, 7, 27, 20, 56, 57), TimeSpan.FromHours(2)); var rendition = new ReservationRendition { Date = date.ToString("o"), Name = "Fnaah Ploeh", Email = "[email protected]", Quantity = 2 }; var validatorStub = new Mock<IApiValidator>(); validatorStub .Setup(v => v.Validate(rendition)) .Returns(true); var repositoryMock = new Mock<IReservationRepository>(); repositoryMock .Setup(r => r.GetReservedSeats(date)) .Returns(10); var maîtreDStub = new Mock<IMaîtreD>(); maîtreDStub .Setup(m => m.CanAccept(rendition.Quantity, 10)) .Returns(false); var mapperStub = new Mock<IReservationMapper>();
![Page 31: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/31.jpg)
![Page 32: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/32.jpg)
TDD
Test-Induced Damage
?
![Page 33: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/33.jpg)
TDD
Test-Driven Design
![Page 34: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/34.jpg)
Correlation
![Page 35: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/35.jpg)
TDD
Test-Driven Design
Test-Induced Damage
![Page 36: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/36.jpg)
TDD
Test-Driven Development
![Page 37: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/37.jpg)
Functional Programming
![Page 38: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/38.jpg)
Stubs Mocks
http://bit.ly/xunitpatterns
![Page 39: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/39.jpg)
Stubs Mocks
![Page 40: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/40.jpg)
Stubs Mocks
![Page 41: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/41.jpg)
Stubs Mocks
http://bit.ly/growingoos
Queries Commands
Side-effects No side-effects
![Page 42: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/42.jpg)
Stubs Mocks
Queries Commands
Side-effects No side-effects
F# is a Functional-First language
![Page 43: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/43.jpg)
[<Fact>] let ``Post returns correct result on success`` () = let imp _ = Success () use sut = new ReservationsController(imp) let rendition : ReservationRendition = { Date = "2014-08-09+2:00" Name = "Mark Seemann" Email = "[email protected]" Quantity = 4 } let result : IHttpActionResult = sut.Post rendition test <@ result :? Results.OkResult @>
type Result<'TSuccess,'TFailure> = | Success of 'TSuccess | Failure of 'TFailure
http://fsharpforfunandprofit.com/posts/recipe-part2
![Page 44: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/44.jpg)
[<CLIMutable>] type ReservationRendition = { Date : string Name : string Email : string Quantity : int }
type ReservationsController(imp) = inherit ApiController() member this.Post(rendition : ReservationRendition) = this.Ok() :> IHttpActionResult
![Page 45: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/45.jpg)
[<Fact>] let ``Validate.reservation returns correct result on valid date`` () = let rendition : ReservationRendition = { Date = "2014-08-09+2:00" Name = "Mark Seemann" Email = "[email protected]" Quantity = 4 } let actual = Validate.reservation rendition let expected = Success { Date = DateTimeOffset(DateTime(2014, 8, 9), TimeSpan.FromHours 2.) Name = "Mark Seemann" Email = "[email protected]" Quantity = 4 } test <@ expected = actual @>
![Page 46: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/46.jpg)
type Reservation = { Date : DateTimeOffset Name : string Email : string Quantity : int }
let reservation (rendition : ReservationRendition) = Success { Date = rendition.Date |> DateTimeOffset.Parse Name = rendition.Name Email = rendition.Email Quantity = rendition.Quantity }
![Page 47: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/47.jpg)
[<Fact>] let ``Validate.reservation returns right result on invalid date`` () = let rendition : ReservationRendition = { Date = "Not a date" Name = "Mark Seemann" Email = "[email protected]" Quantity = 4 } let actual = Validate.reservation rendition let expected : Result<Reservation, Error> = Failure(ValidationError("Invalid date.")) test <@ expected = actual @>
![Page 48: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/48.jpg)
type Error = | ValidationError of string
let reservation (rendition : ReservationRendition) = match rendition.Date |> DateTimeOffset.TryParse with | (true, date) -> Success { Date = date Name = rendition.Name Email = rendition.Email Quantity = rendition.Quantity } | _ -> Failure(ValidationError "Invalid date.")
![Page 49: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/49.jpg)
[<Fact>] let ``check returns right result at no prior reservations`` () = let capacity = 10 let getReservedSeats _ = 0 let reservation = { Date = DateTimeOffset(DateTime(2014, 8, 10), TimeSpan.FromHours 2.) Name = "Mark Seemann" Email = "[email protected]" Quantity = 4 } let actual = Capacity.check capacity getReservedSeats reservation let expected : Result<Reservation, Error> = Success reservation test <@ expected = actual @>
![Page 50: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/50.jpg)
let check capacity getReservedSeats reservation = Success reservation
![Page 51: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/51.jpg)
[<Fact>] let ``check returns right result at too little remaining capacity`` () = let capacity = 10 let getReservedSeats _ = 7 let reservation = { Date = DateTimeOffset(DateTime(2014, 8, 10), TimeSpan.FromHours 2.) Name = "Mark Seemann" Email = "[email protected]" Quantity = 4 } let actual = Capacity.check capacity getReservedSeats reservation let expected : Result<Reservation, Error> = Failure CapacityExceeded test <@ expected = actual @>
![Page 52: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/52.jpg)
type Error = | ValidationError of string | CapacityExceeded
let check capacity getReservedSeats reservation = let reservedSeats = getReservedSeats reservation.Date if capacity < reservation.Quantity + reservedSeats then Failure CapacityExceeded else Success reservation
![Page 53: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/53.jpg)
[<Fact>] let ``Post returns correct result on validation error`` () = let imp _ = Failure(ValidationError("Invalid date.")) use sut = new ReservationsController(imp) let rendition : ReservationRendition = { Date = "2014-08-09+2:00" Name = "Mark Seemann" Email = "[email protected]" Quantity = 4 } let result : IHttpActionResult = sut.Post rendition test <@ result :? Results.BadRequestErrorMessageResult @>
![Page 54: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/54.jpg)
type ReservationsController ( imp : ReservationRendition -> Result<unit, Error> ) = inherit ApiController() member this.Post(rendition : ReservationRendition) = match imp rendition with | Failure(ValidationError msg) -> this.BadRequest msg :> IHttpActionResult | _ -> this.Ok() :> IHttpActionResult
![Page 55: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/55.jpg)
[<Fact>] let ``Post returns correct result on capacity exceeded`` () = let imp _ = Failure CapacityExceeded use sut = new ReservationsController(imp) let rendition : ReservationRendition = { Date = "2014-08-09+2:00" Name = "Mark Seemann" Email = "[email protected]" Quantity = 4 } let result : IHttpActionResult = sut.Post rendition test <@ result :? Results.StatusCodeResult @> test <@ (result :?> Results.StatusCodeResult).StatusCode = HttpStatusCode.Forbidden @>
![Page 56: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/56.jpg)
type ReservationsController(imp) = inherit ApiController() member this.Post(rendition : ReservationRendition) = match imp rendition with | Failure(ValidationError msg) -> this.BadRequest msg :> IHttpActionResult | Failure CapacityExceeded -> this.StatusCode HttpStatusCode.Forbidden :> IHttpActionResult | Success () -> this.Ok() :> IHttpActionResult
![Page 57: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/57.jpg)
let getReservedSeats (date : DateTimeOffset) = let min = DateTimeOffset(date.Date , date.Offset) let max = DateTimeOffset(date.Date.AddDays 1., date.Offset) use ctx = new ReservationsContext() query { for r in ctx.Reservations do where (min <= r.Date && r.Date < max) select r.Quantity } |> Seq.sum
let saveReservation (reservation : BookingApi.Reservation) = use ctx = new ReservationsContext() ctx.Reservations.Add( Reservation( Date = reservation.Date, Name = reservation.Name, Email = reservation.Email, Quantity = reservation.Quantity)) |> ignore ctx.SaveChanges() |> ignore
![Page 58: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/58.jpg)
type Result<'TSuccess,'TFailure> = | Success of 'TSuccess | Failure of 'TFailure
let bind f x = match x with | Success s -> f s | Failure f -> Failure f
let map f x = match x with | Success s -> Success(f s) | Failure f -> Failure f
http://fsharpforfunandprofit.com/posts/recipe-part2
![Page 59: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/59.jpg)
let imp = Validate.reservation >> Rop.bind (Capacity.check 10 SqlGateway.getReservedSeats) >> Rop.map SqlGateway.saveReservation new ReservationsController(imp) :> _
![Page 60: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/60.jpg)
![Page 61: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/61.jpg)
Functional-First: Few Mocks
Functions: Few interfaces
Function composition: Flexible
![Page 62: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/62.jpg)
Q.E.D.
![Page 63: Look, no Mocks! Functional TDD with F#gotocon.com/.../slides/MarkSeemann_LookNoMocksFunctionalTDD… · TDD is dead!](https://reader034.fdocuments.us/reader034/viewer/2022051807/6007f02f4168966f664cafcb/html5/thumbnails/63.jpg)
TDD is not dead