<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
	<channel>
		<title><![CDATA[Rails Forum - Ruby on Rails Help and Discussion Forum - Testing your model code...]]></title>
		<link>http://railsforum.com/viewtopic.php?id=66</link>
		<description><![CDATA[The most recent posts in Testing your model code....]]></description>
		<lastBuildDate>Sun, 18 Jun 2006 17:44:03 +0000</lastBuildDate>
		<generator>PunBB</generator>
		<item>
			<title><![CDATA[Re: Testing your model code...]]></title>
			<link>http://railsforum.com/viewtopic.php?pid=416#p416</link>
			<description><![CDATA[<p>So I&#039;m back working on these assertions again, just applied the library to a second project and using that to help triangulate in on some gaps -- my length tests currently only work for double bounded ranges, and I think would get confused even with a 0..20 type range as it would try to create a -1 length string to test, etc.</p><p>I&#039;m also struggling to find a good way to extend the assert_uniqueness_of to work for scoped uniqueness.&nbsp; I have individual tests that work, but they require a more complicated pre-configured fixture.... so moving the tests into the generic code will place additional demands on the conventions surrounding the test suite....&nbsp; The current requirements I think are reasonable, but adding more might make it less friendly.</p><p>Finally I&#039;d like to create an assert_format_of to piggyback on the main assert_attribute_required, via a :format=&gt;/ / add-on.&nbsp; However, I&#039;m drawing a blank at a good way to dynamically create strings that don&#039;t match a given regexp -- other than something brute forice like building a random length random string from a large characters set and looping until it doesn&#039;t pass the regexp, and then testing it in the model class.&nbsp; Generating 5-10 such strings within the test to hopefully exercise more of the possible failure modes.&nbsp; However that adds non-determinism into testing, which I don&#039;t like doing...&nbsp; Otherwise I&#039;d need to basically write a parallel regexp parser that creates a list of known failing words by going through the regexp and generating an invalid character at each point in the expression while keeping the rest legit....&nbsp; Anyone know of any existing code that might help me with this?</p>]]></description>
			<author><![CDATA[dummy@example.com (NielsenE)]]></author>
			<pubDate>Sun, 18 Jun 2006 17:44:03 +0000</pubDate>
			<guid>http://railsforum.com/viewtopic.php?pid=416#p416</guid>
		</item>
		<item>
			<title><![CDATA[Re: Testing your model code...]]></title>
			<link>http://railsforum.com/viewtopic.php?pid=408#p408</link>
			<description><![CDATA[<div class="quotebox"><cite>NielsenE wrote:</cite><blockquote><p>[PS, any chance we can get a Ruby syntax highlighter for this forum so we can use ruby blocks and not just code blocks?]</p></blockquote></div><p><a href="http://railsforum.com/viewtopic.php?pid=407#p407">You wanted something?</a> <img src="http://railsforum.com/img/smilies/smile.png" width="15" height="15" alt="smile" /></p>]]></description>
			<author><![CDATA[dummy@example.com (vin)]]></author>
			<pubDate>Sun, 18 Jun 2006 12:01:59 +0000</pubDate>
			<guid>http://railsforum.com/viewtopic.php?pid=408#p408</guid>
		</item>
		<item>
			<title><![CDATA[Re: Testing your model code...]]></title>
			<link>http://railsforum.com/viewtopic.php?pid=402#p402</link>
			<description><![CDATA[<div class="quotebox"><cite>danger wrote:</cite><blockquote><p>I also think that in order to tie the migratory state of the db to tests (like you mentioned) we&#039;d have to either 1) always revert to a previous code version when testing against a previous migration or 2) require each test method to only execute if the current schema_info.version is above a certain level.</p></blockquote></div><p>That&#039;s not quite what I meant by driving the migrations.&nbsp; What I&#039;m trying to find a good way to express in test code is the notion of &quot;this class must include the following persist-able attributes&quot;&nbsp; The simple fact that the class respond_to? the names of the various accessors/mutators isn&#039;t enough to imply that those attributes are round-tripped to the DB.&nbsp; As well as the notion that some attributes aren&#039;t persisted -- take a stereotypical User class.&nbsp; It&#039;ll have to deal with &quot;raw&quot; password and password confirmation fields that aren&#039;t persisted and the hashed password that is persisted, but set indirectly, etc.</p><p>I&#039;ve started writing a &quot;baby-steps&quot; approach to this, but I&#039;m not happy with where its going, so I&#039;m letting it stew while I hope for inspiration... It needs to be updated to my current level of understanding,&nbsp; but ....&nbsp; <a href="http://www.verticalexpressionsoftware.com/blog/rails-and-tdd/">http://www.verticalexpressionsoftware.c &#133; s-and-tdd/</a>&nbsp; the back 2/3rd are most applicable to what I&#039;m struggling with.</p><div class="quotebox"><cite>danger wrote:</cite><blockquote><p>I really like your custom assertions.&nbsp; For some reason I was under the impression that tests needn&#039;t be too DRY.&nbsp; I tend to repeat myself a lot in tests - maybe I should look into that.</p></blockquote></div><p>I first learned TDD from the Astel book, and he was extremely aggressive about refactoring test code.&nbsp; And I&#039;ve normally found that being aggressive in refactoring test code keeps you more honest in refactoring production code.&nbsp; &nbsp;In some sense its the &quot;Broken Window&quot; effect that the Pragmatic Programmers talk about, once you let one place get sloppy, it rapidly degenerates.&nbsp; In the book for instance he&#039;ll have a separate test case for testing a zero element list, versus testing a one-element list.&nbsp; Both test cases needed a custom setup, that was shared by multiple test methods, thus leading to their separation.&nbsp; Furthermore often the two setups are slightly similar so you end up extracting a common superclass for them both to inherit from, etc..&nbsp; The test code quickly gets very DRY.&nbsp; However it does make naming even more important, a lot of your test case setup (called a fixture in other languages, but not too exactly equal to Rails fixtures) can get pushed to super classes and you might not be able to tell at a quick glance exactly what the state of the object was in pre-test.&nbsp; If your naming is clear, that solves that problem.&nbsp; (Rails conventions and those of Test::Rails on top, can get slightly in the way here as well).</p><p>Rails makes it slightly hard to refactor functional (or controller under Test::Rails) tests as some of the automated plumbing can get confused, but I&#039;ve been working through that most of today, and have a workable solution -- ie one that meets my needs and is clean, but not one that is releasable as a plugin/patch.</p>]]></description>
			<author><![CDATA[dummy@example.com (NielsenE)]]></author>
			<pubDate>Sun, 18 Jun 2006 07:12:09 +0000</pubDate>
			<guid>http://railsforum.com/viewtopic.php?pid=402#p402</guid>
		</item>
		<item>
			<title><![CDATA[Re: Testing your model code...]]></title>
			<link>http://railsforum.com/viewtopic.php?pid=401#p401</link>
			<description><![CDATA[<p>Sure, but please understand these are still a work in progress and the work of a neophyte ruby coder.&nbsp; Plus they are getting complex enough that I want to split them off to a side project complete with their own suite of tests to test the test....&nbsp; I find it enjoyable coding, but I shouldn&#039;t be so distracted from my main development efforts....</p><p>I also need to covert them to give better default failure messages, they should probably be changed to use assert_block in many cases, and I think I should extract a wrapper lambda/block to remove the duplicated code for retrieving the classname and deal with caching the attribute hash.</p><p>These currently live in test_helper.rb<br /><pre name="code" class="ruby:nogutter">class Test::Unit::TestCase<br />&nbsp; self.use_transactional_fixtures = true<br />&nbsp; self.use_instantiated_fixtures&nbsp; = false<br />&nbsp; &nbsp; </p><p>&nbsp; protected<br />&nbsp; def assert_unique_on (field)<br />&nbsp; &nbsp; klass = class_under_test<br />&nbsp; &nbsp; cached = @attr_hash.clone</p><p>&nbsp; &nbsp; cached[field]=get_reference_fixture.send(field)<br />&nbsp; &nbsp; current = klass.count<br />&nbsp; &nbsp; tmp = klass.create(cached)<br />&nbsp; &nbsp; assert_field_invalid tmp, field<br />&nbsp; &nbsp; assert_equal current, klass.count</p><p>&nbsp; &nbsp; tmp.destroy<br />&nbsp; end<br />&nbsp; <br />&nbsp; <br />&nbsp; def assert_attribute_required(field,opt={})<br />&nbsp; &nbsp; klass = class_under_test<br />&nbsp; &nbsp; cached = @attr_hash.clone</p><p>&nbsp; &nbsp; cached[field]=nil<br />&nbsp; &nbsp; obj = klass.new(cached)<br />&nbsp; &nbsp; assert_not_valid obj<br />&nbsp; &nbsp; assert_field_invalid obj, field</p><p>&nbsp; &nbsp; assert_required_length(field,opt[:length],opt) if opt.has_key? :length<br />&nbsp; &nbsp; assert_unique_on(field) if (opt.has_key?(:unique) &amp;&amp; opt[:unique])<br />&nbsp; &nbsp; assert_confirmation_required(field, opt[:confirmed]) if (opt.has_key?(:confirmed))<br />&nbsp; end</p><p>&nbsp; def assert_confirmation_required(field, confirm)<br />&nbsp; &nbsp; klass = class_under_test<br />&nbsp; &nbsp; cached = @attr_hash.clone</p><p>&nbsp; &nbsp; cached[confirm]=cached[confirm].swapcase.reverse<br />&nbsp; &nbsp; obj = klass.new(cached)<br />&nbsp; &nbsp; assert_not_valid obj<br />&nbsp; &nbsp; assert_field_invalid obj, field<br />&nbsp; end</p><p>&nbsp; def assert_required_association(field)<br />&nbsp; &nbsp; &nbsp;field_id = (field.to_s + &quot;_id&quot;).to_sym</p><p>&nbsp; &nbsp; &nbsp;klass = class_under_test<br />&nbsp; &nbsp; &nbsp;cached = @attr_hash.clone</p><p>&nbsp; &nbsp; &nbsp;obj = klass.new<br />&nbsp; &nbsp; &nbsp;assert_respond_to obj, (&quot;build_&quot;+field.to_s)<br />&nbsp; &nbsp; &nbsp;field_id = (field.to_s + &quot;_id&quot;).to_sym</p><p>&nbsp; &nbsp; &nbsp;cached[field_id]=nil<br />&nbsp; &nbsp; &nbsp;obj = klass.new(cached)<br />&nbsp; &nbsp; &nbsp;assert_not_valid obj<br />&nbsp; &nbsp; &nbsp;assert_field_invalid obj, field_id<br />&nbsp; &nbsp;end<br />&nbsp; &nbsp;def assert_optional_association(field, opts)<br />&nbsp; &nbsp; &nbsp; klass = class_under_test<br />&nbsp; &nbsp; &nbsp; obj = klass.new<br />&nbsp; &nbsp; &nbsp; assert_respond_to obj, field<br />&nbsp; &nbsp; &nbsp; assert_respond_to obj.send(field), :find<br />&nbsp; &nbsp; &nbsp; assert_instance_of Array, obj.send(field)<br />&nbsp; &nbsp; end<br />&nbsp; &nbsp;</p><p>&nbsp; def assert_not_valid (obj)<br />&nbsp; &nbsp; assert_block(&quot;#{obj.to_s} should not be valid.&quot;) { !obj.valid?} <br />&nbsp; end<br />&nbsp; <br />&nbsp; def assert_required_length(field,allowed,opt={})<br />&nbsp; &nbsp; klass =class_under_test<br />&nbsp; &nbsp; cached = @attr_hash.clone</p><p>&nbsp; &nbsp; failing = [allowed.first-1,allowed.last+1]<br />&nbsp; &nbsp; passing = [allowed.first,allowed.last].uniq<br />&nbsp; &nbsp; for length in failing do<br />&nbsp; &nbsp; &nbsp; populate_field_with_fixed_length_string cached, field, length, opt<br />&nbsp; &nbsp; &nbsp; assert_not_valid klass.new(cached)<br />&nbsp; &nbsp; end<br />&nbsp; &nbsp; for length in passing do<br />&nbsp; &nbsp; &nbsp; populate_field_with_fixed_length_string cached, field, length, opt<br />&nbsp; &nbsp; &nbsp; assert_valid klass.new(cached)<br />&nbsp; &nbsp; end<br />&nbsp; end</p><p>&nbsp; def populate_field_with_fixed_length_string(cached, field, length, opt)<br />&nbsp; &nbsp; cached[field]=&#039;a&#039; * length<br />&nbsp; &nbsp; cached[opt[:confirmed]]=cached[field] if opt.has_key? :confirmed<br />&nbsp; end<br />&nbsp; <br />&nbsp; def get_reference_fixture<br />&nbsp; &nbsp; return class_under_test.find(1)<br />&nbsp; end<br />&nbsp; <br />&nbsp; def class_under_test<br />&nbsp; &nbsp; /([A-Z][A-Za-z_]*)Test/.match(self.class.to_s)<br />&nbsp; &nbsp; $1.constantize<br />&nbsp; end<br />&nbsp; <br />&nbsp; private<br />&nbsp; def assert_field_invalid(obj, field)<br />&nbsp; &nbsp; assert_block(&quot;#{obj.to_s}&#039;s #{field.to_s} should be invalid.&quot;) {obj.errors.invalid?(field)}<br />&nbsp; end<br />end</pre><br />Using them, at present requires one thing -- you have to define a @attr_hash in your test setup.&nbsp; This hash should have a parameter list that can potentially create a new, valid entity, but one that is NOT already in a fixture.&nbsp; Many of the tests take this known good parameter list and then tweak it to create a should fail case.&nbsp; Either by blanking out a field, or grabbing a duplicated value from an arbitrary fixture, etc</p><p>Please ignore the &quot;assert_[required|optional]_association&quot; assertions those are initial essays into the solution space, but are no where near solid enough yet.</p>]]></description>
			<author><![CDATA[dummy@example.com (NielsenE)]]></author>
			<pubDate>Sun, 18 Jun 2006 06:59:18 +0000</pubDate>
			<guid>http://railsforum.com/viewtopic.php?pid=401#p401</guid>
		</item>
		<item>
			<title><![CDATA[Re: Testing your model code...]]></title>
			<link>http://railsforum.com/viewtopic.php?pid=400#p400</link>
			<description><![CDATA[<p>I really like your custom assertions.&nbsp; For some reason I was under the impression that tests needn&#039;t be too DRY.&nbsp; I tend to repeat myself a lot in tests - maybe I should look into that.</p><p>For reference, could you post the code from your custom asserts?</p>]]></description>
			<author><![CDATA[dummy@example.com (danger)]]></author>
			<pubDate>Sun, 18 Jun 2006 06:48:20 +0000</pubDate>
			<guid>http://railsforum.com/viewtopic.php?pid=400#p400</guid>
		</item>
		<item>
			<title><![CDATA[Re: Testing your model code...]]></title>
			<link>http://railsforum.com/viewtopic.php?pid=399#p399</link>
			<description><![CDATA[<p>I&#039;ve been moving towards something like<br /><pre name="code" class="ruby:nogutter">def test_validations<br />&nbsp; assert_attribute_required :username, :length=&gt;(4..20), :unique=&gt;true<br />&nbsp; assert_attribute_required :password, :length=&gt;(4..20), :confirmed=&gt;:password_confirmation<br />&nbsp; assert_attribute_required :email<br />end</pre><br />These are all custom assertions that I&#039;m still working on cleaning up.&nbsp; The tests basically wrap up the method you just showed, with a few other options.&nbsp; Otherwise I feel that the unit test code gets very non-DRY.</p><p>I&#039;m still working on a similar set of assertions for associations.</p><p>[PS, any chance we can get a Ruby syntax highlighter for this forum so we can use ruby blocks and not just code blocks?]</p>]]></description>
			<author><![CDATA[dummy@example.com (NielsenE)]]></author>
			<pubDate>Sun, 18 Jun 2006 06:44:16 +0000</pubDate>
			<guid>http://railsforum.com/viewtopic.php?pid=399#p399</guid>
		</item>
		<item>
			<title><![CDATA[Re: Testing your model code...]]></title>
			<link>http://railsforum.com/viewtopic.php?pid=397#p397</link>
			<description><![CDATA[<p>I&#039;d be interested whether this is an accepted way of doing things, but I create unit tests liberally and I create at least one to check each validation.&nbsp; Here&#039;s a snippet from a page model I have that requires a filename:<br /><pre name="code" class="ruby:nogutter">&nbsp; def test_needs_filename<br />&nbsp; &nbsp; p = pages(:about)<br />&nbsp; &nbsp; p.filename = nil<br />&nbsp; &nbsp; assert_false p.save<br />&nbsp; &nbsp; assert_equal 1, p.errors.length<br />&nbsp; &nbsp; assert_equal &quot;can&#039;t be blank&quot;, p.errors.on(:filename)<br />&nbsp; end</pre><br />It&#039;s pretty simple and it works well.&nbsp; It tests that the model can&#039;t be saved, that there was one error, and that error was in the right place with the right content.&nbsp; What it doesn&#039;t do is relate to the db migrations like you mentioned.</p><p>I believe testing associations and built-in validations is anything but trivial - all my code gets tested.</p><p>I also think that in order to tie the migratory state of the db to tests (like you mentioned) we&#039;d have to either 1) always revert to a previous code version when testing against a previous migration or 2) require each test method to only execute if the current schema_info.version is above a certain level.</p><p>I haven&#039;t personally run into a situation where I needed agile data modeling beyond creating new migrations to handle changes.</p>]]></description>
			<author><![CDATA[dummy@example.com (danger)]]></author>
			<pubDate>Sun, 18 Jun 2006 06:28:04 +0000</pubDate>
			<guid>http://railsforum.com/viewtopic.php?pid=397#p397</guid>
		</item>
		<item>
			<title><![CDATA[Testing your model code...]]></title>
			<link>http://railsforum.com/viewtopic.php?pid=369#p369</link>
			<description><![CDATA[<p>I&#039;m a recent convert to RoR, but have been attempting to practice TDD for about a year now in various languages. I&#039;m finding it a little hard to properly test the models in RoR.&nbsp; I&#039;ve spent a lot of time developing some custom assertions to help drive thee development of validates_*, and the association links and these are getting closer to feeling acceptable to me.&nbsp; However I haven&#039;t found a good way to TDD the migration in the first place -- ie. if we assume that the database truly is an application database, then it should be viewed as part of the application code-base and the generation of a migration file should only follow a failing test case...&nbsp; Has anyone else explored possible ways of intelligently driving this aspect of model development?</p><p>How do you approach testing use of the built-in validations, and association link?&nbsp; (has_many, etc)&nbsp; Do you feel these fall under the category of &quot;trivial&quot; like a lot of accessors and don&#039;t bother testing them?&nbsp; Do you test them indirectly by normal exercise of the class, etc?</p><p>Note that this does assume not just the regular agile development practices, but agile data modeling as well...</p>]]></description>
			<author><![CDATA[dummy@example.com (NielsenE)]]></author>
			<pubDate>Sat, 17 Jun 2006 20:53:00 +0000</pubDate>
			<guid>http://railsforum.com/viewtopic.php?pid=369#p369</guid>
		</item>
	</channel>
</rss>
