This 1 hour and 20 minute screencast guides you through a TDD Kata for designing with Model-View-Presenter pattern in iOS using the JetBrains AppCode IDE for objective-C and the OCMock static library.
The premise of this kata is that the existing ViewControllers in any iOS application are tightly coupled to presentation layer concerns. Rather than attempting to write tests directly against the view controllers, instead write unit tests to generate a Presenter class, injected with multiple protocols, that will coordinate activities between those protocols. In this classic "bank account transfer" example, the presenter delegates method calls to a remote account repository protocol, a local account repository protocol, and a view protocol. The use of OCMock to mock these protocols enables us to design and understand the interactions, and to "generate-by-usage" each element of the MVP pattern. Later in the kata an IOC container class, "ServiceLocator", is designed by unit test to standardize presenter instantiation in a single location.
This kata uses the JetBrains AppCode IDE to generate all tests and code. The kata has a heavy emphasis on effective use of keyboard shortcuts to generate classes, protocols, methods and import statements quickly and naturally as part of the design process. Please note that in this screencast, the key mappings have been set to the standard Intellij keymap used by Intellij (java). This keymap is often set as the default in other JetBrains IDEs. (JetBrains provides IDEs in multiple programming languages.)
To begin the kata screencast, click here.
Monday, December 03, 2012
Monday, February 13, 2012
TDD with Objective-C and Calculator Kata (using JetBrains' AppCode)
I've just created a 1 hour tutorial/screencast that demonstrates TDD in objective-C (iOS 5) via Roy Osherove's Calculator Kata. The screencast primarily uses JetBrains' new AppCode IDE for objective-C, but it also flips occasionally into XCode 4.2 to set up a storyboard with a simple UIViewController that connects to the TDD-created Calculator class.
The screencast demonstrates a variety of layouts and keyboard shortcuts for AppCode (and to a lesser extent, XCode) as well as covering a number of language features of objective-C.
Please have a look, and if you have any questions, send me a comment at my twitter account.
Screencast: Learning Objective-C via TDD and Calculator Kata
The screencast demonstrates a variety of layouts and keyboard shortcuts for AppCode (and to a lesser extent, XCode) as well as covering a number of language features of objective-C.
Please have a look, and if you have any questions, send me a comment at my twitter account.
Screencast: Learning Objective-C via TDD and Calculator Kata
Sunday, February 12, 2012
DDD Kata, Part 4 (Service Layer with Mocks)
Pre-Requisite: DDD Kata Part 3
Kata Review
In part 2 of the kata, you built a simple service test to demonstrate the passing of the Item from the Inventory aggregate root to the Invoice aggregate root. In part 3 of the kata, you created IUnitOfWork interface to manage atomic transactions with commit and rollbacks.
Now we need to design the real service.
In this test we will "inject" repository interfaces into the service class constructor to do the work of persisting the state changes to our domain entities. The UnitOfWork we created in part 3 of the kata will assist us in this effort.
Completed kata example on github: DDD Kata Part 4 sample code (github)
NOTE If you haven't already download RhinoMocks, download it and add the DLLs to a 3rd Party Libs directory for reference.
1. Open the previous solution you created in kata 3.
2. Add a reference to RhinoMocks.DLL to the library "Kata.Services.Tests.Unit".
3. Use RhinoMocks to mock the following interfaces (use Resharper to generate the new ones).
NOTE Your mocking levels are stub, dynamic, and strict. The 3rd choice strictly enforces the test specfiications. Start with a strict implementation for now.
4. Create a test.
5. Enter code comments, and basic Rhino Mocks method calls, to generate a test skeleton:
Now populate the test skeleton as follows:
6. In declare constants, create constants for productCode and serialNumber
7. In declare constants, build an Inventory instance using method StockItemBy()
8. In declare constants, create an Invoice
9. RhinoMocks tests based on object equality, so make sure you have assigned Ids to all your objects.
10. In expectations, expect that IUnitOfWorkFactory creates IUnitOfWork.
12. Add comment: // Call to Inventory.PullItemBy(productCode)
13. Add comment: // Call to Invoice.BillItem(item)
15. Expect that IUnitOfWork.Commit() is called
17. Between ReplayAll and VerifyAll, create InvoicingService instance with all mocked interfaces.
18. Now use Resharper to generate the method under test: InvoicingService.CreateSimpleInvoice(productCode,serialNumber).
19. Run the test and watch it fail.
20. Use the expectations set in the test to assemble the method.
21. Remember to wrap the call in IUnitOfWork using statement, and to commit at end of the block
22. Invoice will fail because its Id cannot be known, so MODIFY the expectation for InvoiceRepository.Save() by adding:
LastCall.IgnoreArguments();
23. All tests should now pass.
This completes DDD kata part 3.
The next kata will introduce Fluent NHibernate as an implementation framework against the domain entities and repository interfaces that you have created so far.
Kata Review
In part 2 of the kata, you built a simple service test to demonstrate the passing of the Item from the Inventory aggregate root to the Invoice aggregate root. In part 3 of the kata, you created IUnitOfWork interface to manage atomic transactions with commit and rollbacks.
Now we need to design the real service.
In this test we will "inject" repository interfaces into the service class constructor to do the work of persisting the state changes to our domain entities. The UnitOfWork we created in part 3 of the kata will assist us in this effort.
Completed kata example on github: DDD Kata Part 4 sample code (github)
NOTE If you haven't already download RhinoMocks, download it and add the DLLs to a 3rd Party Libs directory for reference.
1. Open the previous solution you created in kata 3.
2. Add a reference to RhinoMocks.DLL to the library "Kata.Services.Tests.Unit".
3. Use RhinoMocks to mock the following interfaces (use Resharper to generate the new ones).
NOTE Your mocking levels are stub, dynamic, and strict. The 3rd choice strictly enforces the test specfiications. Start with a strict implementation for now.
- IInvoiceRepository
- IInventoryRepository
- IUnitOfWorkFactory
- IUnitOfWork
private MockRepository _mockRepository;
private IInvoiceRepository _invoiceRepository;
private IInventoryRepository _inventoryRepository;
private IUnitOfWorkFactory _unitOfWorkFactory;
private IUnitOfWork _unitOfWork;
[SetUp]
public void SetUp()
{
_mockRepository = new MockRepository();
_invoiceRepository = _mockRepository.StrictMock();
_inventoryRepository = _mockRepository.StrictMock();
_unitOfWorkFactory = _mockRepository.StrictMock();
_unitOfWork = _mockRepository.StrictMock();
}
4. Create a test.
5. Enter code comments, and basic Rhino Mocks method calls, to generate a test skeleton:
[Test]
public void CreateSimpleInvoiceMethod_ProductCodeAndSerialNumberInputs_GenratesSimpleInvoice()
{
// declare constants
// expectations
_mockRepository.ReplayAll();.
// call to new service method
_mockRepository.VerifyAll();
}
Now populate the test skeleton as follows:
6. In declare constants, create constants for productCode and serialNumber
7. In declare constants, build an Inventory instance using method StockItemBy()
8. In declare constants, create an Invoice
9. RhinoMocks tests based on object equality, so make sure you have assigned Ids to all your objects.
// declare constants
const string productCode = "ABCD1234";
const string serialNumber = "BB2135315";
var inventory = new Inventory { Id = 1234 };
inventory.StockItemBy(productCode, serialNumber);
var invoice = new Invoice() { Id = 1234 };
10. In expectations, expect that IUnitOfWorkFactory creates IUnitOfWork.
NOTE If the Create() method exists on the implementation class (UnitOfWorkFactory) but not on the interface, use Resharper to generate it on the interface.11. Expect that InventoryRepository.LoadInventoryByProduct(IUnitOfWork uow, string productCode) returns Inventory instance.
12. Add comment: // Call to Inventory.PullItemBy(productCode)
13. Add comment: // Call to Invoice.BillItem(item)
NOTE These must be comments only as they aren't on mock objects. Actual calls would reduce inventory to zero in no context and cause a null error below.14. Expect that InvoiceRepository.Save(IUnitOfWork uow, Invoice invoice) saves invoice.
15. Expect that IUnitOfWork.Commit() is called
NOTE If the Commit() method exists on the implementation class (UnitOfWork) but not on the interface, use Resharper to generate it on the interface.16. Expect that IUnitOfWork.Dispose() is called
// expectations
Expect.Call(_unitOfWorkFactory.Create()).Return(_unitOfWork);
Expect.Call(_inventoryRepository.LoadInventoryByProduct(_unitOfWork, productCode)).Return(inventory);
// Call to Inventory.PullItemBy(productCode)
// Call to Invoice.BillItem(item)
_invoiceRepository.Save(_unitOfWork, invoice);
_unitOfWork.Commit();
_unitOfWork.Dispose();
17. Between ReplayAll and VerifyAll, create InvoicingService instance with all mocked interfaces.
_mockRepository.ReplayAll();
var sut = new InvoicingService(_unitOfWorkFactory, _inventoryRepository, _invoiceRepository);
Invoice actualInvoice = sut.CreateSimpleInvoice(productCode, serialNumber);
_mockRepository.VerifyAll();
18. Now use Resharper to generate the method under test: InvoicingService.CreateSimpleInvoice(productCode,serialNumber).
19. Run the test and watch it fail.
20. Use the expectations set in the test to assemble the method.
21. Remember to wrap the call in IUnitOfWork using statement, and to commit at end of the block
public Invoice CreateSimpleInvoice(string productCode, string serialNumber)
{
using(IUnitOfWork unitOfWork = _unitOfWorkFactory.Create())
{
Inventory inventory = _inventoryRepository.LoadInventoryByProduct(unitOfWork, productCode);
Item item = inventory.PullItemBy(productCode);
var invoice = new Invoice();
invoice.BillItem(item);
_invoiceRepository.Save(unitOfWork, invoice);
unitOfWork.Commit();
return invoice;
}
}
22. Invoice will fail because its Id cannot be known, so MODIFY the expectation for InvoiceRepository.Save() by adding:
LastCall.IgnoreArguments();
23. All tests should now pass.
This completes DDD kata part 3.
The next kata will introduce Fluent NHibernate as an implementation framework against the domain entities and repository interfaces that you have created so far.
DDD Kata, Part 3 (build atomic transaction manager i.e. UnitOfWork)
Pre-requisite: DDD Kata Part 2
Kata Focus
1) Work occurs in the Repository layer, which will be used to persist to and from a data store. The data store will be encapsulated behind interfaces.
2) A pre-requisite activity is to build a wrapper interface to encapsulate transaction commit/rollback, with commit and rollback occurring on Dispose().
Completed kata example on github: DDD Kata Part 3 sample code (github)
The Kata
Time goal: under 30 minutes
Repository layer
1. Create new class libraries:
We will start by creating the interface to wrap transaction commits and rollbacks. For an initial, simple name, we'll use AtomicTransactionManager. In a few minutes we will refactor that to use the name of the corresponding design pattern.
Repository: AtomicTransactionManager
1. In the new Repository unit test library, create class AtomicTransactionManagerTests.cs
2. Verify that AtomicTransactionManager is instance of IAtomicTransactionManager.
3. Verify that the constructor of AtomicTransactionManager sets TransactionState property to “Is Begun”.
6. Verify that IUnitOfWork is instance of IDisposable
7. Verify that the Dispose() method sets TransactionState property to “RolledBack”.
8. Verify that calling first the Commit() method, then the Dispose() method, sets TransactionState Property to “Committed”.
UnitOfWorkFactory
Use a factory class to encapsulate the generation of the IUnitOfWork.
9. Verify that UnitOfWorkFactory is instance of IUnitOfWorkFactory.
10. Verify that IUnitOfWorkFactory.Create() returns an IUnitOfWork instance.
Part 3 of the kata is complete.
In the next kata, we will build the service layer with repository interfaces and mock objects.
Continue with DDD Kata Part 4
Kata Focus
1) Work occurs in the Repository layer, which will be used to persist to and from a data store. The data store will be encapsulated behind interfaces.
2) A pre-requisite activity is to build a wrapper interface to encapsulate transaction commit/rollback, with commit and rollback occurring on Dispose().
Completed kata example on github: DDD Kata Part 3 sample code (github)
The Kata
Time goal: under 30 minutes
Repository layer
1. Create new class libraries:
- Kata.Repository.Tests.Unit
- Kata.Repository
We will start by creating the interface to wrap transaction commits and rollbacks. For an initial, simple name, we'll use AtomicTransactionManager. In a few minutes we will refactor that to use the name of the corresponding design pattern.
Repository: AtomicTransactionManager
1. In the new Repository unit test library, create class AtomicTransactionManagerTests.cs
2. Verify that AtomicTransactionManager is instance of IAtomicTransactionManager.
3. Verify that the constructor of AtomicTransactionManager sets TransactionState property to “Is Begun”.
NOTE You should be using Resharper's "generate-by-usage" (alt-Enter) to generate these properties and methods of the sut. However, make sure that the declared type on the sut is interface, otherwise your generate-by-usage will create class-only properties and methods. It should be creating the properties and methods on the interface.4. Verify that Commit() method sets TransactionState property to “CommitRequested”
The actual name for this design pattern is UnitOfWork. You can read about it here: Martin Fowler, PEAA: Unit of Work5. Refactor the class and interface you have created so far to UnitOfWork and IUnitOfWork
6. Verify that IUnitOfWork is instance of IDisposable
7. Verify that the Dispose() method sets TransactionState property to “RolledBack”.
8. Verify that calling first the Commit() method, then the Dispose() method, sets TransactionState Property to “Committed”.
UnitOfWorkFactory
Use a factory class to encapsulate the generation of the IUnitOfWork.
9. Verify that UnitOfWorkFactory is instance of IUnitOfWorkFactory.
10. Verify that IUnitOfWorkFactory.Create() returns an IUnitOfWork instance.
Part 3 of the kata is complete.
In the next kata, we will build the service layer with repository interfaces and mock objects.
Continue with DDD Kata Part 4
Tuesday, November 22, 2011
DDD Kata, part 2 (Add second aggregate root to domain. Service method stage 1)
Pre-requisite: DDD Kata part 1
Completed kata example on github: DDD Kata Part 2 sample code (github)
Kata Focus
1) A second aggregate root (Inventory).
2) Business methods in each aggregate root to transfer a Product's Item from Inventory to Invoice
3) Service method, stage 1: Non-persistent, no mocks; only to verify service method created, and that it passes Item across the aggregate roots.
NOTE The next kata (part 3) will introduce design of service orchestration through mocks, repository interfaces, and IUnitOfWork with IUnitOfWorkFactory.
The Kata
Time goal: under 30 minutes
Domain: Inventory
Note Steps 1 through 5 work through the same concepts (object identity, read-only collections, sets) as were explored in DDD kata part 1. You may wish to build to the end of step 5 only once, and then use this as a jumping off point for the newer material in kata 2.
1. M: New test classes for Inventory, Product and Item--start by verifying that they are instances of DomainEntityBase.
2. M: Verify that Inventory.Products is read-only collection (instance of IEnumerable<Product>).3. M: Verify that Inventory.AddProduct() increments Inventory.Products collection property.
4. M: Verify that Product.Items is read-only collection (instance of IEnumerable<Item>).
5. M: Verify that Product.AddItem() increments Product.Items collection property.
New concepts begin here.
Domain: DomainEntityBase
Changes to DomainEntityBase are necessary for proper collection add/remove behaviour on transient objects (with Id = 0).
1. M: Verify that TransientId is of type System.Guid.
2. M: Verify that TransientId has a non-empty value (i.e. not = Guid.Empty).
3. B: Verify that two instances of DomainEntityBase with 0 Id, but matching TransientId values are equal.
Domain: Inventory
4. M: Verify that Inventory.GetNewOrExistingProductBy(string productCode) returns product with matching code.
Hint The methods in tests 7 and 8 will both call to the method created in test 6.
5. M: Verify that Inventory.StockItemBy(string productCode, int serialNumber):
7. M: Refactor LineItem. Change its Product property to reference Item instead. Fix and update any broken tests.
8. B: Verify that Invoice.BillItem(Item item) increments Invoice.LineItems, and that the LineItem references the billed Item.
Service layer, Stage 1
Non-persistent, just verifying the football pass of Item from one aggregate root to another.
1. M: Create new class libraries:
Part 2 of the kata is complete.
Continue with DDD Kata Part 3
Completed kata example on github: DDD Kata Part 2 sample code (github)
Kata Focus
1) A second aggregate root (Inventory).
2) Business methods in each aggregate root to transfer a Product's Item from Inventory to Invoice
3) Service method, stage 1: Non-persistent, no mocks; only to verify service method created, and that it passes Item across the aggregate roots.
NOTE The next kata (part 3) will introduce design of service orchestration through mocks, repository interfaces, and IUnitOfWork with IUnitOfWorkFactory.
The Kata
Time goal: under 30 minutes
Domain: Inventory
Note Steps 1 through 5 work through the same concepts (object identity, read-only collections, sets) as were explored in DDD kata part 1. You may wish to build to the end of step 5 only once, and then use this as a jumping off point for the newer material in kata 2.
1. M: New test classes for Inventory, Product and Item--start by verifying that they are instances of DomainEntityBase.
2. M: Verify that Inventory.Products is read-only collection (instance of IEnumerable<Product>).3. M: Verify that Inventory.AddProduct() increments Inventory.Products collection property.
4. M: Verify that Product.Items is read-only collection (instance of IEnumerable<Item>).
5. M: Verify that Product.AddItem() increments Product.Items collection property.
New concepts begin here.
Domain: DomainEntityBase
Changes to DomainEntityBase are necessary for proper collection add/remove behaviour on transient objects (with Id = 0).
1. M: Verify that TransientId is of type System.Guid.
2. M: Verify that TransientId has a non-empty value (i.e. not = Guid.Empty).
3. B: Verify that two instances of DomainEntityBase with 0 Id, but matching TransientId values are equal.
Domain: Inventory
4. M: Verify that Inventory.GetNewOrExistingProductBy(string productCode) returns product with matching code.
Hint The methods in tests 7 and 8 will both call to the method created in test 6.
5. M: Verify that Inventory.StockItemBy(string productCode, int serialNumber):
- increments count of Inventory.Products.First().Items
- sets Product.ProductCode and Item.SerialNumber
- returns Item from Product.Items
- decrements Product.Items (i.e. the Item has been removed)
7. M: Refactor LineItem. Change its Product property to reference Item instead. Fix and update any broken tests.
8. B: Verify that Invoice.BillItem(Item item) increments Invoice.LineItems, and that the LineItem references the billed Item.
Service layer, Stage 1
Non-persistent, just verifying the football pass of Item from one aggregate root to another.
1. M: Create new class libraries:
- Kata.Services.Tests.Unit
- Kata.Services
Part 2 of the kata is complete.
Continue with DDD Kata Part 3
Sunday, October 30, 2011
DDD Kata, part 1 (simple domain: Invoice and LineItem)
Kata Focus
1) Object identity and equality by Id
2) Id maintained in base class (entity object)
3) Equality on properties (value object)
4) A single aggregate root
5) Associations controlled from aggregate root (read-only, unique sets)
6) Business logic verified from the aggregate root
The kata will focus 80% on ORM mechanics (such as ORM issues of identity and equality) and 20% business requirements; tests are therefore delineated as M (for Mechanics) or B (for Business requirement).
The Kata
Time goal: under 30 minutes
A. DomainEntityBase
1. M: Verify that two instances of DomainEntityBase are equal when they have the same ID value
2. M: Verify that two instances are NOT equal when they have different ID values
3. M: Verify that two instances are NOT equal when they have 0 ID values.
B. Invoice, Money, and LineItems association
1. M: Verify that Invoice is an instance of DomainEntityBase.
2. M: Verify that LineItem is an instance of DomainEntityBase.
3. M: Verify that Money's constructor accepts Amount (decimal) and Currency (string) parameters whose values match equivalent properties.
4. M: Verify that Money.Amount and Money.Currency properties are read-only
5. M: Verify that two Moneys are equal when they have the same Amount and Currency.
6. M: Verify that Invoice has a read-only collection of LineItems.
7. M: Verify that adding a LineItem to Invoice increases its count of LineItems from 0 to 1
8. M: Verify that adding the SAME LineItem (by identifier) does not increment the set of LineItems.
9. M: Verify that the bi-directional reference (LineItem.Invoice) equals the owning Invoice.
10. B: Given an existing LineItem, when I try to add a LineItem without a ProductCode,
then I am informed that I must provide a ProductCode.
Bonus (outside the 30 minute kata window)
1. B: Given an existing LineItem with Price (type Money) of Currency CDN, when I try to add a LineItem with a USD Price, then I am informed that all LineItems must share the same Currency.
2. B: Given a set of LineItems, when I check the SubTotal for the Invoice, then the SubTotal matches Sum of the Quantity of LineItems times the Price.
3. B: Given an existing LineItem, when I try to add another LineItem with the same ProductCode,
then the original LineItem for that ProductCode has its Quantity incremented by the quantity of the added item.
4. B.Given an Invoice with LineItems, when the Currency of LineItems is USD and the SubTotal > 100M, then the Discount amount equals 5% of the SubTotal.
Continue with DDD Kata part 2
*****
Test Examples
DomainEntityBase, test 1:
DomainEntityBase, test 2:
DomainEntityBase, test 3:
Invoice_Money_LineItems, test 1:
Invoice_Money_LineItems, test 2:
Invoice_Money_LineItems, test 3:
Invoice_Money_LineItems, test 4:
Invoice_Money_LineItems, test 5:
Invoice_Money_LineItems, test 6:
Invoice_Money_LineItems, test 7:
Invoice_Money_LineItems, test 8:
Invoice_Money_LineItems, test 9:
Invoice_Money_LineItems, test 10:
1) Object identity and equality by Id
2) Id maintained in base class (entity object)
3) Equality on properties (value object)
4) A single aggregate root
5) Associations controlled from aggregate root (read-only, unique sets)
6) Business logic verified from the aggregate root
The kata will focus 80% on ORM mechanics (such as ORM issues of identity and equality) and 20% business requirements; tests are therefore delineated as M (for Mechanics) or B (for Business requirement).
The Kata
Time goal: under 30 minutes
A. DomainEntityBase
1. M: Verify that two instances of DomainEntityBase are equal when they have the same ID value
2. M: Verify that two instances are NOT equal when they have different ID values
3. M: Verify that two instances are NOT equal when they have 0 ID values.
B. Invoice, Money, and LineItems association
1. M: Verify that Invoice is an instance of DomainEntityBase.
2. M: Verify that LineItem is an instance of DomainEntityBase.
3. M: Verify that Money's constructor accepts Amount (decimal) and Currency (string) parameters whose values match equivalent properties.
4. M: Verify that Money.Amount and Money.Currency properties are read-only
5. M: Verify that two Moneys are equal when they have the same Amount and Currency.
6. M: Verify that Invoice has a read-only collection of LineItems.
7. M: Verify that adding a LineItem to Invoice increases its count of LineItems from 0 to 1
8. M: Verify that adding the SAME LineItem (by identifier) does not increment the set of LineItems.
9. M: Verify that the bi-directional reference (LineItem.Invoice) equals the owning Invoice.
10. B: Given an existing LineItem, when I try to add a LineItem without a ProductCode,
then I am informed that I must provide a ProductCode.
Bonus (outside the 30 minute kata window)
1. B: Given an existing LineItem with Price (type Money) of Currency CDN, when I try to add a LineItem with a USD Price, then I am informed that all LineItems must share the same Currency.
2. B: Given a set of LineItems, when I check the SubTotal for the Invoice, then the SubTotal matches Sum of the Quantity of LineItems times the Price.
3. B: Given an existing LineItem, when I try to add another LineItem with the same ProductCode,
then the original LineItem for that ProductCode has its Quantity incremented by the quantity of the added item.
4. B.Given an Invoice with LineItems, when the Currency of LineItems is USD and the SubTotal > 100M, then the Discount amount equals 5% of the SubTotal.
Continue with DDD Kata part 2
*****
Test Examples
DomainEntityBase, test 1:
[Test]
public void TwoInstance_SameIdInput_AreEqual()
{
const int id = 1325123;
var sut1 = new DomainEntityBase { Id = id };
var sut2 = new DomainEntityBase { Id = id };
Assert.AreEqual(sut1, sut2);
}
DomainEntityBase, test 2:
[Test]
public void TwoInstance_DifferentIdInput_AreNotEqual()
{
var sut1 = new DomainEntityBase { Id = 123512 };
var sut2 = new DomainEntityBase { Id = 64236 };
Assert.AreNotEqual(sut1, sut2);
}
DomainEntityBase, test 3:
[Test]
public void TwoInstance_ZeroIdInput_AreNotEqual()
{
var sut1 = new DomainEntityBase { Id = 0 };
var sut2 = new DomainEntityBase { Id = 0 };
Assert.AreNotEqual(sut1, sut2);
}
Invoice_Money_LineItems, test 1:
[Test]
public void Constructor_NoINputs_IsInstanceOfDomainEntityBase()
{
var sut = new Invoice();
Assert.IsInstanceOf(typeof(DomainEntityBase), sut);
}
Invoice_Money_LineItems, test 2:
[Test]
public void Constructor_NoINputs_IsInstanceOfDomainEntityBase()
{
var sut = new LineItem();
Assert.IsInstanceOf(typeof(DomainEntityBase), sut);
}
Invoice_Money_LineItems, test 3:
[Test]
public void Constructor_AmountAndCurrencyInputs_MatchGetterProperties()
{
const decimal amount = 3.25M;
const string currency = "CDN";
var sut = new Money(amount, currency);
Assert.AreEqual(amount, sut.Amount);
Assert.AreEqual(currency, sut.Currency);
}
Invoice_Money_LineItems, test 4:
[Test]
public void Constructor_AmountAndCurrencyInputs_AreReadOnly()
{
const decimal amount = 3.25M;
const string currency = "CDN";
var sut = new Money(amount, currency);
Assert.IsFalse(sut.GetType().GetProperty("Amount").CanWrite);
Assert.IsFalse(sut.GetType().GetProperty("Currency").CanWrite);
}
Invoice_Money_LineItems, test 5:
[Test]
public void TwoInstances_SameCurrencyAndAmountInputs_AreEqual()
{
const decimal amount = 3.25M;
const string currency = "CDN";
var sut1 = new Money(amount, currency);
var sut2 = new Money(amount, currency);
Assert.AreEqual(sut1, sut2); // use struct for default "all-class-members" equality
}
Invoice_Money_LineItems, test 6:
[Test]
public void LineItemsProperty_Getter_IsReadOnlyCollection()
{
var sut = new Invoice();
Assert.IsInstanceOf(typeof(IEnumerable<LineItem>), sut.LineItems);
}
Invoice_Money_LineItems, test 7:
[Test]
public void AddLineItemsMethod_LineItemInput_IncrementLineItemsCollection()
{
var sut = new Invoice();
Assert.AreEqual(0, sut.LineItems.Count());
sut.AddLineItem(new LineItem { ProductCode = "aaa"});
Assert.AreEqual(1, sut.LineItems.Count());
}
Invoice_Money_LineItems, test 8:
[Test]
public void AddLineItemsMethod_SameLineItemTwiceInput_DoesNotIncrementLineItemsCollection()
{
var sut = new Invoice();
var lineItem = new LineItem { Id = 3522, ProductCode = "aaa" };
sut.AddLineItem(lineItem);
Assert.AreEqual(1, sut.LineItems.Count());
sut.AddLineItem(lineItem);
Assert.AreEqual(1, sut.LineItems.Count());
}
Invoice_Money_LineItems, test 9:
[Test]
public void AddLineItemsMethod_LineItemInput_InvoicePropertyMatchesParent()
{
var sut = new Invoice();
var lineItem = new LineItem { Id = 3522, ProductCode = "aaa"};
sut.AddLineItem(lineItem);
Assert.AreEqual(lineItem.Invoice, sut);
}
Invoice_Money_LineItems, test 10:
// Given an existing LineItem, when I try to add a LineItem without a ProductCode,
// then I am informed that I must provide a ProductCode.
[Test]
[ExpectedException(typeof(InvalidLineItemException), ExpectedMessage = "You must provide a ProductCode")]
public void AddLineItemsMethod_LineItemWithoutProductCode_ThrowsException()
{
var sut = new Invoice();
var lineItem = new LineItem { Id = 3522 };
sut.AddLineItem(lineItem);
}
Sunday, July 17, 2011
Branch-Per-Feature using the Total Integration Total Isolation Principle
I posted this originally as a comment on a Google Plus thread here which has a more complete discussion on ideas (and disagreements) regarding Branch-Per-Feature and Continuous Integration.
The purpose of this entry is to focus on how a principle of Total Integration and Total Isolation shapes the approach taken to branch-per feature.
*****
In this approach, all of the following are givens:
1) releases occur on a regular basis, and development is oriented to the release schedule
2) releases coincide with merge to master
3) the release is tagged, and an empty commit immediately following the release commit is also tagged (as the start of the new cycle)
4) the existing integration ("dev") branch and the qa branch from last cycle are now re-pointed to the new start-of-cycle tag
Key point here: master is ONLY updated once per release, at the point of release--but the integration/dev and qa branches originate from (and are therefore identical to) master
The process now begins, which adheres to a "Total Integration Total Isolation" principle
1) Each developer chooses a ticket and creates a branch with that number (eg. In JIRA, tickets ABC-141, ABC-142, ABC-143, ABC-144)
2) This branch will be short lived (it won't live past the release of the ticket) although the actual commits will be preserved.
3) The developer commits frequently their branch (eg. ABC-143).
4) However, as per some of the heated discussion in this thread, the developer also merges every few hours with the integration/dev branch and checks for conflicts, compile fails, and runs all tests either locally or via the CI server's integraton/dev branch build.
5) Merge conflicts are resolved (and cached for future reuse if the DVCS permits), but outright failures requires the dev to go back to their feature branch and make the fix there, before re-attempting the merge to integration/dev branch.
6) Note that the feature branches never merge FROM the integration branch, because this would violate the isolation side of the Total Integration Total Isolation principle.
7) What about major refactorings or new archicture/scaffolding that one or more features need to share? In that case, a new ticket (eg. Dev task ABC-145) is created to hold that shared work and a branch is created (again, off of the start-of-cycle tag) to hold that major refactoring or scaffolding. The features requiring this branch change their start point/dependency from the start-of-cycle tag to this refactoring/scaffolding branch and they will retain this dependency until the end of the release.
8) Going forward, each new work is commited only to the feature branch, and each feature branch is regularly merged to integration/dev. This results in the Total Integration Total Isolation goal
9) Since every branch now originates from start-of-cycle (or from a shared refactoring/scaffolding branch that originated from start-of-cycle), QA can now safely pick and choose which features to merge onto the qa branch, and ultimately, which to release and merge to master.
10) Any features that were not released can be discarded (if rejected entirely) or rebased onto the next start-of-cycle tag (if to be resumed).
The purpose of this entry is to focus on how a principle of Total Integration and Total Isolation shapes the approach taken to branch-per feature.
*****
In this approach, all of the following are givens:
1) releases occur on a regular basis, and development is oriented to the release schedule
2) releases coincide with merge to master
3) the release is tagged, and an empty commit immediately following the release commit is also tagged (as the start of the new cycle)
4) the existing integration ("dev") branch and the qa branch from last cycle are now re-pointed to the new start-of-cycle tag
Key point here: master is ONLY updated once per release, at the point of release--but the integration/dev and qa branches originate from (and are therefore identical to) master
The process now begins, which adheres to a "Total Integration Total Isolation" principle
1) Each developer chooses a ticket and creates a branch with that number (eg. In JIRA, tickets ABC-141, ABC-142, ABC-143, ABC-144)
2) This branch will be short lived (it won't live past the release of the ticket) although the actual commits will be preserved.
3) The developer commits frequently their branch (eg. ABC-143).
4) However, as per some of the heated discussion in this thread, the developer also merges every few hours with the integration/dev branch and checks for conflicts, compile fails, and runs all tests either locally or via the CI server's integraton/dev branch build.
5) Merge conflicts are resolved (and cached for future reuse if the DVCS permits), but outright failures requires the dev to go back to their feature branch and make the fix there, before re-attempting the merge to integration/dev branch.
6) Note that the feature branches never merge FROM the integration branch, because this would violate the isolation side of the Total Integration Total Isolation principle.
7) What about major refactorings or new archicture/scaffolding that one or more features need to share? In that case, a new ticket (eg. Dev task ABC-145) is created to hold that shared work and a branch is created (again, off of the start-of-cycle tag) to hold that major refactoring or scaffolding. The features requiring this branch change their start point/dependency from the start-of-cycle tag to this refactoring/scaffolding branch and they will retain this dependency until the end of the release.
8) Going forward, each new work is commited only to the feature branch, and each feature branch is regularly merged to integration/dev. This results in the Total Integration Total Isolation goal
9) Since every branch now originates from start-of-cycle (or from a shared refactoring/scaffolding branch that originated from start-of-cycle), QA can now safely pick and choose which features to merge onto the qa branch, and ultimately, which to release and merge to master.
10) Any features that were not released can be discarded (if rejected entirely) or rebased onto the next start-of-cycle tag (if to be resumed).
Friday, July 01, 2011
Branch-Per-Feature: Successful Transitions/Cleanup Between Sprints
.
Introduction
Branch-per-feature is the discipline of beginning every feature branch for a given sprint off exactly the same commit (typically, the first commit of the sprint). The strict enforcement of isolation between features quickly reveals the bad habits of dependencies we build between multiple features, and forces us to ask the right questions of how to keep our features independent.
Some benefits of branch-per-feature:
Successful Transitions (Cleanup) Between Sprints
The focus of this blog post is more narrow: to define the steps involved in successful transitions between sprints/releases when using branch-per-feature to manage the release.
We've been working out the logistics for this recently at work, under the guidance of @martinaatmaa and @adymitruk. What this looks like:
Over the course of a given sprint, each feature is branched off a common commit from the start of the sprint. As each feature reaches a point of stability and completion, it is merged back into an integration branch (named something like projectname-dev). When qa is ready to test, all features to be tested are folded into a qa branch (named something like projectname-qa). Upon release, the projectname-qa branch is merged into master, and tagged as the release branch for that sprint. Now that the code has been released, the feature branches for that sprint are no longer required. They are cleaned up (deleted), although the underlying commits are kept.
Examples and screenshots illustrating this process are shown below.
To begin the next sprint, a new empty commit is created and tagged something like start-sprint2. Each feature is created off that starting commit.
Scaffolding
Early in the sprint, if it is determined that a scaffolding commit is necessary to hold some common architecture to be used by all features, a new scaffolding-only feature is created for that purpose. The scaffolding feature branch becomes the new starting point of the commit, with all features branching off that pre-requisite feature.
Feature Branch Naming
The tools we are using to achieve this are git and JIRA (with the Greenhopper plugin). For feature branch naming, we use the JIRA ticket names. For example, a project whose tickets are named PAYGATE-265, PAYGATE-286 would have corresponding git feature branch names (paygate-265, paygate-286).
To practise my skills in branch-per-feature at home, I've been using Ubuntu, rails, git, and a local install of JIRA/Greenhopper. The project used for this practise is the book Ruby on Rails 3 Tutorial. I've broken out the chapter contents into individual features as JIRA tickets. The JIRA project is broken up into very small sprints (1 weekend worth of work per sprint, probably 4-6 hours at most.)
The rest of this blog entry demonstrates branch-per-feature using this simple project, as we fold the features of sprint 1 into an integration branch, then a qa branch, and finally a release branch. We then clean up the old branches and begin work on sprint 2 features, with each one branching off the starting commit of sprint 2.
Here is a screenshot of sprint 1 in JIRA. Each ticket in this sprint has been implemented as a feature branch in git:
To complete the sprint (and its related release), I perform 3 steps:
With the sprint completed, its time to go back to JIRA (with Greenhopper plugin) to verify that everything in sprint 1 is closed, and that remaining story points are at 0, followed by setting up and prioritizing sprint 2:
Now that sprint 2 is prepared, its time for the dev team to begin coding. I switch to the JIRA/Greenhopper task board view, and drag my first ticket LRNRAILS-15 to In Progress:
To begin work on the dev tickets, we need to first set up the new sprint 2 in git. I begin by creating an empty commit off the release/master branch. To do this, I checkout the tag release-lrnrails-sprint1 (or master branch). To create the new commit of an empty branch:
I then tag it with a name such as start-lrnrails-sprint2.
Since this is the commit that all features will originate from, I will also move my integration (rails-dev) and QA branches (rails-qa) to this commit (by checking out eadch branch and then using git reset --hard start-lrnrais-sprint2 to point them at the starting commit).
Finally, we need to CLEAN UP (remove) all of the sprint 1 feature branches as they are no longer needed. At work we relied on @adymitruk's bash scripts to construct the delete commands that clear out both the local and remote branches. I tried this with my project at home, and got it to work successfully.
For example, given that I want to delete branch jira-lrnrails-5, my local and remote commands would be:
To achieve this for all branch-per-features which were merged into the release, I checkout the release-lrnrails-sprint1 tag (or master branch), and then run the following commands, first as preview (using echo to verify the commands):
and then the actual execution of the commands:
Then, the same for the remote branches:
Now, with all of the branch-per-features deleted for sprint1, the view is much cleaner in gitk:
Now I am ready to begin work on sprint 2. I checkout the starting point commit for this sprint:
and then create my feature branch off that starting commit:
I then proceed to write the code for this feature. At a certain point, I will do one-or-more commits for this feature branch:
With the feature branch commited, I am ready to test my integration branch for the first time. I check out the integration branch (myprojectname-dev) and then do a git merge --no-ff against the new feature branch:
This gives me my first integration branch merge of sprint 2:
From here, the second (and subsequent) sprints can move forward using branch-per-feature to properly isolate code changes, and to enable QA to assemble release packages based on a specifically chosen subset of verified features.
Introduction
Branch-per-feature is the discipline of beginning every feature branch for a given sprint off exactly the same commit (typically, the first commit of the sprint). The strict enforcement of isolation between features quickly reveals the bad habits of dependencies we build between multiple features, and forces us to ask the right questions of how to keep our features independent.
Some benefits of branch-per-feature:
- dev: proper isolation of code changes; breaking bad habits of code dependencies between branches
- dev: embracing granularity of code changes
- dev/QA: (almost) painless merging
- QA: ability to assemble a release made up only of branches that are ready
Successful Transitions (Cleanup) Between Sprints
The focus of this blog post is more narrow: to define the steps involved in successful transitions between sprints/releases when using branch-per-feature to manage the release.
We've been working out the logistics for this recently at work, under the guidance of @martinaatmaa and @adymitruk. What this looks like:
Over the course of a given sprint, each feature is branched off a common commit from the start of the sprint. As each feature reaches a point of stability and completion, it is merged back into an integration branch (named something like projectname-dev). When qa is ready to test, all features to be tested are folded into a qa branch (named something like projectname-qa). Upon release, the projectname-qa branch is merged into master, and tagged as the release branch for that sprint. Now that the code has been released, the feature branches for that sprint are no longer required. They are cleaned up (deleted), although the underlying commits are kept.
Examples and screenshots illustrating this process are shown below.
To begin the next sprint, a new empty commit is created and tagged something like start-sprint2. Each feature is created off that starting commit.
Scaffolding
Early in the sprint, if it is determined that a scaffolding commit is necessary to hold some common architecture to be used by all features, a new scaffolding-only feature is created for that purpose. The scaffolding feature branch becomes the new starting point of the commit, with all features branching off that pre-requisite feature.
Feature Branch Naming
The tools we are using to achieve this are git and JIRA (with the Greenhopper plugin). For feature branch naming, we use the JIRA ticket names. For example, a project whose tickets are named PAYGATE-265, PAYGATE-286 would have corresponding git feature branch names (paygate-265, paygate-286).
Note If a scaffolding feature is required in the sprint, the scaffolding branch will have a normal branch name (paygate-262) and all subsequent commits can indicate the dependency in their name (paygate-265-d-262, paygate-286-d-262).Practise Scenario
To practise my skills in branch-per-feature at home, I've been using Ubuntu, rails, git, and a local install of JIRA/Greenhopper. The project used for this practise is the book Ruby on Rails 3 Tutorial. I've broken out the chapter contents into individual features as JIRA tickets. The JIRA project is broken up into very small sprints (1 weekend worth of work per sprint, probably 4-6 hours at most.)
The rest of this blog entry demonstrates branch-per-feature using this simple project, as we fold the features of sprint 1 into an integration branch, then a qa branch, and finally a release branch. We then clean up the old branches and begin work on sprint 2 features, with each one branching off the starting commit of sprint 2.
Here is a screenshot of sprint 1 in JIRA. Each ticket in this sprint has been implemented as a feature branch in git:
To complete the sprint (and its related release), I perform 3 steps:
- merge all the tickets into the integration branch (rails-dev) and test the code
- upon success, merge all passing features into the QA branch (rails-qa) and test the code
- upon success, merge rails-qa into master and tag it as the release.
With the sprint completed, its time to go back to JIRA (with Greenhopper plugin) to verify that everything in sprint 1 is closed, and that remaining story points are at 0, followed by setting up and prioritizing sprint 2:
Now that sprint 2 is prepared, its time for the dev team to begin coding. I switch to the JIRA/Greenhopper task board view, and drag my first ticket LRNRAILS-15 to In Progress:
To begin work on the dev tickets, we need to first set up the new sprint 2 in git. I begin by creating an empty commit off the release/master branch. To do this, I checkout the tag release-lrnrails-sprint1 (or master branch). To create the new commit of an empty branch:
git commit --allow-empty
I then tag it with a name such as start-lrnrails-sprint2.
git tag start-lrnrails-sprint2
Since this is the commit that all features will originate from, I will also move my integration (rails-dev) and QA branches (rails-qa) to this commit (by checking out eadch branch and then using git reset --hard start-lrnrais-sprint2 to point them at the starting commit).
Finally, we need to CLEAN UP (remove) all of the sprint 1 feature branches as they are no longer needed. At work we relied on @adymitruk's bash scripts to construct the delete commands that clear out both the local and remote branches. I tried this with my project at home, and got it to work successfully.
For example, given that I want to delete branch jira-lrnrails-5, my local and remote commands would be:
git branch -D jira-lrnrails-5
git push origin :jira-lrnrails-5
To achieve this for all branch-per-features which were merged into the release, I checkout the release-lrnrails-sprint1 tag (or master branch), and then run the following commands, first as preview (using echo to verify the commands):
git branch --merged | grep lrnrails -i | xargs -i{} echo git branch -D {}
and then the actual execution of the commands:
git branch --merged | grep lrnrails -i | xargs -i{} git branch -D {}
Then, the same for the remote branches:
git branch -r --merged | grep lrnrails -i | cut -d '/' -f 2 | xargs -i{} echo git push origin :{}
git branch -r --merged | grep lrnrails -i | cut -d '/' -f 2 | xargs -i{} git push origin :{}
Now, with all of the branch-per-features deleted for sprint1, the view is much cleaner in gitk:
Now I am ready to begin work on sprint 2. I checkout the starting point commit for this sprint:
git checkout start-lrnrails-sprint2
and then create my feature branch off that starting commit:
git checkout -b jira-lrnrails-15
I then proceed to write the code for this feature. At a certain point, I will do one-or-more commits for this feature branch:
git add . -A
git commit -m "LRNRAILS-15 Adding variables to the views"
With the feature branch commited, I am ready to test my integration branch for the first time. I check out the integration branch (myprojectname-dev) and then do a git merge --no-ff against the new feature branch:
git merge --no-ff jira-lrnrails-15
This gives me my first integration branch merge of sprint 2:
From here, the second (and subsequent) sprints can move forward using branch-per-feature to properly isolate code changes, and to enable QA to assemble release packages based on a specifically chosen subset of verified features.
Saturday, March 19, 2011
Programming in sprint cycles (currently sprint 9)
This post is meant to be a bit more observational than my usual how-to posts. It describes what my first experiencing of programming in agile sprints has been like.
The sprints are set up on a weekly basis, starting on Thursday mornings with a demo to the stakeholders of the completed user stories of the just-completed sprint. Each demo is guided from a list of (JIRA-based) tickets stored in a spreadsheet. Read out the ticket name, go over the acceptance criteria, hide the spreadsheet, and demo the completed feature. The demos are recorded in Camtasia the day before, and stored online for ease of access by all members of the team for later reference (eg. an additional stakeholder added, or a team member back from 2 weeks vacation.)
With the demo completed, a sprint retrospective is held, guided by our team lead / SCRUM person. We go into more or less detail, depending on the week. (I've found this process to be amazingly clarifying and effective.) We then launch JIRA, go into the planning view, add any additional stories/tasks/bugs that have become evident, assign any unassigned tickets, and place them in likely order of completion. We then go through each ticket and give it a story point rating. This rating has been much easier to arrive at with each subsequent sprint. We look at the total story points, knowing what our average velocity has become per sprint, and either push tickets back to the later sprint, or perhaps bring tickets forward into the current sprint if we are a bit short.
By this point it is typically noon, so we grab some lunch. After lunch I go to my desk, launch JIRA, flip into task board view, and look at my backlog. I drag over (typically the top) item. Most of the time, I try and keep only one item in the active development swimlane (although by the end of a busy day (several smaller story-point tickets may have traversed from backlog to development to tech review).
I'm now ready to start, so I launch git bash, change to the git repo containing the area where I need to work (typically web applications, with a submodule to shared libraries). I do a git fetch to get the latest, checkout the dev branch, and do a git merge --ff-only origin/dev if necessary to catch up to latest. I think create a local branch on that commit, typically with the JIRA ticket number in the branch name, something like prd1234addMissingNullCheckGuardCondition (I've always liked long descriptive branch names or for that matter, method names in code, since tab in git or Intellisense in VS will be happy to save the typing effort for me later).
At this point, I try and gain an understanding of what's needed. Before long, I'll be creating an NUnit test. The test may be in a Nnnn.Tests.Unit project or Nnnn.Tests.Integration project, depending on the legacy code situation and the ability (or inability) to substitute dependencies. The general rule of thumb at the moment is that if it is new functionality altogether, or if it is abstracting/creating utility or helper classes that work with the legacy code, then unit tests will be possible. For new functionality, dependencies are DI injected and tested with mocks or just simple fake implementation classes.
When the work is completed, I do git fetch again, fast-forward the dev branch if new work has been added, swtich to my local branch, rebase it onto the dev branch, switch to dev branch, merge in my local branch, check the results in gitk --all GUI view, and if everything looks clean, push it to origin. I do this in the submodule / shared libraries first, and then again at the outer level. This push to origin is picked up on by the TeamCity integration server, and for the current project that typically means I want to wait for TeamCity to create the artifact (a combination of pages and xcopy-deploy style DLLs whose structure I have setup and verified on TeamCity awhile back). When TeamCity, compiles, passes all tests, and generates the artifact successfully, I save the artifact zip file to the web server, unzip it, move it to the deploy directory, update the permissions, and repoint the IIS home directory for that project to the new artifact.
With that complete, I drag the ticket from the development swimlane to the tech review swimlane, alert QA that it is ready, typically verbally, often supplemented with an explanation either in JIRA or just in email, and look to my backlog for the next ticket.
This is of course interrupted constantly by emails, discussions, whiteboard sketch sessions, morning standup, and afternoon coffee runs, but in the midst of daily chaos, the ongoing process of the tickets moving across the swimlanes gives a steady rhythm to the week.
We're in sprint 9 now, and probably one more sprint will finish this project. Another project is in the works. This is a most interesting, satisfying, and fascinating process.
The sprints are set up on a weekly basis, starting on Thursday mornings with a demo to the stakeholders of the completed user stories of the just-completed sprint. Each demo is guided from a list of (JIRA-based) tickets stored in a spreadsheet. Read out the ticket name, go over the acceptance criteria, hide the spreadsheet, and demo the completed feature. The demos are recorded in Camtasia the day before, and stored online for ease of access by all members of the team for later reference (eg. an additional stakeholder added, or a team member back from 2 weeks vacation.)
With the demo completed, a sprint retrospective is held, guided by our team lead / SCRUM person. We go into more or less detail, depending on the week. (I've found this process to be amazingly clarifying and effective.) We then launch JIRA, go into the planning view, add any additional stories/tasks/bugs that have become evident, assign any unassigned tickets, and place them in likely order of completion. We then go through each ticket and give it a story point rating. This rating has been much easier to arrive at with each subsequent sprint. We look at the total story points, knowing what our average velocity has become per sprint, and either push tickets back to the later sprint, or perhaps bring tickets forward into the current sprint if we are a bit short.
By this point it is typically noon, so we grab some lunch. After lunch I go to my desk, launch JIRA, flip into task board view, and look at my backlog. I drag over (typically the top) item. Most of the time, I try and keep only one item in the active development swimlane (although by the end of a busy day (several smaller story-point tickets may have traversed from backlog to development to tech review).
I'm now ready to start, so I launch git bash, change to the git repo containing the area where I need to work (typically web applications, with a submodule to shared libraries). I do a git fetch to get the latest, checkout the dev branch, and do a git merge --ff-only origin/dev if necessary to catch up to latest. I think create a local branch on that commit, typically with the JIRA ticket number in the branch name, something like prd1234addMissingNullCheckGuardCondition (I've always liked long descriptive branch names or for that matter, method names in code, since tab in git or Intellisense in VS will be happy to save the typing effort for me later).
At this point, I try and gain an understanding of what's needed. Before long, I'll be creating an NUnit test. The test may be in a Nnnn.Tests.Unit project or Nnnn.Tests.Integration project, depending on the legacy code situation and the ability (or inability) to substitute dependencies. The general rule of thumb at the moment is that if it is new functionality altogether, or if it is abstracting/creating utility or helper classes that work with the legacy code, then unit tests will be possible. For new functionality, dependencies are DI injected and tested with mocks or just simple fake implementation classes.
When the work is completed, I do git fetch again, fast-forward the dev branch if new work has been added, swtich to my local branch, rebase it onto the dev branch, switch to dev branch, merge in my local branch, check the results in gitk --all GUI view, and if everything looks clean, push it to origin. I do this in the submodule / shared libraries first, and then again at the outer level. This push to origin is picked up on by the TeamCity integration server, and for the current project that typically means I want to wait for TeamCity to create the artifact (a combination of pages and xcopy-deploy style DLLs whose structure I have setup and verified on TeamCity awhile back). When TeamCity, compiles, passes all tests, and generates the artifact successfully, I save the artifact zip file to the web server, unzip it, move it to the deploy directory, update the permissions, and repoint the IIS home directory for that project to the new artifact.
With that complete, I drag the ticket from the development swimlane to the tech review swimlane, alert QA that it is ready, typically verbally, often supplemented with an explanation either in JIRA or just in email, and look to my backlog for the next ticket.
This is of course interrupted constantly by emails, discussions, whiteboard sketch sessions, morning standup, and afternoon coffee runs, but in the midst of daily chaos, the ongoing process of the tickets moving across the swimlanes gives a steady rhythm to the week.
We're in sprint 9 now, and probably one more sprint will finish this project. Another project is in the works. This is a most interesting, satisfying, and fascinating process.
Sunday, September 19, 2010
Using git submodules to share a backend library between two local repositories
Acknowledgements
The following knowledge about git submodule usage has been achieved from on-site training with Adam Dymitruk. (Also, be sure to check out Adam's article in Code Magazine: Git from a Developer's Perspective.)
What scenario is this blog entry meant to address?
This blog entry addresses the common scenario where multiple web, Windows, Silverlight, console, or other clients all reference a common set of backend libaries. The backend libraries would contain the domain model, repository layer, utils, external gateway references, and so on.
Why git submodules?
git submodules present a distinct advantage over other methods for referencing backend libraries. Let's look at some of the alternate approaches generally used.
Approaches for referencing backend libraries from multiple clients
- Referencing compiled DLLs (via file directory or GAC).
- Referencing a web proxy (where backend libraries are deployed via a web services layer).
- Referencing the .csproj files from a single location in your file directory
- Referencing the .csproj files from multiple locations (one per local git repository)
Let's have a more detailed look at each approach:
Referencing compiled DLLs
In this case, you treat the backend libraries as a somewhat fixed SDK, much as you might treat a 3rd party library. In debug mode, your breakpoints end at the boundary of the compiled DLL.
Referencing a web proxy
You build a Remote Facade over the domain, in which all possible domain interactions are encapsulated in coarse-grained service methods. You then expose this layer as web services, and in your clients, create web services proxies, which you then reference. In debug mode, your breakpoints end at the boundary of the web service proxy.
Referencing the .csproj files from a single location in file directory
You wish to debug right from the client through to the backend, so you reference your backend libraries in two distinct ways:
- In standard fashion, you have a solution file for the backend libraries themselves, within the enclosing folder. This solution also includes various unit/integration test projects for building / validating your backend libraries.
- However, your external clients ALSO reference one or more of the library/projects as .csproj files, within your local client solution file.
Which brings us to the 4th approach:
Referencing the .csproj files from multiple locations (one per local git repository)
The following diagram demonstrates the file directory configuration for this approach:
In this last scenario, each local git repository (a grouping of client projects, based on some common organizing principle determined by your organization) has a local copy of the backend libaries stored WITHIN that local repository. The benefit provided is that each copy of the backend libaries can exist in a different state (i.e. at a different point in the git commit history, pointing at a different branch.)
Here's what that might look like for you on some future Tuesday at work:
- In the morning, you are working on the SilverlightClientApps local repository, on ClientApp2. For this you require the backend libaries in release1.8 branch.
- In the afternoon, you are working on the WebClientApps local repository, on ClientApp5. for this you require the backend libraries in hotfix_2010Oct branch.
Steps to setup a git submodule
Let's assume you are starting by installing git. We'll then make 3 local libaries: WebApps, WinApps, and BackendLibaries. We'll turn each one into a git repository, store them all in a fake local server (which is good enough for our purposes at the moment), and then setup additional local copies of the BackendLibaries as submodules with distinct commits, in each local repository (i.e. WebApps and WinApps).
Do the following:
- Install git if you don't have it already installed.
- Launch GitBash.
- Create a local directory called /c/dev/
- As per the following screenshot, set up myGitSubmoduleDemo with 3 subdirectories for webApps, winApps, and backend Libraries
- Go to the webApps subdirectory, and set it up as a local git repository, with a single commit, and set to the dev branch:
- Repeat for the winApps directory
- And finally, repeat for the backendLibraries, but with 2 release branches
- Next: you need to set up all 3 local repositories as server repositories. In this example, you simply create a local directory at /c/MyFakeGitRepositoryServer/, and make a bare clone of each local repository in this location.
- Now return to your local repositories, and use the "git remote add origin" command to connect your local repository to each server repository
- Congratulations! You are now ready to create a submodule within each client repository (webApps and winApps). Let's start by creating the submodule within webApps
- Note that in the last step, you change directory from the outer directory, "webApps" to the inner directory (the submodule) "backendLibraries". Notice that these directories are in completely different branches. Other than relative placement, they are INDEPENDENT. You can edit either the outer client repository or the inner submodule, you could add commits to either one, change branches, or do any other git action should you choose; but these are independent local repositories, and your actions in each are separate and independent.
- Now let's repeat the above steps for the "winApps" local repository:
- And finally, just to make the point perfectly clear: let's make a NEW branch on this copy of backendLibraries, add a new file within this branch, and commit it:
- Note that this new branch is on the backendLibraries submodule, WITHIN winApps. The enclosing local repository winApps is unchanged (still within dev branch). Go up one level to the winApps branch, and type "git status" to check your status.
- The git status commands does observer some changes, but these are all EXTERNAL changes, about the submodule itself: it sees that a submodule has been added (along with a .gitmodules configuration file); and it can tell that backendLibaries has been modified. But it knows nothing about the changes within the backendLibraries submodule, because that is not within its scope. You are now managing, two, separate local repositories, one of which happens to be nested as a submodule within the other.
Does having two, independent copies of the backendLibaries in each local repository remind you of anything? It should: this scenario is indistinguishable from having two developers, on two separate machines doing branching, commits, merges, and checkins (git push) on local copies of backendLibraries. It just happens that in this case, those two local developers are both YOU: you, in the morning, working on some webApps and the backendLibraries submodule within webApps; and you, in the afternoon, working on some windowsApps and the backendLibraries submodule within windowsApps.
In conclusion, the main benefits of git submodules (as implemented in the above scenario) are twofold:
- You can use direct .csproj references to your backend Libraries in your client apps.
- You can maintain independent (and diverging) branches of those backend Libraries in each local git repository that you configure with a backendLibraries submodule.
Saturday, August 28, 2010
Combining TDD kata with git branching and merging
I've been learning from Adam Dymitruk recently how to do git branching and merging from the command line. I thought I would codify some of this in a blog post. Since I also am interested in TDD kata, I'm going to try combining the two into a shared TDD/git kata.
To do this, I'll take the first couple of tests from Roy Osherove's Calcualtor kata, and combine them with the practice of git branching and merging. You really don't want to do this unless you already have some comfort with doing Calculator kata; if you are new to the Calculator kata (or to TDD generally) this git configuration will only prove to be a distraction.
However, if you are already comfortable doing the Calculator kata, and want to work on git branching and merging, this will provide some practice.
This blog entry assumes you have the following installed:
* git and the GitBash command-line tool
* Visual Studio 2008 or 2010
* Resharper
This WON'T assume you have access to a git repository on a server, we will fake a "git repo server" on your local hard drive instead.
Setup
1) On your local drive, create a folder to represent your fake git repo server, named something like:
4) Launch GitBash.
5) Use the cd command to change to your solution directory. For this blog entry, let's assume that location is:
You'll observe that the path now includes a branch name, "master" in brackets at the end of the path. In this git/TDD kata, we'll be creating additional branches: a permanent branch named "development", and any number of temporary, feature branches, with names like "myFirstTest". These branches will be merged back into the development branch and discarded as each minor feature reaches a relative degree of solidity (the test passes) and we are ready to move on. No matter how many temporary feature branches we create, we will always return back to (and merge into) the development branch.
9) Now that git knows which files to ignore, let's have a look at your current situation. Type 'git status'
10) From here, going forward, for basic git checkin, you will use the same 4 git commands over and over:
11) Now let's commit those files:
12) At this point, you need to push these files to the git remote repository on the server--but we haven't set that up yet! To do this, change directory to the c:\FakeGitRepoServer:
13) Now you can return to your local repository, set up a connection to the new remote repo, and push your files to that repo. To return to your local repository, type:
15) As above, you can VIEW your remote settings with:
17) Congratulations--you have your Calculator kata started, and your first set of code changes have been:
Excellent! Setup is complete, we're ready to begin the kata!
TDD Calculator kata with branching and merging
1) Create a new "feature" branch for your first test:
3) Switch to Visual Studio, and create your first test. Something like this:
4) Use Resharper to create the Calculator class and move it to the Domain class libary; and get the test to pass.
5) Now use the following 4 commands to checkin your code to your feature branch:
7) Go back to Visual Studio and run your test again, it should pass.
8) Now return to git, and switch to the development branch:
10) Try running your test. Note that the code has disappeared! This is because the code exists only in your firstTest feature branch at the moment. Since you know the code is solid (the test is passing), it is safe to merge it back to dev. Let's do so:
11) Your changes are now merged. Switch to Visual Studio, and reload. (If Visual Studio seems confused about folders or .csproj files, just close and re-open the solution).
12) You should now be able to run your first test while in the development branch; in other words, you have confirmed that the merge was successful.
13) You don't need the feature branch anymore, so delete it:
14) Would you really create and delete a feature branch for a single test? Probably not; you'd probably do a series of tests to flesh out the feature, then merge them, then delete the feature branch. But the purpose, as always, with a kata is to get comfortable practicing a process and making it intuitive. So let's proceed to create a temporary feature branch for the next for kata tests.
15) Create a new feature branch named secondTest.
16) Switch back to Visual Studio, and create your second test, something like:
17) Once again, modify your code until the test passes.
18) Once again, run the following 4 commands to checkin your code and push it to the remote repository (under the feature branch secondTest):
20) Once again, return to git and merge the branches. (This time, try adding the tag --no-ff, which stands for no-fast-forward. This leaves a little more merge history in your record to view later as necessary):
21) Once again, go to Visual Studio and confirm that your second test is running (i.e. has been merged) within the development branch.
22) And finally, once again, delete the feature branch secondTest since you no longer need it:
Conclusion
In this kata you have been familiarizing yourself with the process for combining the TDD process with the git feature branching and merging process.
The key takeaway is the understanding of using git branching as a dynamic process for development, which can be learned effectively as part of working through a TDD kata process that you are already familiar with.
To do this, I'll take the first couple of tests from Roy Osherove's Calcualtor kata, and combine them with the practice of git branching and merging. You really don't want to do this unless you already have some comfort with doing Calculator kata; if you are new to the Calculator kata (or to TDD generally) this git configuration will only prove to be a distraction.
However, if you are already comfortable doing the Calculator kata, and want to work on git branching and merging, this will provide some practice.
This blog entry assumes you have the following installed:
* git and the GitBash command-line tool
* Visual Studio 2008 or 2010
* Resharper
This WON'T assume you have access to a git repository on a server, we will fake a "git repo server" on your local hard drive instead.
Setup
1) On your local drive, create a folder to represent your fake git repo server, named something like:
- c:\FakeGitRepoServer
NOTE You don't NEED it here for the repository. This is just a convenient place to store it. You'll be copying into each project folder as part of your git configuration.3) Launch Visual Studio 2008/10 and create a new solution named "TDDKata_Calcuator_2010Aug28" with two class libraries:
- MyCompName.Kata.Domain
- MyCompName.Kata.Tests.Unit
4) Launch GitBash.
5) Use the cd command to change to your solution directory. For this blog entry, let's assume that location is:
- c:\SourceCode\dotNet\TDDKata_Calculator_2010Aug28\
NOTE The examples in this blog post use relative pathing to move around. Some people find this tiresome. If you'd like a simple alternative, you can specify your paths from the drive root. For example, assuming you are using the C: drive, you could use commands such as:6) Any new folder that isn't yet set up for git must first be initialized for git. Type: "git init"
- cd /c/SourceCode/dotNet/TDDKata_Calculator_2010Aug28
You'll observe that the path now includes a branch name, "master" in brackets at the end of the path. In this git/TDD kata, we'll be creating additional branches: a permanent branch named "development", and any number of temporary, feature branches, with names like "myFirstTest". These branches will be merged back into the development branch and discarded as each minor feature reaches a relative degree of solidity (the test passes) and we are ready to move on. No matter how many temporary feature branches we create, we will always return back to (and merge into) the development branch.
NOTE At the very end of the kata, we will merge the development branch back into the master branch. This is analagous to deployment to production.
NOTE For a definitive article on git branching for team development, see the article A Successful Git Branching Model.8) But first things first: Let's get a local copy of our .gitignore file, something we should always do immediately after initializing the folder. To copy the .gitignore file from the folder where you stored it earlier to your current folder, type:
- cp ../../../FakeGitRepository/.gitignore .
9) Now that git knows which files to ignore, let's have a look at your current situation. Type 'git status'
10) From here, going forward, for basic git checkin, you will use the same 4 git commands over and over:
- git status // this updates you on the file status
- git add . -A // this adds the files to be tracked
- git commit -m "my comment" // this commits the file
- git push // this moves the commit to the remote repository
11) Now let's commit those files:
12) At this point, you need to push these files to the git remote repository on the server--but we haven't set that up yet! To do this, change directory to the c:\FakeGitRepoServer:
cd ../../../FakeGitRepository13) In that directory, create an empty copy (a "bare clone") of your local git directory. The syntax for this is:
git clone --bare ../SourceCode/dotNet/TDDKata_Calculator_2010Aug20/.git
13) Now you can return to your local repository, set up a connection to the new remote repo, and push your files to that repo. To return to your local repository, type:
cd ../SourceCode/dotNet/TDDKataCalculator_2010Aug28/14) Now, from your source directory, to set up a connection to your remote repository, enter:
git remote add origin /c/FakeGitRepoServer/TDDKata_Calculator_2010Aug28.git
15) As above, you can VIEW your remote settings with:
git remote -v16) It took awhile to set up that repository--where were you before you started that? -- You were about to push your local commit to the remote repository. Let's do that now:
git push origin master
17) Congratulations--you have your Calculator kata started, and your first set of code changes have been:
- committed
- pushed to the master branch on the remote repository
git branch development
git checkout development
Excellent! Setup is complete, we're ready to begin the kata!
TDD Calculator kata with branching and merging
1) Create a new "feature" branch for your first test:
git branch firstTest
git checkout firstTest2) The git command line should now show that the current branch is firstTest:
3) Switch to Visual Studio, and create your first test. Something like this:
4) Use Resharper to create the Calculator class and move it to the Domain class libary; and get the test to pass.
5) Now use the following 4 commands to checkin your code to your feature branch:
git add . -A6) Note that when you push your code to the remote repository, that you are pushing it to a parallel branch (firstTest) in the remote repository. This is created on the fly by git if it doesn't already exist.
git commit -m "First test created and passes"
git push origin firstTest
git status
7) Go back to Visual Studio and run your test again, it should pass.
8) Now return to git, and switch to the development branch:
git checkout development9) When you return to Visual Studio, you will be asked to Reload screens. Do so.
10) Try running your test. Note that the code has disappeared! This is because the code exists only in your firstTest feature branch at the moment. Since you know the code is solid (the test is passing), it is safe to merge it back to dev. Let's do so:
git merge firstTest
11) Your changes are now merged. Switch to Visual Studio, and reload. (If Visual Studio seems confused about folders or .csproj files, just close and re-open the solution).
12) You should now be able to run your first test while in the development branch; in other words, you have confirmed that the merge was successful.
13) You don't need the feature branch anymore, so delete it:
git branch -d firstTest
14) Would you really create and delete a feature branch for a single test? Probably not; you'd probably do a series of tests to flesh out the feature, then merge them, then delete the feature branch. But the purpose, as always, with a kata is to get comfortable practicing a process and making it intuitive. So let's proceed to create a temporary feature branch for the next for kata tests.
15) Create a new feature branch named secondTest.
git branch secondTest
git checkout secondTest
16) Switch back to Visual Studio, and create your second test, something like:
17) Once again, modify your code until the test passes.
18) Once again, run the following 4 commands to checkin your code and push it to the remote repository (under the feature branch secondTest):
18) Once again, go to git and checkout the development branch:
git add . -A
git commit -m "Second test created and passes"
git push origin secondTest
git status
git checkout development19) Once again, return to Visual Studio and note that your second test has disappeared.
20) Once again, return to git and merge the branches. (This time, try adding the tag --no-ff, which stands for no-fast-forward. This leaves a little more merge history in your record to view later as necessary):
git merge secondTest --no-ff
21) Once again, go to Visual Studio and confirm that your second test is running (i.e. has been merged) within the development branch.
22) And finally, once again, delete the feature branch secondTest since you no longer need it:
git branch -d secondTest23) Continue creating feature branches for several more tests. When you are done, as your final step, checkout the master branch, and merge all the changes your have made in the development branch to the master branch.
Conclusion
In this kata you have been familiarizing yourself with the process for combining the TDD process with the git feature branching and merging process.
The key takeaway is the understanding of using git branching as a dynamic process for development, which can be learned effectively as part of working through a TDD kata process that you are already familiar with.
Sunday, July 25, 2010
TDD Brownfield example: refactoring a large procedural method to Dependency Injection
It's not uncommon when doing Brownfield TDD to encounter legacy procedural code that is doing multiple distinct actions within a single method. Let's go with a hypothetical example:
"An ASP.NET website has a button_click event handler which does the following:
a) calls to a legacy COM+ object
b) calls to a 3rd party licensed DLL
c) calls to a web service
d) calls some ADO.NET code to save to a database
The method is approximately 100 lines of code and is, in its current form, untestable."
How might you break this down, with some minimal refactoring but without actually changing any of the functionality of the 4 actions within the event handler, to make it testable?
Here is one possible approach:
Begin with Refactoring
1. Do Extract Method refactoring on the event handler to move the code into a seperate method.
2. Use Extract Class refactoring to move this method to a separate class.
3. For testability, this logic needs to be in a separate class library, not in the context of an ASP.NET website, so do the following:
a) create a new project in your solution, of type class library, and name it (eg. Web.Support)
b) move the new class you have created into this project.
c) fix any compiler errors by referencing the new class library and then adding a using statement to the code-behind class.
4. In the new class library, Add References to resolve any compiler errors within the class.
5. Most typically, this will be a reference to System.Web (plus any custom or 3rd-party references your page was using).
When you are done, you should have something like this:
You are now ready to begin creating your unit and integration tests against a coordinator class. This class will coordinate each of the pieces of above functionality, but in the form of interfaces. Each interface will represent a distinct part of the functionality that can be tested separately. Typically in an ASP.NET (WebForm) web site this coordination is achieved using Model-View-Presenter, where the Presenter is a class that will coordinate the various interfaces. These interfaces are made available to the presenter class by "injecting" each interface into the class as a parameter to the class constructor (hence the term: Dependency Injection.) In this case, you are going to have 5 interfaces injected into the presenter class: one for each unique piece of functionality, plus the view interface.
Creating the Unit Test
1. Create two new class library projects:
3. Class variables: you being by declaring repository interfaces for each of the functionality elements you need to test. Note that the names of these interfaces should not reflect the technology--for example, IComPlusObjectRepository is a bad name because at the interface level, one does not know whether COM+ will be used as an implementation. Instead, the names of the interfaces should reflect WHAT the functionality business logic does. Let's rewrite the above 4 method calls as pseudo-code, to define WHAT they actually do:
4. Now that you have an idea of what they do, name your repository interfaces names such as:
6. In the Web.Support class library, you will now need new sub-namespaces for each of these interface or class types. To do this, create the following folders within the class library:
Explanation of the Unit Test with Mocks
For a complete explanation of mock objects, there are many good resources on the web. However, the MAIN purpose of a unit test that uses mock objects is for DESIGN. As you create this unit test, you are designing (experimenting with) possible interactions in the presenter class, using mock versions of the interfaces to speculate what those interface implementations might do, and what values they might return back.
Typically the unit test consists of the specifying mock behaviors (using the Expect() method of Rhino.Mocks for methods or properties that have a return type), following by the ReplayAll() command, followed by the actual presenter instantiation and method call (see above code snippet for exact details.) The [TearDown] method then calls the VerifyAll() command to validate the specification.
Once you start to run the unit tests, the mock object framework will validate the specification and point out where the REAL presenter class doesn't yet match the specification you have created with your mocks. This becomes a trial and error process where you keep adding code to the presenter until all of the expectations which you have set up in your unit test have been satisfied by the presenter.
Why is this important? Why go to all this work to specify the interface interactions within the presenter? Because one you have established a working unit test, you are now in a position to create integration tests where each element can be tested independently.
Integration Tests
You can now create an integration test that ONLY tests the behavior of the COM+ object; the rest of the interfaces can be implemented with a "fake" class whose only purpose is to pretend to succeed. This allows you to focus each integration test on the real behavior of a single interaction.
8. Add a new integration test class to the Tests.Integration class library, named GymMembershipPresenterTests.cs (it will show up under the integration test library so the name can be the same, or different.)
9. Note the code below. For integration tests, we don't have to use a mocking framework. Instead, we create fake classes, whose only purpose is to succeed happily. The first integration test will simply pass by calling all fake classes.
10. The SECOND integration test will test ONLY the interaction with the COM+ object. It does this by replacing one of the fake interface implementations with a real interface implementation, in this case, ComPlusCallerGymMembershipFeeRepository class.
11. What goes into the class ComPlusCallerGymMembershipFeeRepository.cs? A call to the ORIGINAL logic which you worked so hard to extract out into an independent class and an independent method:
From here, you can proceed to create 3 more integration tests (with 3 more real implementation classes that call the original logic in the extracted class.) Each integration test will call only one real implementation, and the rest as fakes, allowing you to independently verify the behavior of each, separate action:
So let's review: you STARTED with a button_click event handler containing long procedural code that did 4 completely distinct actions, which you could not test.
You have ENDED with 4 decoupled interfaces, each representing only one of the actions, and you have the ability to test them independently by implementing each interface, either as mocks in a unit test (to check that your presenter coordinates correctly), as fakes in an integration test (to create pretend success classes for stuff you don't currently care about) or as real implementation classes in an integration test (which actually tests a single, specific action against the original functionality of your legacy code.)
"An ASP.NET website has a button_click event handler which does the following:
a) calls to a legacy COM+ object
b) calls to a 3rd party licensed DLL
c) calls to a web service
d) calls some ADO.NET code to save to a database
The method is approximately 100 lines of code and is, in its current form, untestable."
How might you break this down, with some minimal refactoring but without actually changing any of the functionality of the 4 actions within the event handler, to make it testable?
Here is one possible approach:
Begin with Refactoring
1. Do Extract Method refactoring on the event handler to move the code into a seperate method.
2. Use Extract Class refactoring to move this method to a separate class.
3. For testability, this logic needs to be in a separate class library, not in the context of an ASP.NET website, so do the following:
a) create a new project in your solution, of type class library, and name it (eg. Web.Support)
b) move the new class you have created into this project.
c) fix any compiler errors by referencing the new class library and then adding a using statement to the code-behind class.
4. In the new class library, Add References to resolve any compiler errors within the class.
5. Most typically, this will be a reference to System.Web (plus any custom or 3rd-party references your page was using).
NOTE For broken references to Session, use: System.Web.HttpContext.Current.Session5. Finally, within this new external method, use Extract Method refactoring to break out the 4 unique pieces of functionality into 4 public methods that can be called separately.
When you are done, you should have something like this:
public class MyExtractedClass
{
public void MyExtractedButtonClickEventHandler()
{
// various parameters declared here
CallToLegacyComObject(); // each metod will have various parameters
CallToThirdPartyDLL();
CallToWebService();
CallToAdoNetDbSave();
}
}
You are now ready to begin creating your unit and integration tests against a coordinator class. This class will coordinate each of the pieces of above functionality, but in the form of interfaces. Each interface will represent a distinct part of the functionality that can be tested separately. Typically in an ASP.NET (WebForm) web site this coordination is achieved using Model-View-Presenter, where the Presenter is a class that will coordinate the various interfaces. These interfaces are made available to the presenter class by "injecting" each interface into the class as a parameter to the class constructor (hence the term: Dependency Injection.) In this case, you are going to have 5 interfaces injected into the presenter class: one for each unique piece of functionality, plus the view interface.
Creating the Unit Test
1. Create two new class library projects:
- Tests.Unit
- Tests.Integration
3. Class variables: you being by declaring repository interfaces for each of the functionality elements you need to test. Note that the names of these interfaces should not reflect the technology--for example, IComPlusObjectRepository is a bad name because at the interface level, one does not know whether COM+ will be used as an implementation. Instead, the names of the interfaces should reflect WHAT the functionality business logic does. Let's rewrite the above 4 method calls as pseudo-code, to define WHAT they actually do:
public class MyExtractedClass
{
public void MyExtractedButtonClickEventHandler()
{
// various parameters declared here
// 1. get gym membership fee structure
// 2. parse and write a PDF invoice
// 3. register details with national gym organization
// 4. save gym membership changes to db
}
}
4. Now that you have an idea of what they do, name your repository interfaces names such as:
- IGymMembershipFeeRepository
- IPdfInvoiceParserRepository
- INationalGymRegistrationRepository
- IGymMembershipRepository
6. In the Web.Support class library, you will now need new sub-namespaces for each of these interface or class types. To do this, create the following folders within the class library:
- Repository
- Presenter
- View
NOTE We create the unit test BEFORE the interfaces or classes exist, so we won't have Intellisense assistance as we type out these interface or class names as they do not exist yet. The compiler will flag these in Visual Studio by marking them in red. You can then implement these classes in multiple ways:
- manually
- make use of the Generate by Usage feature in Visual Studio 2010
- install a tool like Resharper to enable you to generate these classes more quickly
[TestFixture]
public class GymMembershipPresenterTests
{
private MockRepository _mockRepository;
private IGymMembershipFeeRepository _gymMembershipFeeRepository;
private IPdfInvoiceParserRepository _pdfInvoiceParserRepository;
private INationalGymRegistrationRepository _nationalGymRegistrationRepository;
private IGymMembershipRepository _gymMembershipRepository;
private IGymMembershipView _startTransactionView;
[SetUp]
public void SetUp()
{
_mockRepository = new MockRepository();
_gymMembershipFeeRepository = _mockRepository.StrictMock<IGymMembershipFeeRepository>();
_pdfInvoiceParserRepository = _mockRepository.StrictMock<IPdfInvoiceParserRepository>();
_nationalGymRegistrationRepository = _mockRepository.StrictMock<INationalGymRegistrationRepository>();
_gymMembershipRepository = _mockRepository.StrictMock<IGymMembershipRepository>();
_gymMembershipView = _mockRepository.StrictMock<IGymMembershipView>();
}
[TearDown]
public void TearDown()
{
_mockRepository.VerifyAll();
}
[Test]
public void Constructor_FiveRepositoryInputs_ConfiguresGymMembershipAndReturnsMessage()
{
const string name = "Sally Wong";
const decimal amount = 35.00M;
GymMembership gymMembership = new GymMembership { Name = name, Amount = amount };
Expect.Call(_gymMembershipFeeRepository.CreateMembershipFee(name)).Return(gymMembership);
InvoicePdf invoice = new InvoicePdf { GymMembership = gymMembership };
Expect.Call(pdfInvoiceParserRepository.CreatePdf(gymMembership)).Return(invoice);
NationalGymInfo nationalGymInfo = new NationalGymInfo { ResponseCode = "<out>some expected xml</out>" };
Expect.Call(nationalGymRegistrationRepository.RegisterDetails(gymMembership.Name, gymMembership.Amount)).Return(nationalGymInfo);
var gymMembership = new GymMembership
{
Name = name,
LockerNumber = 352,
Amount = amount,
NationalGymInfo = nationalGymInfo
};
_gymMembershipRepository.Save(gymMembership);
_gymMembershipView.Message = "Your membership has been processed.";
_mockRepository.ReplayAll();
var sut = new GymMembershipPresenter(_gymMembershipFeeRepository,
_pdfInvoiceParserRepository,
_nationalGymRegistrationRepository
_gymMembershipRepository,
_startTransactionView);
sut.CreateNewGymMembership(name, amount);
}
}
Explanation of the Unit Test with Mocks
For a complete explanation of mock objects, there are many good resources on the web. However, the MAIN purpose of a unit test that uses mock objects is for DESIGN. As you create this unit test, you are designing (experimenting with) possible interactions in the presenter class, using mock versions of the interfaces to speculate what those interface implementations might do, and what values they might return back.
Typically the unit test consists of the specifying mock behaviors (using the Expect() method of Rhino.Mocks for methods or properties that have a return type), following by the ReplayAll() command, followed by the actual presenter instantiation and method call (see above code snippet for exact details.) The [TearDown] method then calls the VerifyAll() command to validate the specification.
Once you start to run the unit tests, the mock object framework will validate the specification and point out where the REAL presenter class doesn't yet match the specification you have created with your mocks. This becomes a trial and error process where you keep adding code to the presenter until all of the expectations which you have set up in your unit test have been satisfied by the presenter.
Why is this important? Why go to all this work to specify the interface interactions within the presenter? Because one you have established a working unit test, you are now in a position to create integration tests where each element can be tested independently.
Integration Tests
You can now create an integration test that ONLY tests the behavior of the COM+ object; the rest of the interfaces can be implemented with a "fake" class whose only purpose is to pretend to succeed. This allows you to focus each integration test on the real behavior of a single interaction.
8. Add a new integration test class to the Tests.Integration class library, named GymMembershipPresenterTests.cs (it will show up under the integration test library so the name can be the same, or different.)
9. Note the code below. For integration tests, we don't have to use a mocking framework. Instead, we create fake classes, whose only purpose is to succeed happily. The first integration test will simply pass by calling all fake classes.
[TestFixture]
public class GymMembershipPresenterTests
{
private IGymMembershipFeeRepository _gymMembershipFeeRepository;
private IPdfInvoiceParserRepository _pdfInvoiceParserRepository;
private INationalGymRegistrationRepository _nationalGymRegistrationRepository;
private IGymMembershipRepository _gymMembershipRepository;
private IGymMembershipView _startTransactionView;
[SetUp]
public void SetUp()
{
_gymMembershipFeeRepository = FakeGymMembershipFeeRepository();
_pdfInvoiceParserRepository = FakePdfInvoiceParserRepository();
_nationalGymRegistrationRepository = FakeNationalGymRegistrationRepository();
_gymMembershipRepository = FakeGymMemberhipsRepository();
_gymMembershipView = FakeGymMembershipView();
}
[Test]
public void Constructor_AllFakeRepositoryInputs_ConfiguresGymMembershipAndReturnsMessage()
{
const string name = "Sally Wong";
const decimal amount = 35.00M;
var sut = new GymMembershipPresenter(_gymMembershipFeeRepository,
_pdfInvoiceParserRepository,
_nationalGymRegistrationRepository
_gymMembershipRepository,
_startTransactionView);
sut.CreateNewGymMembership(name, amount);
Assert.AreEqual("Your membership has been processed.", _gymMembershipView.Message);
}
}
10. The SECOND integration test will test ONLY the interaction with the COM+ object. It does this by replacing one of the fake interface implementations with a real interface implementation, in this case, ComPlusCallerGymMembershipFeeRepository class.
[Test]
public void Constructor_RealComPlusGymMembershipFeeAndFakeRepositoryInputs_ConfiguresGymMembershipAndReturnsMessage()
{
const string name = "Sally Wong";
const decimal amount = 35.00M;
_gymMembershipFeeRepository = new ComPlusCallerGymMembershipFeeRepository();
var sut = new GymMembershipPresenter(_gymMembershipFeeRepository,
_pdfInvoiceParserRepository,
_nationalGymRegistrationRepository
_gymMembershipRepository,
_startTransactionView);
sut.CreateNewGymMembership(name, amount);
Assert.AreEqual("Your membership has been processed.", _gymMembershipView.Message);
}
11. What goes into the class ComPlusCallerGymMembershipFeeRepository.cs? A call to the ORIGINAL logic which you worked so hard to extract out into an independent class and an independent method:
public class ComPlusCallerGymMembershipFeeRepository : IParkingLotRepository
{
public GymMembership CreateMembershipFee(string name)
{
var myExtractedClass = new MyExtractedClass();
double amountCharged = myExtractedClass.CallToLegacyComObject(name);
var gymMembership = new GymMembership { Name = name, Amount = amountCharged };
return gymMembership;
}
}
From here, you can proceed to create 3 more integration tests (with 3 more real implementation classes that call the original logic in the extracted class.) Each integration test will call only one real implementation, and the rest as fakes, allowing you to independently verify the behavior of each, separate action:
- against the COM+ legacy object
- against the 3rd party DLL
- against the web service
- against the ADONET db layer.
So let's review: you STARTED with a button_click event handler containing long procedural code that did 4 completely distinct actions, which you could not test.
You have ENDED with 4 decoupled interfaces, each representing only one of the actions, and you have the ability to test them independently by implementing each interface, either as mocks in a unit test (to check that your presenter coordinates correctly), as fakes in an integration test (to create pretend success classes for stuff you don't currently care about) or as real implementation classes in an integration test (which actually tests a single, specific action against the original functionality of your legacy code.)
NOTE As a final step, you would implement the IGymMembershipView interface on your original ASPX page, and have the button_click event either call directly to the method GymMembershpPresenter.CreateNewGymMembership(name, amount), or via a View event's event handler implementation within the presenter. This is an important final refactoring, so that both your integration tests, and your presentation layer, would be calling the same (tested) code.
Subscribe to:
Posts (Atom)

