Russell Tracey
2007-Jul-29 19:50 UTC
[rspec-users] Isolating rails model specs from their implementation
I''m currently taking a Rails project management app I built when learning Rails and adding specs to it. During the course of building the app the requirement that project should be archiveable was added. So a project is in one of two states active or archived. This led to the creation of the following methods: Project.active_projects Project.archived_projects @project.active? @project.archived? @project.archive! @project.unarchive! The current implementation of this is using a separate table of "visibilities" as follows: # Implementation 1 (Current) Tables: Project id name visibility_status_id 1 ActiveProject 1 1 ArchivedProject 2 VisibilityStatuses id name 1 Live 2 Archived But the same behavior could be implemented using a datetime column as follows: # Implementation 2 Tables: Project id name archived_at 1 ActiveProject null 1 ArchivedProject 2007-07-29:18:57 Or in fact numerous other ways e.g. # Implementation 3 Army of cows: Each cow represents a project, the cows wear one of two hats to indicate the active/archived status of the project they represent. ...and so on. It''s my understanding that model specs (and specs in general) should be shielded from the implementation details, so how do i check that Project.active_projects only returns active projects without looking at assuming something about the implementation? My initial thought is to check each of them using one of the other exposed methods above, in this case... Project.active_projects.all? {|p| p.active? } but then i can''t work out how to spec all the other methods without going round in circles so that each spec would end up assuming that the other methods work (in this case that p.active? is working) or worse resorting to peeking at implementation details. Russell Tracey
Kyle Hargraves
2007-Jul-29 20:16 UTC
[rspec-users] Isolating rails model specs from their implementation
Russell Tracey wrote:> It''s my understanding that model specs (and specs in general) should > be shielded from the implementation details, so how do i check that > Project.active_projects only returns active projects without looking > at assuming something about the implementation? My initial thought is > to check each of them using one of the other exposed methods above, in > this case... > > Project.active_projects.all? {|p| p.active? } > > but then i can''t work out how to spec all the other methods without > going round in circles so that each spec would end up assuming that > the other methods work (in this case that p.active? is working) or > worse resorting to peeking at implementation details.Nothing is wrong with assuming that everything else works as it should. In fact, it''s pretty much exactly what you want: you only care about the behaviour of the little facet of the model you''re dealing with *right now*. If something else doesn''t work, some other spec is responsible for demonstrating that it''s misbehaving. As for the specific issue, I typically do something like: Spec that active? and archived? do what they should. Those probably actually do require some form of implementation details -- if X column has Y value, it''s archived, otherwise not, etc. Then, spec archive! (and unarchive!): it "should archive projects" do @project.archive! project = find_that_project_again project.should be_archived end And finally, for your finders: before(:each) do active_projects = set_up_some_active_projects archived_projects = set_up_some_archived_projects end it "should return only active projects" do all_active = Project.active_projects.all? { |p| p.active? } all_active.should be_true end So the entirety really only hinges on active? and archived? working properly. And if active? is just !archived?, there''s only one real spot from which errors will start cascading. HTH, Kyle
David Chelimsky
2007-Jul-29 20:21 UTC
[rspec-users] Isolating rails model specs from their implementation
On 7/29/07, Russell Tracey <russell.tracey at gmail.com> wrote:> I''m currently taking a Rails project management app I built when > learning Rails and adding specs to it. During the course of building > the app the requirement that project should be archiveable was added. > So a project is in one of two states active or archived. > > This led to the creation of the following methods: > > Project.active_projects > Project.archived_projects > > @project.active? > @project.archived? > @project.archive! > @project.unarchive! > > The current implementation of this is using a separate table of > "visibilities" as follows: > > # Implementation 1 (Current) > > Tables: > > Project > id name visibility_status_id > 1 ActiveProject 1 > 1 ArchivedProject 2 > > VisibilityStatuses > id name > 1 Live > 2 Archived > > But the same behavior could be implemented using a datetime column as follows: > > # Implementation 2 > > Tables: > > Project > id name archived_at > 1 ActiveProject null > 1 ArchivedProject 2007-07-29:18:57 > > Or in fact numerous other ways e.g. > > # Implementation 3 > > Army of cows: > > Each cow represents a project, the cows wear one of two hats > to indicate the active/archived status of the project they represent. > > ...and so on. > > It''s my understanding that model specs (and specs in general) should > be shielded from the implementation details, so how do i check that > Project.active_projects only returns active projects without looking > at assuming something about the implementation? My initial thought is > to check each of them using one of the other exposed methods above, in > this case... > > Project.active_projects.all? {|p| p.active? } > > but then i can''t work out how to spec all the other methods without > going round in circles so that each spec would end up assuming that > the other methods work (in this case that p.active? is working) or > worse resorting to peeking at implementation details.Keep in mind that back-filling examples to existing code is a very different process from writing the examples first, which is the situation for which RSpec is intended. In that case, you might start with one example like this: describe Project do it " should not be active by default" do project = Project.create project.should_not be_active end end Then the next example might be that when you activate it should be active: describe Project do it " should not be active by default" { ... } it "should be active after you activate it" do project = Project.create project.activate! project.should be_active end it "should show up in the list of active projects when activated" do project = Project.create project.activate! Project.active_projects.should include(project) end end etc. In this second pair of examples, we never "test" the activate! method in terms of looking at its internal effects (i.e. that it changes something in the database), but rather through the difference in the way the object behaves after having called the activate! method. Make sense? David
Russell Tracey
2007-Jul-29 21:09 UTC
[rspec-users] Isolating rails model specs from their implementation
On 29/07/07, David Chelimsky <dchelimsky at gmail.com> wrote:> On 7/29/07, Russell Tracey <russell.tracey at gmail.com> wrote: > > I''m currently taking a Rails project management app I built when > > learning Rails and adding specs to it. During the course of building > > the app the requirement that project should be archiveable was added. > > So a project is in one of two states active or archived. > > > > This led to the creation of the following methods: > > > > Project.active_projects > > Project.archived_projects > > > > @project.active? > > @project.archived? > > @project.archive! > > @project.unarchive! > > > > The current implementation of this is using a separate table of > > "visibilities" as follows: > > > > # Implementation 1 (Current) > > > > Tables: > > > > Project > > id name visibility_status_id > > 1 ActiveProject 1 > > 1 ArchivedProject 2 > > > > VisibilityStatuses > > id name > > 1 Live > > 2 Archived > > > > But the same behavior could be implemented using a datetime column as follows: > > > > # Implementation 2 > > > > Tables: > > > > Project > > id name archived_at > > 1 ActiveProject null > > 1 ArchivedProject 2007-07-29:18:57 > > > > Or in fact numerous other ways e.g. > > > > # Implementation 3 > > > > Army of cows: > > > > Each cow represents a project, the cows wear one of two hats > > to indicate the active/archived status of the project they represent. > > > > ...and so on. > > > > It''s my understanding that model specs (and specs in general) should > > be shielded from the implementation details, so how do i check that > > Project.active_projects only returns active projects without looking > > at assuming something about the implementation? My initial thought is > > to check each of them using one of the other exposed methods above, in > > this case... > > > > Project.active_projects.all? {|p| p.active? } > > > > but then i can''t work out how to spec all the other methods without > > going round in circles so that each spec would end up assuming that > > the other methods work (in this case that p.active? is working) or > > worse resorting to peeking at implementation details. > > Keep in mind that back-filling examples to existing code is a very > different process from writing the examples first, which is the > situation for which RSpec is intended. In that case, you might start > with one example like this: > > describe Project do > it " should not be active by default" do > project = Project.create > project.should_not be_active > end > end > > Then the next example might be that when you activate it should be active: > > describe Project do > it " should not be active by default" { ... } > > it "should be active after you activate it" do > project = Project.create > project.activate! > project.should be_active > end > > it "should show up in the list of active projects when activated" do > project = Project.create > project.activate! > Project.active_projects.should include(project) > end > end > > etc. > > In this second pair of examples, we never "test" the activate! method > in terms of looking at its internal effects (i.e. that it changes > something in the database), but rather through the difference in the > way the object behaves after having called the activate! method. > > Make sense? > > David > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >Yes this is making more sense now, the only remaining thing i am wondering is, given the above examples, would specing that active?/archived? be possible without resorting to implementation details that Kyle wrote about. If i''m reading them correctly the examples above spec out the behavior of activate!, active_projects and the default state of a project after creation, but don''t define the behavior of active? In this app the projects are actually "active" by default before being archived at some point later. So they are only ever in one of two states, active or archived. I''m thinking something like this describe Project do ... it " Not sure what this would be called?" do project = Project.create project.should be_active project.archive! project.should_not be_active end end
David Chelimsky
2007-Jul-29 21:45 UTC
[rspec-users] Isolating rails model specs from their implementation
On 7/29/07, Russell Tracey <russell.tracey at gmail.com> wrote:> On 29/07/07, David Chelimsky <dchelimsky at gmail.com> wrote: > > On 7/29/07, Russell Tracey <russell.tracey at gmail.com> wrote: > > > I''m currently taking a Rails project management app I built when > > > learning Rails and adding specs to it. During the course of building > > > the app the requirement that project should be archiveable was added. > > > So a project is in one of two states active or archived. > > > > > > This led to the creation of the following methods: > > > > > > Project.active_projects > > > Project.archived_projects > > > > > > @project.active? > > > @project.archived? > > > @project.archive! > > > @project.unarchive! > > > > > > The current implementation of this is using a separate table of > > > "visibilities" as follows: > > > > > > # Implementation 1 (Current) > > > > > > Tables: > > > > > > Project > > > id name visibility_status_id > > > 1 ActiveProject 1 > > > 1 ArchivedProject 2 > > > > > > VisibilityStatuses > > > id name > > > 1 Live > > > 2 Archived > > > > > > But the same behavior could be implemented using a datetime column as follows: > > > > > > # Implementation 2 > > > > > > Tables: > > > > > > Project > > > id name archived_at > > > 1 ActiveProject null > > > 1 ArchivedProject 2007-07-29:18:57 > > > > > > Or in fact numerous other ways e.g. > > > > > > # Implementation 3 > > > > > > Army of cows: > > > > > > Each cow represents a project, the cows wear one of two hats > > > to indicate the active/archived status of the project they represent. > > > > > > ...and so on. > > > > > > It''s my understanding that model specs (and specs in general) should > > > be shielded from the implementation details, so how do i check that > > > Project.active_projects only returns active projects without looking > > > at assuming something about the implementation? My initial thought is > > > to check each of them using one of the other exposed methods above, in > > > this case... > > > > > > Project.active_projects.all? {|p| p.active? } > > > > > > but then i can''t work out how to spec all the other methods without > > > going round in circles so that each spec would end up assuming that > > > the other methods work (in this case that p.active? is working) or > > > worse resorting to peeking at implementation details. > > > > Keep in mind that back-filling examples to existing code is a very > > different process from writing the examples first, which is the > > situation for which RSpec is intended. In that case, you might start > > with one example like this: > > > > describe Project do > > it " should not be active by default" do > > project = Project.create > > project.should_not be_active > > end > > end > > > > Then the next example might be that when you activate it should be active: > > > > describe Project do > > it " should not be active by default" { ... } > > > > it "should be active after you activate it" do > > project = Project.create > > project.activate! > > project.should be_active > > end > > > > it "should show up in the list of active projects when activated" do > > project = Project.create > > project.activate! > > Project.active_projects.should include(project) > > end > > end > > > > etc. > > > > In this second pair of examples, we never "test" the activate! method > > in terms of looking at its internal effects (i.e. that it changes > > something in the database), but rather through the difference in the > > way the object behaves after having called the activate! method. > > > > Make sense? > > > > David > > _______________________________________________ > > rspec-users mailing list > > rspec-users at rubyforge.org > > http://rubyforge.org/mailman/listinfo/rspec-users > > > > Yes this is making more sense now, the only remaining thing i am > wondering is, given the above examples, would specing that > active?/archived? be possible without resorting to implementation > details that Kyle wrote about. If i''m reading them correctly the > examples above spec out the behavior of activate!, active_projects and > the default state of a project after creation, but don''t define the > behavior of active? > > In this app the projects are actually "active" by default before being > archived at some point later. So they are only ever in one of two > states, active or archived. > > I''m thinking something like this > > describe Project do > ... > it " Not sure what this would be called?" do > project = Project.create > project.should be_active > > project.archive! > > project.should_not be_active > end > endChanging state between expectations in one example is a TDD no-no. The reason is that if "project.should be_active" fails, you might do something to fix it and then find that "project.should_not be_active" fails. If you have them in separate examples then the fact that only one fails and not the other gives you better feedback: describe Project do it "should be active when first created" do project = Project.create project.should be_active end it "should not be active after it is archived" do project = Project.create project.archive! project.should_not be_active end end> _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
David Chelimsky
2007-Jul-29 21:49 UTC
[rspec-users] Isolating rails model specs from their implementation
On 7/29/07, David Chelimsky <dchelimsky at gmail.com> wrote:> On 7/29/07, Russell Tracey <russell.tracey at gmail.com> wrote: > > On 29/07/07, David Chelimsky <dchelimsky at gmail.com> wrote: > > > On 7/29/07, Russell Tracey <russell.tracey at gmail.com> wrote: > > > > I''m currently taking a Rails project management app I built when > > > > learning Rails and adding specs to it. During the course of building > > > > the app the requirement that project should be archiveable was added. > > > > So a project is in one of two states active or archived. > > > > > > > > This led to the creation of the following methods: > > > > > > > > Project.active_projects > > > > Project.archived_projects > > > > > > > > @project.active? > > > > @project.archived? > > > > @project.archive! > > > > @project.unarchive! > > > > > > > > The current implementation of this is using a separate table of > > > > "visibilities" as follows: > > > > > > > > # Implementation 1 (Current) > > > > > > > > Tables: > > > > > > > > Project > > > > id name visibility_status_id > > > > 1 ActiveProject 1 > > > > 1 ArchivedProject 2 > > > > > > > > VisibilityStatuses > > > > id name > > > > 1 Live > > > > 2 Archived > > > > > > > > But the same behavior could be implemented using a datetime column as follows: > > > > > > > > # Implementation 2 > > > > > > > > Tables: > > > > > > > > Project > > > > id name archived_at > > > > 1 ActiveProject null > > > > 1 ArchivedProject 2007-07-29:18:57 > > > > > > > > Or in fact numerous other ways e.g. > > > > > > > > # Implementation 3 > > > > > > > > Army of cows: > > > > > > > > Each cow represents a project, the cows wear one of two hats > > > > to indicate the active/archived status of the project they represent. > > > > > > > > ...and so on. > > > > > > > > It''s my understanding that model specs (and specs in general) should > > > > be shielded from the implementation details, so how do i check that > > > > Project.active_projects only returns active projects without looking > > > > at assuming something about the implementation? My initial thought is > > > > to check each of them using one of the other exposed methods above, in > > > > this case... > > > > > > > > Project.active_projects.all? {|p| p.active? } > > > > > > > > but then i can''t work out how to spec all the other methods without > > > > going round in circles so that each spec would end up assuming that > > > > the other methods work (in this case that p.active? is working) or > > > > worse resorting to peeking at implementation details. > > > > > > Keep in mind that back-filling examples to existing code is a very > > > different process from writing the examples first, which is the > > > situation for which RSpec is intended. In that case, you might start > > > with one example like this: > > > > > > describe Project do > > > it " should not be active by default" do > > > project = Project.create > > > project.should_not be_active > > > end > > > end > > > > > > Then the next example might be that when you activate it should be active: > > > > > > describe Project do > > > it " should not be active by default" { ... } > > > > > > it "should be active after you activate it" do > > > project = Project.create > > > project.activate! > > > project.should be_active > > > end > > > > > > it "should show up in the list of active projects when activated" do > > > project = Project.create > > > project.activate! > > > Project.active_projects.should include(project) > > > end > > > end > > > > > > etc. > > > > > > In this second pair of examples, we never "test" the activate! method > > > in terms of looking at its internal effects (i.e. that it changes > > > something in the database), but rather through the difference in the > > > way the object behaves after having called the activate! method. > > > > > > Make sense? > > > > > > David > > > _______________________________________________ > > > rspec-users mailing list > > > rspec-users at rubyforge.org > > > http://rubyforge.org/mailman/listinfo/rspec-users > > > > > > > Yes this is making more sense now, the only remaining thing i am > > wondering is, given the above examples, would specing that > > active?/archived? be possible without resorting to implementation > > details that Kyle wrote about. If i''m reading them correctly the > > examples above spec out the behavior of activate!, active_projects and > > the default state of a project after creation, but don''t define the > > behavior of active? > > > > In this app the projects are actually "active" by default before being > > archived at some point later. So they are only ever in one of two > > states, active or archived. > > > > I''m thinking something like this > > > > describe Project do > > ... > > it " Not sure what this would be called?" do > > project = Project.create > > project.should be_active > > > > project.archive! > > > > project.should_not be_active > > end > > end > > Changing state between expectations in one example is a TDD no-no. The > reason is that if "project.should be_active" fails, you might do > something to fix it and then find that "project.should_not be_active" > fails. If you have them in separate examples then the fact that only > one fails and not the other gives you better feedback: > > describe Project do > it "should be active when first created" do > project = Project.create > project.should be_active > end > > it "should not be active after it is archived" do > project = Project.create > project.archive! > project.should_not be_active > end > endFYI - the two together also tell a more complete story: Project - should be active when first created - should not be active after it is archived Cheers, David
Ashley Moran
2007-Jul-31 14:22 UTC
[rspec-users] Isolating rails model specs from their implementation
On 29 Jul 2007, at 22:45, David Chelimsky wrote:> Changing state between expectations in one example is a TDD no-no. The > reason is that if "project.should be_active" fails, you might do > something to fix it and then find that "project.should_not be_active" > fails.Glad I spotted this - I think I''m guilty of this one. I''ve dumped a copy on the RSpec style page on our wiki <http://www.codeweavers.net/ wiki/bin/view/RSpecCollab/WebHome> until I get chance to tidy it up Ashley