TFS Sucks and I Fixed It 3
Well, not really. But at least now I can check out locked files when company policy prohibits the use of TFS’s “Multiple Checkouts” feature.
Here’s the trick: when somebody has a file checked out that you need to modify, do this:
- Enable multiple checkouts on the team project.
- Remove the lock (but not the checkout) from the workspace that has the file checked out.
- Check out the file in your workspace.
- Turn multiple checkouts back off.
If the other person beats you to the checkin, he will notice nothing. However, if you check in first, TFS will prompt him to merge before checking in. (Obviously, if your team is accustomed to pessimistic locking, you should let the the other person know what you’re up to.)
I’ve created a program to do this quickly so that others don’t catch on to your mischief. It has a few quirks (no error checking and some assumptions about the working directory) but it works rather nicely:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
class Program
{
static VersionControlServer _sourceControl;
static Workspace _workspace;
static TeamProject _project;
static void Main(string[] args)
{
ConnectToTfs();
try
{
_project.ExclusiveCheckout = false;
UnlockFiles(args);
CheckoutFiles(args);
}
finally
{
_project.ExclusiveCheckout = true;
}
}
private static void ConnectToTfs()
{
var wsInfo = Workstation.Current.GetLocalWorkspaceInfo(".");
var tfs = TeamFoundationServerFactory.GetServer(wsInfo.ServerUri.ToString());
_sourceControl = (VersionControlServer)tfs.GetService(typeof(VersionControlServer));
_workspace = _sourceControl.GetWorkspace(wsInfo);
_project = _workspace.GetTeamProjectForLocalPath(".");
}
static void UnlockFiles(string[] localPaths)
{
var serverPaths = GetServerPaths(localPaths);
var pendingSets = _sourceControl.QueryPendingSets(serverPaths, RecursionType.Full, null, null);
foreach (var pendingSet in pendingSets)
UnlockItemsFromPendingSet(serverPaths, pendingSet);
}
static string[] GetServerPaths(string[] localPaths)
{
return new List<String>(localPaths)
.Select(arg => _workspace.GetServerItemForLocalItem(arg))
.Select(arg => arg.ToLower())
.ToArray();
}
static void UnlockItemsFromPendingSet(string[] serverPaths, PendingSet pendingSet)
{
foreach (var change in pendingSet.PendingChanges)
{
if (serverPaths.Contains(change.ServerItem.ToLower()))
{
var ws = _sourceControl.GetWorkspace(pendingSet.Computer, pendingSet.OwnerName);
if (ws.SetLock(change.ServerItem, LockLevel.None) > 0)
Console.WriteLine("Lock unset on {0}", change.ServerItem);
}
}
}
private static void CheckoutFiles(string[] args)
{
_workspace.PendEdit(args);
}
}A Quick Tour of NHibernate
After playing around with NHibernate for a couple weeks, I’ve come up with this “whirlwind tour” of sorts. Suppose that you have three classes: Individual, Address and Account. The Individual class contains the individual’s name, DOB, address, and a collection of Account objects. The Account class simply contains a Type property that can be, for example, “Checking” or “Savings”:
public class Individual
{
public Individual()
{
Accounts = new List<Account>();
}
public virtual int Id { get; set; }
public virtual IList<Account> Accounts { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual DateTime DOB { get; set; }
public virtual Address Address { get; set; }
}
public class Address
{
public virtual string Line1 { get; set; }
public virtual string Line2 { get; set; }
public virtual string City { get; set; }
public virtual string State { get; set; }
public virtual string Zip { get; set; }
}
public class Account
{
public virtual int Id { get; set; }
public virtual string Type { get; set; }
}In the database, there is a table called IndividualAccount which allows for a many-to-many relationship between individuals and accounts. This means that an individual can have both a checking and a savings account, and that two individuals can share the same account. Think joint checking. Here’s the DDL:
CREATE TABLE Account (
Id INT IDENTITY NOT NULL,
Type NVARCHAR(255) null,
primary key (Id)
)
CREATE TABLE IndividualAccount (
Account_id INT not null,
Individual_id INT not null
)
CREATE TABLE Individual (
Id INT IDENTITY NOT NULL,
FirstName NVARCHAR(255) null,
LastName NVARCHAR(255) null,
DOB DATETIME null,
Addr1 NVARCHAR(255) null,
Addr2 NVARCHAR(255) null,
City NVARCHAR(255) null,
State NVARCHAR(255) null,
Zip NVARCHAR(255) null,
primary key (Id)
)Mapping
Now we need to tell NHibernate how to map between the object model and the database. We can do this by creating .hbm (Hibernate Mapping) XML files, or we can use Fluent NHibernate to write the mappings in code. I’ve opted for the fluent approach. These two classes (behind the scenes) generate HBM:
public class IndividualMap : ClassMap<Individual>
{
public IndividualMap()
{
Id(x => x.Id);
HasManyToMany<Account>(x => x.Accounts)
.WithTableName("IndividualAccount")
.Cascade.All();
Map(x => x.FirstName);
Map(x => x.LastName);
Map(x => x.DOB);
Component<Address>(x => x.Address, m =>
{
m.Map(x => x.Line1, "Addr1");
m.Map(x => x.Line2, "Addr2");
m.Map(x => x.City);
m.Map(x => x.State);
m.Map(x => x.Zip);
});
}
}
public class AccountMap : ClassMap<Account>
{
public AccountMap()
{
Id(x => x.Id);
HasManyToMany<Individual>(x => x.Individuals)
.WithTableName("IndividualAccount")
.Inverse();
Map(x => x.Type);
}
}Next we have to build an ISessionFactory object. Again, Fluent NHibernate helps us out:
private ISessionFactory BuildSessionFactory()
{
return Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2005
.ConnectionString(c => c.FromConnectionStringWithKey("NHTest"))
)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Individual>())
.BuildSessionFactory();
}Once we have a session factory, we use it to build an ISession object. All persistent objects are registered with this session object. When a session gets “flushed,” NHibernate determines which objects in the session have been added, updated, and deleted and generates the appropriate SQL to “make it so” in the database.
Inserting Data
We can use the following method populate our database with a couple individuals and accounts:
private void PopulateTestData(ISession session)
{
var chad = new Individual
{
FirstName = "Chad",
LastName = "Hendry",
DOB = new DateTime(1980, 7, 30),
Address = new Address
{
Line1 = "12 Main Street",
Line2 = "#1A",
City = "Chicago",
State = "IL",
Zip = "60622"
}
};
chad.Accounts.Add(new Account { Type = "Checking" });
chad.Accounts.Add(new Account { Type = "Savings" });
session.Save(chad);
var bob = new Individual
{
FirstName = "Bob",
LastName = "Jones",
DOB = new DateTime(1970, 10, 24),
Address = new Address
{
Line1 = "13 Main Street",
Line2 = "",
City = "Chicago",
State = "IL",
Zip = "60622"
}
};
bob.Accounts.Add(new Account { Type = "Checking" });
session.Save(bob);
session.Flush();
}The generated SQL from the NHibernate logging is as follows:
NHibernate: INSERT INTO [Individual] (FirstName, LastName, DOB, Addr1, Addr2, City, State, Zip) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7); select SCOPE_IDENTITY(); @p0 = 'Chad', @p1 = 'Hendry', @p2 = '7/30/1980 12:00:00 AM', @p3 = '12 Main Street', @p4 = '#1A', @p5 = 'Chicago', @p6 = 'IL', @p7 = '60622'
NHibernate: INSERT INTO [Account] (Type) VALUES (@p0); select SCOPE_IDENTITY(); @p0 = 'Checking'
NHibernate: INSERT INTO [Account] (Type) VALUES (@p0); select SCOPE_IDENTITY(); @p0 = 'Savings'
NHibernate: INSERT INTO [Individual] (FirstName, LastName, DOB, Addr1, Addr2, City, State, Zip) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7); select SCOPE_IDENTITY(); @p0 = 'Bob', @p1 = 'Jones', @p2 = '10/24/1970 12:00:00 AM', @p3 = '13 Main Street', @p4 = '', @p5 = 'Chicago', @p6 = 'IL', @p7 = '60622'
NHibernate: INSERT INTO [Account] (Type) VALUES (@p0); select SCOPE_IDENTITY(); @p0 = 'Checking'
NHibernate: INSERT INTO IndividualAccount (Individual_id, Account_id) VALUES (@p0, @p1); @p0 = '1', @p1 = '1'
NHibernate: INSERT INTO IndividualAccount (Individual_id, Account_id) VALUES (@p0, @p1); @p0 = '1', @p1 = '2'
NHibernate: INSERT INTO IndividualAccount (Individual_id, Account_id) VALUES (@p0, @p1); @p0 = '2', @p1 = '3'Let’s query to retrieve an individual, update it, and persist it:
[Test]
public void MoveChadToHawaii()
{
var i = _session
.CreateQuery("from Individual i where i.FirstName = :name")
.SetString("name", "Chad")
.List<Individual>()
.First();
i.Address.Line1 = "10 Beach St";
i.Address.Line2 = null;
i.Address.City = "Maui";
i.Address.State = "HI";
i.Address.Zip = "96767";
_session.Flush();
}The generated code is:
NHibernate: select individual0_.Id as Id3_, individual0_.Addr1 as Addr2_3_, individual0_.Addr2 as Addr3_3_, individual0_.City as City3_, individual0_.State as State3_, individual0_.Zip as Zip3_, individual0_.FirstName as FirstName3_, individual0_.LastName as LastName3_, individual0_.DOB as DOB3_ from [Individual] individual0_ where (individual0_.FirstName=@p0 ); @p0 = 'Chad'
NHibernate: UPDATE [Individual] SET Addr1 = @p0, Addr2 = @p1, City = @p2, State = @p3, Zip = @p4, FirstName = @p5, LastName = @p6, DOB = @p7 WHERE Id = @p8; @p0 = '10 Beach St', @p1 = '', @p2 = 'Maui', @p3 = 'HI', @p4 = '96767', @p5 = 'Chad', @p6 = 'Hendry', @p7 = '7/30/1980 12:00:00 AM', @p8 = '1'Lazy Loading
Let’s say we want to load an individual and iterate through the associated accounts:
[Test]
public void LoadIndividualAndIterateAccounts()
{
Console.WriteLine("getting individual");
var individual = _session.Get<Individual>(1);
Console.WriteLine("iterating accounts");
foreach (var account in individual.Accounts)
Console.WriteLine(account.Type);
}The output is as follows:
getting individual
NHibernate: SELECT individual0_.Id as Id3_0_, individual0_.Addr1 as Addr2_3_0_, individual0_.Addr2 as Addr3_3_0_, individual0_.City as City3_0_, individual0_.State as State3_0_, individual0_.Zip as Zip3_0_, individual0_.FirstName as FirstName3_0_, individual0_.LastName as LastName3_0_, individual0_.DOB as DOB3_0_ FROM [Individual] individual0_ WHERE individual0_.Id=@p0; @p0 = '1'
iterating accounts
NHibernate: SELECT accounts0_.Individual_id as Individual2_1_, accounts0_.Account_id as Account1_1_, account1_.Id as Id1_0_, account1_.Type as Type1_0_ FROM IndividualAccount accounts0_ left outer join [Account] account1_ on accounts0_.Account_id=account1_.Id WHERE accounts0_.Individual_id=@p0; @p0 = '1'
Checking
SavingsNotice how the accounts are not queried for until the first time we access the Accounts property. Also notice that we didn’t do anything fancy in our Individual class. The Accounts property is a simple IList<Account>; it knows nothing about the database or even persistence in general. What’s happening is that NHibernate is assigning to the Accounts property a proxy object that implements IList<Account>, and that object queries for the associated accounts when (and if) it’s used. You can also configure NHibernate to perform eager loading if, for example, you want to bring back the individual and accounts all in one query.
Querying
NHibernate provides a query language called HQL. Taken from the NHibernate refererence documentation: “NHibernate is equiped with an extremely powerful query language that (quite intentionally) looks very much like SQL. But don’t be fooled by the syntax; HQL is fully object-oriented, understanding notions like inheritence, polymorphism and association.”
Our simple schema doesn’t give us room to execute too interesting of a query, but consider trying to find all individuals that have a checking account:
SELECT DISTINCT
i
FROM
Individual i
JOIN i.Accounts a
WHERE
a.Type = 'Checking'This produces the following, reasonably decent SQL:
select distinct
individual0_.Id as Id7_,
individual0_.Addr1 as Addr2_7_,
individual0_.Addr2 as Addr3_7_,
individual0_.City as City7_,
individual0_.State as State7_,
individual0_.Zip as Zip7_,
individual0_.FirstName as FirstName7_,
individual0_.LastName as LastName7_,
individual0_.DOB as DOB7_
from
[Individual] individual0_
inner join IndividualAccount accounts1_ on individual0_.Id=accounts1_.Individual_id
inner join [Account] account2_ on accounts1_.Account_id=account2_.Id
where
(account2_.Type='Checking' )Many more interesting examples can be found in the HQL Examples section of the reference documentation.
StructureMap
Today I installed mono and nant on my MacBook and finally got around to test-driving StructureMap, a dependency injection / inversion of control framework for .Net. Here’s what happened:
using System;
using StructureMap;
class Program {
static void Main() {
ObjectFactory.Initialize(x => {
x.ForRequestedType<IMood>().TheDefaultIsConcreteType<Sleepy>();
});
Person p = ObjectFactory.GetInstance<Person>();
p.SaySomething();
}
}
class Person {
private IMood _mood;
public Person(IMood mood) {
_mood = mood;
}
public void SaySomething() {
Console.WriteLine(_mood.Say("I think I need a coffee"));
}
}
interface IMood {
string Say(string message);
}
class Sleepy : IMood {
public string Say(string message) {
return "Zzzz... " + message;
}
}As you can see, to create a new Person we must pass it an IMood object. So, we tell StructureMap that the default concrete type for IMood is Sleepy and let it assemble the Person object accordingly.
In addition to the (recommended) fluent API above, StructureMap supports configuration through an XML configuration file. One way to do this is to tell StructureMap to use it’s default configuration file:
ObjectFactory.Initialize(x => {
x.UseDefaultStructureMapConfigFile = true;
});Then create a file named StructureMap.config in the same directory as your executable with the following XML:
<StructureMap MementoStyle="Attribute">
<DefaultInstance
PluginType="IMood,MyAssembly"
PluggedType="Sleepy,MyAssembly"
/>
</StructureMap>Another feature that seems like it could be useful is the ability to use conditional logic for determining which concrete types are used. For instance, given the following Wired mood:
public class Wired : IMood {
public string Say(string message) {
return message.Replace(" ", "") + "!!";
}
}We can have StructureMap use the Sleepy mood when it’s early and the Wired mood when it’s not:
ObjectFactory.Initialize(x => {
x.InstanceOf<IMood>().Is.Conditional(o => {
o.If(c => IsEarly()).ThenIt.Is.OfConcreteType<Sleepy>();
o.TheDefault.Is.OfConcreteType<Wired>();
});
});That’s just a short overview of some of the features I tried out during my initial sitting with StructureMap. There are several more very interesting features described in the documentation. A couple of the features I’m most interested in and hope to learn (and blog about) soon are:
The ability to define conventions that specify which concrete types are loaded, i.e., convention over configuration.
Ways to scope the lifecycle of objects that StructureMap creates, for instance, “create one object per
HttpContext.”StructureMap’s built-in integration with mocking frameworks such as RhinoMocks and Moq.
WriteRoom, Vim Style
The concept behind WriteRoom is to minimize distractions by being a full-screen, bare-bones editor. After trying it out, I very much liked the concept but was very distracted by the lack of vi keybindings. So, I created the following script to put MacVim into WriteRoom mode:
set lines=30
set columns=70
colorscheme blue
set guifont=Monaco:h18
set guioptions-=r
set fuoptions=background:#00000080
set fu
" hide ~'s
hi NonText guifg=bg
" wrap words
set formatoptions=1
set lbr
" make k and j navigate display lines
map k gk
map j gjI saved this to a file, focus.vim, and I run mvim -S focus.vim file.txt to edit file.txt in WriteRoom mode.
Printing Man Pages in OS X Leopard
Here’s a useful snippet to print nicely formatted man pages in OS X Leopard:
man -t gittutorial | open -f -a /Applications/Preview.app/Or, as a simple script:
#!/opt/local/bin/ruby
ARGV.each do |a|
`man -t #{a} | open -f -a /Applications/Preview.app/`
endCucumber
Here is an example Cucumber feature file that tests a website for displaying information about restaurants. In short, if a restaurant has only one location, that loctaion should be shown on the restaurant’s home page. However, if a restaurant has multiple locations, the restaurant home page should have links to each location:
Feature: Locations
In order to find something to eat
A hungry consumer
Wants to see a restaurant's locations
Scenario: View a restaurant with one location
Given a restaurant named "Yum Tasty"
Given Yum Tasty has the following location:
| name | phone | description |
| North | 333-3333 | the original |
When I go to the homepage for "Yum Tasty"
Then I should see "333-3333"
And I should see "the original"
Scenario Outline: View a restaurant with multiple locations
Given a restaurant named "Yum Tasty"
Given Yum Tasty has the following locations:
| name | phone | description |
| North | 333-3333 | the original |
| South | 444-4444 | new location, same tasty |
When I go to the homepage for "Yum Tasty"
And I follow "<location>"
Then I should see "<phone>"
And I should see "<description>"
Examples:
| location | phone | description |
| North | 333-3333 | the original |
| South | 444-4444 | new location, same tasty |The top four lines contain the feature description. This is simply a description of the feature we are testing and is ignored by Cucumber.
Following the feature description are two scenarios: the first to view a restaurant with a single location, and the second to view a restaurant with multiple locations. Scenarios contain steps that Cucumber executes and reports on as having passed or failed.
Steps start with one the words “Given”, “When”, “Then”, or “And.” The text that follows is matched against a set of step definitions. Each step definition contains a regular expression and a block to execute whenever that regular expression matches a step. For example, here is the step definition that matches the first step in our example scenario:
Given /^a restaurant named "(.+)":$/ do |name|
Factory(:restaurant, :name => name)
endAs you can see, the matches captured by the regular expression are passed to the block. In this case the block uses factory_girl to create a new restaurant using name captured by the regular expression.
The next step in the scenario is more complicated. Following it is some tabular data that gets passed to the matching step definition block. In the block, we take that data, convert it to an array of hashes, and use each hash to create a new location for the specified restaurant:
Given /^(.*) has the following locations?:$/ do |name, locations|
restaurant = Restaurant.find_by_name(name)
locations.hashes.each do |hash|
Factory :location, hash.merge(:restaurant => restaurant)
end
endNow that we have a restaurant and a location, the next step is to visit the restaurant’s home page. Cucumber ships with a number of step definitions that use Webrat to interact with the website by visiting URLs, clicking buttons, following links, validating response text, etc. As a result, the Webrat methods (in this case, visit) are made available to our custom step definitions:
Given /^I go to the homepage for "(.+)"$/ do |name|
restaurant = Restaurant.find_by_name(name)
visit restaurant_url(restaurant)
endThe final two steps are matched by a step definition that ships with Cucumber. They simply take what’s in the quotation marks, convert it to a regular expression, and assert that it matches the current HTTP response.
The next scenario is actually a scenario outline: that means that it runs multiple times, once for each set of example data in the “Examples” section below. The steps in this scenario contain the placeholders <location>, <phone>, and <description>. When running scenario outlines, Cucumber automatically replaces these placeholders with the corresponding values in the current set of sample data.
Example Rails Controller Tests
It’s taken me a while settle on a style for writing controller tests in Rails. The approach I currently use involves a combination of Shoulda, factory_girl, and RR as the mock/stub/double framework. I’ve described in recent posts my reasoning behind writing tests this way, but I’ve yet to provide any examples. So, that’s what this post is about.
All of my controller tests are different, but I’ve tried to pull together some of the more common aspects while building the example presented here.
Let’s get started! These tests are for a controller for the Item model, and items belong to categories. To start, let’s define factories to create instances of the two models:
require 'factory_girl'
Factory.define(:category) do |category|
category.name 'Toys'
category.description 'Things to play with'
end
Factory.define(:item) do |item|
item.category { |i| i.association(:category) }
item.name 'Ball'
item.price 1.50
item.description 'Bounces and rolls'
endNow we have a way to create Category and Item objects, specifying only the parameters that are relevant to the test. For example, if we need to test how something behaves given an expensive item, we might call Factory(:item, :price => 1_000_000). This also creates the associated category.
Now we can start writing the tests. Let’s start with the index action. It should load all the items, assign them, and render the index template:
context "the index action" do
setup do
@items = Array.new(3) { Factory(:item) }
get :index
end
should_render_template :index
should_assign_to :items
should "show all items" do
assert_same_elements @items, assigns(:items)
end
endThe new action must assign to @item an object of type Item and render the new template:
context "the new action" do
setup do
category = Factory(:category)
get :new, :category_id => category.id
end
should_assign_to :item, :class => Item
should_render_template :new
endNext is the create action. These tests use three custom methods, two of which I’ve described in earlier posts: should_create_row and before_setup. The third method, assert_has_attributes, is straightforward:
def assert_has_attributes(attributes, model)
attributes.each do |k, v|
assert_equal v, model.send(k) if v
end
endThe create action should create an item with the correct attributes and attach it to the specified restaurant, set the flash, and redirect to the new item. If the creation fails, it should assign to @item and render the new template:
context "the create action" do
setup do
@attributes = Factory.attributes_for(:item)
@category = Factory(:category)
post :create, :category_id => @category.id,
:item => @attributes
end
should_create_row Item do |item|
assert_has_attributes @attributes, item
assert_equal @category, item.category
end
should_set_flash_to 'Item has been saved'
should_redirect_to('the item') { item_url(Item.last) }
context "when unsuccessful" do
before_setup do
stub.instance_of(Item).save { false }
end
should_assign_to :item, :class => Item
should_render_template :new
end
endThe show action should load the item, assign it, and render the show template:
context "the show action" do
setup do
@item = Factory(:item)
get :show, :id => @item.id
end
should_assign_to(:item) { @item }
should_render_template :show
endThe edit action should load the item, assign it, and render the new template:
context "the edit action" do
setup do
@item = Factory(:item)
get :edit, :id => @item.id
end
should_assign_to(:item) { @item }
should_render_template :edit
endThe update action has to take an existing item and update it with new attributes. When successful, it should update the item, set the flash, and redirect to the item page. When unsuccessful, it should assign to @item and render the edit template:
context "the update action" do
setup do
@item = Factory(:item)
@attributes = {
:name => 'new name',
:price => 3.14
:description => 'new description',
}
put :update, :id => @item.id, :item => @attributes
end
should "update the item" do
@item.reload
assert_has_attributes @attributes, @item
end
should_set_flash_to 'Item has been saved'
should_redirect_to('the item') { item_url(@item) }
context "when unsuccessful" do
before_setup do
stub.proxy(Item).find do |i|
stub(i).update_attributes { false }
end
end
should_assign_to(:item) { @item }
should_render_template :edit
end
endThe destroy action should delete the item, set the flash, and redirect to the category associated to the deleted item. It uses the should_delete_row method described in an earlier post.
context "the destroy action" do
setup do
@item = Factory(:item)
end
context '' do
setup do
delete :destroy, :id => @item.id
end
should_delete_row Item do |item|
assert_equal @item, item
end
should_set_flash_to 'Item has been deleted'
should_redirect_to 'the category' do
category_url(@item.category)
end
end
endMocks in Controller Tests
Over the past couple of months I have changed the way I think about using mocks and stubs, especially as it relates to testing Ruby on Rails controllers.
One purpose behind mock objects is that they enable testing of classes in isolation. The idea is that tests that test only a single thing have just one reason to break and are consequently less brittle.
However, when this is applied to Rails controllers by mocking ActiveRecord, as I’ve done in this post, the mocks actually wound up making the tests more brittle. Why? Because I was overmocking, testing the implementation rather than behavior. I ended up with a bunch of tests like this:
describe "GET :show" do
it "should load the item" do
Item.should_receive(:find).with('3').and_return(@item)
get :show, :id => 3
end
endWriting these types of tests felt like I was testing my ability to remember to write Item.find(params[:id]) in the controller more than anything else.
Eventually I decided to lessesn the mocking in my controller tests, and this meant that my tests were going to have to hit the database. I’ve tried to avoid this in the past for a number of reasons:
Tests that interact with the database are slow, and slow tests make for a bad TDD experience.
I’d be testing more than one thing. For example, the test for the create action would exercise model validation code, which should instead be part of the unit / model tests.
Having to satisfy referential integrity constraints in the database can produce a lot of test setup code that detracts from the clarity of the tests. Why create a customer, a contract, a sales rep and an order when the test is only concerned with the order amount? This could be addressed using Rails fixtures, but I have never had a good experience with them. They’re a maintenance burden, and separating test code and test data makes the tests harder to follow.
After working for a while without mocks in my controller tests, here’s what I have learned:
First, the tests are indeed slower. On my 2.4GHz Core 2 Duo MacBook Pro, my 215 controller tests take about seven seconds to run. That’s slower than using the mock approach, but it’s fast enough to be effective. Also, autotest does a great job of only running the tests that need to be run, which minimizes the problem.
I’ve found the remaining issues to be less of an problem than I imagined thanks to thoughtbot’s factory_girl gem. With factory_girl, you define factories and use those factories to create data for your tests. (This has been named the “Object Mother” pattern.) Take the following example which defines three factories:
Factory.sequence(:login) { |n| "login_#{n}" }
Factory.define(:user) do |user|
user.login { Factory.next(:login) }
user.password "secret"
user.name "Bob"
end
Factory.define(:item) do |item|
item.name "Blanket"
item.price 9.95
end
Factory.define(:purchase) do |purchase|
purchase.user { |p| p.association(:user) }
purchase.item { |p| p.association(:item) }
purchase.quantity 1
endNow, I can type the following to create a purchase along with it’s associated user and item with just one line of code:
@purchase = Factory(:purchase)Perhaps the test requires a purchase with a quantity other than the default of one. That too is simple:
@purchase = Factory(:purchase, :quantity => 2)Being able to create entire object structures in the database with just a single line of code not only keeps tests focused and clear, it also eases the problem of exercising validation code in the controller tests. Granted, if I put a minimum length requirement of 4 characters on User.name, I will have a lot of failing tests, but only until I update the factory. This is a lat easier than, say, going through a bunch of Rails fixtures YAML files.
should_create_row
In writing my controller tests, I’ve found it awkward to verify that a create action actually creates a row in the database. This is a new challenge for me, as I have curbed my use of mock objects in favor of a Shoulda nested context + Factory Girl combination. More on that later.
My early approaches including finding the :last model and verify that it had the expected attributes, finding the new model by some unique attribute, or simply using should_change to verify that the model’s count had increased. However, none of these approaches seemed expressive enough.
What I settled on was creating a Shoulda helper called should_create_row. Here’s an example of how it’s used:
context "the create action" do
setup do
@attributes = Factory.attributes_for(:item)
post :create, :item => @attributes
end
should_create_row Item do |item|
assert_has_attributes @attributes, item
end
endThe assert_has_attributes is a simple helper method that makes sure the object has getter methods and values corresponding to the key/value pairs in the hash.
I’ve also built a should_delete_row helper that verifies that a row has been deleted. However, it’s usage is a bit awkward. This is because the row being deleted cannot be created in the same nested context; if it were, there would be no net change in rows to detect:
context "the destroy action" do
setup do
@item = Factory(:item, :section => @section)
end
context '' do
setup do
delete :destroy, :id => @item.id
end
should_delete_row Item do |item|
assert_equal @item, item
end
end
endA downside of this approach is that you’re forced to make all of your assertions on the new or deleted model object in a single test. While I’m not religious about having a single expectation per test rule, I do try to be conscious of it.
Below is the source code. I’d appreciate any thoughts on how to improve this, especially considering the problems mentioned above!
module ShouldCreateRow
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def should_create_row(model_class, &block)
before = lambda do
@_old_rows = model_class.find(:all)
end
should "create a #{model_class}", :before => before do
new_rows = model_class.find(:all) - @_old_rows
assert_equal 1, new_rows.length
block.bind(self).call(new_rows.first)
end
end
def should_delete_row(model_class, &block)
before = lambda do
@_old_rows = model_class.find(:all)
end
should "delete a #{model_class}", :before => before do
deleted_rows = @_old_rows - model_class.find(:all)
assert_equal 1, deleted_rows.length
block.bind(self).call(deleted_rows.first)
end
end
end
end
Test::Unit::TestCase.send :include, ShouldCreateRowNested Contexts and before_setup
I’m no longer striving to test my controllers in isolation. Doing so led to excessive model-mocking and controller tests that tested more the controller’s implementation than behavior. Avoiding mocks necessarily means that controller tests will hit the database. The question then becomes: how do we put the test data in the database?
My current approach is to use nested contexts to build up and refine test data. For example, the outer context might create a model object, while a nested context stubs that object to be invalid.
But there is a problem. Take the following example:
context "the create action" do
setup do
post :create, :person => Factory.attributes_for(:person)
end
should_redirect_to "person_url(@person)"
context "when the person is invalid" do
setup do
stub.instance_of(Person).save { false }
end
should_render_template :new
end
end The assert_same_elements assertion fails. Why? Because people are stubbed invalid after the GET: index request. What’s needed is some type of before_setup block, and that’s exactly what I duck-punched so I can write my tests the way I like them:
class Shoulda::Context
def initialize_with_before_setup(*args, &blk)
@before_setup_blocks = []
initialize_without_before_setup(*args, &blk)
end
alias_method_chain :initialize, :before_setup
def before_setup(&blk)
@before_setup_blocks << blk
end
def run_all_before_setup_blocks(binding)
self.parent.run_all_before_setup_blocks(binding) if am_subcontext?
@before_setup_blocks.each do |before_setup_block|
before_setup_block.bind(binding).call
end
end
def create_test_from_should_hash(should)
test_name = [
"test:", full_name, "should", "#{should[:name]}. "
].flatten.join(' ').to_sym
if test_unit_class.instance_methods.include?(test_name.to_s)
warn " * WARNING: '#{test_name}' is already defined"
end
context = self
test_unit_class.send(:define_method, test_name) do
begin
context.run_all_before_setup_blocks(self)
context.run_parent_setup_blocks(self)
should[:before].bind(self).call if should[:before]
context.run_current_setup_blocks(self)
should[:block].bind(self).call
ensure
context.run_all_teardown_blocks(self)
end
end
end
endI’m very interested in hearing opinions as to whether or not this is a good idea, or perhaps some insight into writing better tests without such a mechanism. What do you think?
Older posts: 1 2