[TFUI] snippet - assert_stdout

"Phlip" <[email protected]> Sun, 13 Jan 2008 00:07:33 -0800
Newsgroups gmane.comp.programming.test-first-user-interfaces
Message-ID <006201c855bb$5b2b5170$0b0b0a0a@Marley>
--9MfiyR2Pv4IXnGzwxMlo0xZILo2G87o5vyZ6sYE
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: 7bit

TFUIers:

Ideally, when your developer tests call production code, it should not spew 
output to your STDOUT stream. If it must emit strings, it should return them 
to its caller. That way, your main() methods have the option to spew the 
strings, and your tests can instead intercept the strings and assert_match 
their contents.

Real life is often less than ideal. Your production code might couple with 
legacy code. Or your production code might require some out-of-band spew, 
such as a "syntax" or "usage" message.

Ruby makes this situation delightfully easy to test, by intercepting the 
STDOUT itself. Some languages would force you to manipulate the raw STDOUT 
file descriptor. Ruby simply permits you to replace your $stdout object with 
one that implements .write.

So, without further ado, here's the assertion to trap and match your STDOUT 
stream:

  class BufferStdout
    def write(stuff)
      (@output ||= '') << stuff
    end
    def output;  @output || ''  end
  end

  def assert_stdout(matcher = nil, diagnostic = nil)
    waz = $stdout
    $stdout = BufferStdout.new
    yield
    assert_match matcher, $stdout.output, diagnostic  if matcher
    return $stdout.output  # for if you need the whole string
  ensure
    $stdout = waz
  end

  def deny_stdout(unmatcher, diagnostic = nil, &block)
    got = assert_stdout(nil, nil, &block)
    assert_no_match unmatcher, got, diagnostic
  end

With further ado, here's the assertion in action:

  def test_help
    assert_stdout /invalid.*argument.*
                   verbose.*=\>.*:false/mx do
      assert_efficient_sql(:help){}
    end
  end

That test case demonstrates that another assertion, assert_efficient_sql, 
will spew a helpful error to $stdout if you call it with an invalid 
argument.

Modulo my typical life-issues, we will soon see more of that 
assert_invalid_sql. But it only works with MySQL.

If you use it with another database adapter, it should not fail; it should 
simply warn (spew) that it honestly cannot determine your SQL's efficiency.

To test that, we write a case which installs another adapter, then detects 
that the warning only appears once:

    assert_stdout /requires MySQL/, 'warn'  do  assert_efficient_sql  end
      deny_stdout /requires MySQL/, 'once'  do  assert_efficient_sql  end

That shows deny_stdout in action, and the 'diagnostic' parameters.

-- 
 Phlip
 http://www.oreilly.com/catalog/9780596510657/
 ^ assert_xpath 


--9MfiyR2Pv4IXnGzwxMlo0xZILo2G87o5vyZ6sYE
Content-Type: text/html; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
</head>




<body style="background-color: #ffffff;">

<!--~-|**|PrettyHtmlStartT|**|-~-->
<div id="ygrp-mlmsg" style="width:655px; position:relative;">
  <div id="ygrp-msg" style="width: 490px; padding: 0 15px 0 0; float:left;  z-index:1;">
<!--~-|**|PrettyHtmlEndT|**|-~-->

    <div id="ygrp-text">
            <p>TFUIers:<br>
<br>
Ideally, when your developer tests call production code, it should not spew <br>
output to your STDOUT stream. If it must emit strings, it should return them <br>
to its caller. That way, your main() methods have the option to spew the <br>
strings, and your tests can instead intercept the strings and assert_match <br>
their contents.<br>
<br>
Real life is often less than ideal. Your production code might couple with <br>
legacy code. Or your production code might require some out-of-band spew, <br>
such as a &quot;syntax&quot; or &quot;usage&quot; message.<br>
<br>
Ruby makes this situation delightfully easy to test, by intercepting the <br>
STDOUT itself. Some languages would force you to manipulate the raw STDOUT <br>
file descriptor. Ruby simply permits you to replace your $stdout object with <br>
one that implements .write.<br>
<br>
So, without further ado, here's the assertion to trap and match your STDOUT <br>
stream:<br>
<br>
class BufferStdout<br>
    def write(stuff)<br>
      (@output ||= '') &lt;&lt; stuff<br>
    end<br>
    def output;  @output || ''  end<br>
  end<br>
<br>
def assert_stdout(<wbr>matcher = nil, diagnostic = nil)<br>
    waz = $stdout<br>
    $stdout = BufferStdout.<wbr>new<br>
    yield<br>
    assert_match matcher, $stdout.output, diagnostic  if matcher<br>
    return $stdout.output  # for if you need the whole string<br>
  ensure<br>
    $stdout = waz<br>
  end<br>
<br>
def deny_stdout(<wbr>unmatcher, diagnostic = nil, &amp;block)<br>
    got = assert_stdout(<wbr>nil, nil, &amp;block)<br>
    assert_no_match unmatcher, got, diagnostic<br>
  end<br>
<br>
With further ado, here's the assertion in action:<br>
<br>
def test_help<br>
    assert_stdout /invalid.*argument.<wbr>*<br>
                   verbose.*=\&gt;<wbr>.*:false/<wbr>mx do<br>
      assert_efficient_<wbr>sql(:help)<wbr>{}<br>
    end<br>
  end<br>
<br>
That test case demonstrates that another assertion, assert_efficient_<wbr>sql, <br>
will spew a helpful error to $stdout if you call it with an invalid <br>
argument.<br>
<br>
Modulo my typical life-issues, we will soon see more of that <br>
assert_invalid_<wbr>sql. But it only works with MySQL.<br>
<br>
If you use it with another database adapter, it should not fail; it should <br>
simply warn (spew) that it honestly cannot determine your SQL's efficiency.<br>
<br>
To test that, we write a case which installs another adapter, then detects <br>
that the warning only appears once:<br>
<br>
assert_stdout /requires MySQL/, 'warn'  do  assert_efficient_<wbr>sql  end<br>
      deny_stdout /requires MySQL/, 'once'  do  assert_efficient_<wbr>sql  end<br>
<br>
That shows deny_stdout in action, and the 'diagnostic' parameters.<br>
<br>
-- <br>
 Phlip<br>
 <a href="http://www.oreilly.com/catalog/9780596510657/">http://www.oreilly.<wbr>com/catalog/<wbr>9780596510657/</a><br>
 ^ assert_xpath <br>
<br>
</p>
    </div>  

    <!--~-|**|PrettyHtmlStart|**|-~-->
    <span width="1" style="color: white;">__._,_.___</span>
    <!-- Start the section with Message In topic -->
    <div id="ygrp-actbar">
              <span class="left">
          <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces/message/1007;_ylc=X3oDMTM0dmU3aDhtBF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BG1zZ0lkAzEwMDcEc2VjA2Z0cgRzbGsDdnRwYwRzdGltZQMxMjAwMjExNjYwBHRwY0lkAzEwMDc-">
            Messages in this topic          </a> (<span class="bld">1</span>)
        </span>
        <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces/post;_ylc=X3oDMTJwYmY3OTBuBF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BG1zZ0lkAzEwMDcEc2VjA2Z0cgRzbGsDcnBseQRzdGltZQMxMjAwMjExNjYw?act=reply&messageNum=1007">
          <span class="bld">
            Reply          </span> (via web post)
        </a>  | 
        <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces/post;_ylc=X3oDMTJlbG9pcTFpBF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BHNlYwNmdHIEc2xrA250cGMEc3RpbWUDMTIwMDIxMTY2MA--" class="bld">
          Start a new topic        </a>
          </div> 
    <!-------     Start Nav Bar  ------>
    <!-- |**|begin egp html banner|**| -->
    <div id="ygrp-vitnav">
                <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces/messages;_ylc=X3oDMTJlZjg2Y3IzBF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BHNlYwNmdHIEc2xrA21zZ3MEc3RpbWUDMTIwMDIxMTY2MA--">Messages</a>  
            |    <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces/files;_ylc=X3oDMTJmNGFhbnYwBF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BHNlYwNmdHIEc2xrA2ZpbGVzBHN0aW1lAzEyMDAyMTE2NjA-">Files</a>  
            |    <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces/photos;_ylc=X3oDMTJlcjNyODNxBF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BHNlYwNmdHIEc2xrA3Bob3QEc3RpbWUDMTIwMDIxMTY2MA--">Photos</a>  
            |    <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces/links;_ylc=X3oDMTJmMGtsN29tBF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BHNlYwNmdHIEc2xrA2xpbmtzBHN0aW1lAzEyMDAyMTE2NjA-">Links</a>  
            |    <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces/database;_ylc=X3oDMTJjNWltYjQ5BF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BHNlYwNmdHIEc2xrA2RiBHN0aW1lAzEyMDAyMTE2NjA-">Database</a>  
            |    <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces/polls;_ylc=X3oDMTJmdnAxZHQwBF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BHNlYwNmdHIEc2xrA3BvbGxzBHN0aW1lAzEyMDAyMTE2NjA-">Polls</a>  
            |    <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces/members;_ylc=X3oDMTJlOXY1OHUwBF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BHNlYwNmdHIEc2xrA21icnMEc3RpbWUDMTIwMDIxMTY2MA--">Members</a>  
            |    <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces/calendar;_ylc=X3oDMTJkYzM4cGJxBF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BHNlYwNmdHIEc2xrA2NhbARzdGltZQMxMjAwMjExNjYw">Calendar</a>  
    </div>  
    <!-- |**|end egp html banner|**| -->

                        <div id="ygrp-grft">
          

        </div>
      
                  <div id="ygrp-mkp">
      <div id="hd">MARKETPLACE</div>
      <div id="ads">
                  <div class="ad">
            <hr size=1 noshade><a href="http://us.ard.yahoo.com/SIG=12m8c56bc/M=571476.12066680.12490312.11509771/D=groups/S=1705007207:MKP1/Y=YAHOO/EXP=1200218860/A=5086951/R=0/SIG=12k8pu1n3/*http://college-finder.net/index.cfm?key=yahoo_colfngrouptxt_12066680&c=CA152932587">Earn your degree in as few as 2 years - Advance your career with an AS, BS, MS degree</a> - College-Finder.net.          </div>
                              </div>
    </div>
      
    <!-- yahoo logo -->
    <!-- |**|begin egp html banner|**| -->
    <div id="ygrp-ft">
      <a href="http://groups.yahoo.com/;_ylc=X3oDMTJkZGVqY2h0BF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BHNlYwNmdHIEc2xrA2dmcARzdGltZQMxMjAwMjExNjYw">
      <img src="http://us.i1.yimg.com/us.yimg.com/i/yg/img/logo/ma_grp_160.gif" height="15" width="106" border="0" alt="Yahoo! Groups"></a> <br>
      <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces/join;_ylc=X3oDMTJmZjZoZTB1BF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BHNlYwNmdHIEc2xrA3N0bmdzBHN0aW1lAzEyMDAyMTE2NjA-">Change settings via the Web</a> (Yahoo! ID required) <br>
      Change settings via email: <a href="mailto:TestFirstUserInterfaces-digest-hHKSG33TihhbjbujkaE4pw@public.gmane.org?subject=Email Delivery: Digest">Switch delivery to Daily Digest</a> | <a href = "mailto:TestFirstUserInterfaces-traditional-hHKSG33TihhbjbujkaE4pw@public.gmane.org?subject=Change Delivery Format: Traditional">Switch format to Traditional</a> <br>

      <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces;_ylc=X3oDMTJkOTZ2cXFsBF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BHNlYwNmdHIEc2xrA2hwZgRzdGltZQMxMjAwMjExNjYw">
        Visit Your Group 
      </a> |
      <a href="http://docs.yahoo.com/info/terms/">
        Yahoo! Groups Terms of Use      </a> |
      <a href="mailto:TestFirstUserInterfaces-unsubscribe-hHKSG33TihhbjbujkaE4pw@public.gmane.org?subject=">
        Unsubscribe      </a> 
    </div>     <!-- |**|end egp html banner|**| -->
  </div> <!-- ygrp-msg -->

  
  <!-- Sponsor -->
  <!-- |**|begin egp html banner|**| -->
  <div id="ygrp-sponsor" style="width:140px;float: left; clear: none; margin-left: 5px; background:white; margin-bottom:25px ;position:absolute; top:0; right: 0;">
    <!-- Network content -->
    
    <!-- Start vitality -->
    <div id="ygrp-vital">
            <a href="http://groups.yahoo.com/group/TestFirstUserInterfaces;_ylc=X3oDMTJlcGlmYWJzBF9TAzk3MzU5NzE0BGdycElkAzg3NDQ3MDQEZ3Jwc3BJZAMxNzA1MDA3MjA3BHNlYwN2dGwEc2xrA3ZnaHAEc3RpbWUDMTIwMDIxMTY2MA--">
        Visit Your Group      </a>
    </div> 
              
    <!-- Network content -->
              <div id="nc">
              <div class="ad">
                      <div id="hd1">Yahoo! Finance</div> 
<p><a href="http://us.ard.yahoo.com/SIG=12jdp3c56/M=493064.12016257.12445664.8674578/D=groups/S=1705007207:NC/Y=YAHOO/EXP=1200218860/A=4507179/R=0/SIG=12de4rskk/*http://us.rd.yahoo.com/evt=50284/*http://finance.yahoo.com/personal-finance">It's Now Personal</a></p> 
<p>Guides, news,</p> 
<p>advice & more.</p>                   </div>
                    <div class="ad">
                      <div id="hd1">New business?</div> 
<p><a href="http://us.ard.yahoo.com/SIG=12jp4bkh1/M=493064.12016308.12445700.8674578/D=groups/S=1705007207:NC/Y=YAHOO/EXP=1200218860/A=3848640/R=0/SIG=131an6mds/*http://searchmarketing.yahoo.com/arp/srchv2.php?o=US2002&cmp=Yahoo&ctv=Groups1&s=Y&s2=&s3=&b=50">Get new customers.</a></p> 
<p>List your web site</p> 
<p>in Yahoo! Search.</p>                  </div>
                    <div class="ad">
                      <div id="hd1">Improvement Zone</div> 
<p><a href="http://us.ard.yahoo.com/SIG=12jhs53e9/M=493064.12117552.12537389.8674578/D=groups/S=1705007207:NC/Y=YAHOO/EXP=1200218860/A=5170401/R=0/SIG=11ml7n8m5/*http://advision.webevents.yahoo.com/selfimprovement/">on Yahoo! Groups</a></p> 
<p>Find groups about</p> 
<p>New Year's goals.</p>                  </div>
          </div>
    
  </div>   <!-- |**|end egp html banner|**| -->
  <div style="clear:both; color: #FFF; font-size:1px;">.</div>
</div>   <img src="http://geo.yahoo.com/serv?s=97359714/grpId=8744704/grpspId=1705007207/msgId=1007/stime=1200211660/nc1=4507179/nc2=3848640/nc3=5170401" width="1" height="1"> <br>

<span  style="color: white;">__,_._,___</span>
<!--~-|**|PrettyHtmlEnd|**|-~-->
</body>
<!--~-|**|PrettyHtmlStart|**|-~-->
<head>
<style type="text/css">
<!--
#ygrp-mkp{
  border: 1px solid #d8d8d8;
  font-family: Arial;
  margin: 14px 0px;
  padding: 0px 14px;
}
#ygrp-mkp hr{
  border: 1px solid #d8d8d8;
}
#ygrp-mkp #hd{
  color: #628c2a;
  font-size: 85%;
  font-weight: bold;
  line-height: 122%;
  margin: 10px 0px;
}
#ygrp-mkp #ads{
  margin-bottom: 10px;
}
#ygrp-mkp .ad{
  padding: 0 0;
}
#ygrp-mkp .ad a{
  color: #0000ff;
  text-decoration: none;
}
-->
</style>
</head>
<head>
<style type="text/css">
<!--
#ygrp-sponsor #ygrp-lc{
  font-family: Arial;
}
#ygrp-sponsor #ygrp-lc #hd{
  margin: 10px 0px;
  font-weight: bold;
  font-size: 78%;
  line-height: 122%;
}
#ygrp-sponsor #ygrp-lc .ad{
  margin-bottom: 10px;
  padding: 0 0;
}
-->
</style>
</head>
<head>
<style type="text/css">
<!--
#ygrp-mlmsg {font-size:13px; font-family: arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}
#ygrp-mlmsg table {font-size:inherit;font:100%;}
#ygrp-mlmsg select, input, textarea {font:99% arial,helvetica,clean,sans-serif;}
#ygrp-mlmsg pre, code {font:115% monospace;*font-size:100%;}
#ygrp-mlmsg * {line-height:1.22em;}
#ygrp-text{
    font-family: Georgia;	
}
#ygrp-text p{
    margin: 0 0 1em 0;
}
#ygrp-tpmsgs{
    font-family: Arial;	
    clear: both;
}
#ygrp-vitnav{
	padding-top: 10px;
	font-family: Verdana;
	font-size: 77%;
	margin: 0;
}
#ygrp-vitnav a{
	padding: 0 1px;
}
#ygrp-actbar{
	clear: both;
	margin: 25px 0;
	white-space:nowrap;
	color: #666;
	text-align: right;
}
#ygrp-actbar .left{
	float: left;
	white-space:nowrap;
}
.bld{font-weight:bold;}
#ygrp-grft{
	font-family: Verdana;
	font-size: 77%;
	padding: 15px 0;
}
#ygrp-ft{
  font-family: verdana;
  font-size: 77%;
  border-top: 1px solid #666; 
  padding: 5px 0; 
}
#ygrp-mlmsg #logo{
  padding-bottom: 10px;
}

#ygrp-vital{
	background-color: #e0ecee;
	margin-bottom: 20px;
	padding: 2px 0 8px 8px;
}
#ygrp-vital #vithd{
	font-size: 77%;
	font-family: Verdana;
	font-weight: bold;
	color: #333;
	text-transform: uppercase;
}
#ygrp-vital ul{
	padding: 0;
	margin: 2px 0;
}
#ygrp-vital ul li{
  list-style-type: none;
  clear: both;
  border: 1px solid #e0ecee;  
}
#ygrp-vital ul li .ct{
  font-weight: bold;
  color: #ff7900;
  float: right;
  width: 2em;
  text-align:right;
  padding-right: .5em;
}
#ygrp-vital ul li .cat{
  font-weight: bold;
}
#ygrp-vital a{
	text-decoration: none;
}

#ygrp-vital a:hover{
  text-decoration: underline;
}

#ygrp-sponsor #hd{
	color: #999;
	font-size: 77%;
}
#ygrp-sponsor #ov{
	padding: 6px 13px;
	background-color: #e0ecee;
	margin-bottom: 20px;
}
#ygrp-sponsor #ov ul{
	padding: 0 0 0 8px;
	margin: 0;
}
#ygrp-sponsor #ov li{
	list-style-type: square;
	padding: 6px 0;
	font-size: 77%;
}
#ygrp-sponsor #ov li a{
	text-decoration: none;
	font-size: 130%;
}
#ygrp-sponsor #nc{
  background-color: #eee;
  margin-bottom: 20px;
  padding: 0 8px;
}
#ygrp-sponsor .ad{
	padding: 8px 0;
}
#ygrp-sponsor .ad #hd1{
	font-family: Arial;
	font-weight: bold;
	color: #628c2a;
	font-size: 100%;
	line-height: 122%;
}
#ygrp-sponsor .ad a{
	text-decoration: none;
}
#ygrp-sponsor .ad a:hover{
	text-decoration: underline;
}
#ygrp-sponsor .ad p{
	margin: 0;
}
o{font-size: 0; }
.MsoNormal{
   margin: 0 0 0 0;
}
#ygrp-text tt{
  font-size: 120%;
}
blockquote{margin: 0 0 0 4px;}
.replbq{margin:4}
-->
</style>
</head>
<!--~-|**|PrettyHtmlEnd|**|-~-->
</html><!--End group email -->


--9MfiyR2Pv4IXnGzwxMlo0xZILo2G87o5vyZ6sYE--