Style Guide
Version 19 (Brian Ford, 03/30/2009 09:30 AM)
| 1 | 1 | h1. Style Guide |
|
|---|---|---|---|
| 2 | 1 | ||
| 3 | 2 | Brian Ford | Generally, "RSpec":http://rspec.rubyforge.org specs describe the expected behavior of code. While RSpec is fairly young, there are some conventions for writing specs. The RubySpecs cover a wide variety of components, so we have developed some pragmatic conventions to handle the various situations. As noted below, some conventions are more rigid than others. |
| 4 | 1 | ||
| 5 | 1 | These conventions apply to all specs. Existing specs that deviate from these conventions need to be fixed. Consistency is the principle that will almost always trump other conventions. Consistency aids understanding and readability. There are many thousands of lines of code in the spec files, so the value of consistency cannot be overstated. |
|
| 6 | 1 | ||
| 7 | 1 | The specs uniformly use @describe@ not @context@. The use of @it@ is preferred over @specify@ except in situations when the first word of the string is not a verb. The word "should" is unnecessary noise in the spec description strings and is not used. (The rationale is this: the spec string describes the expected behavior unconditionally. The code examples, on the other hand, set up an expectation that is tested with the call to the _should_ method. The code examples can violate the expectation, but the spec string does not. The value of the spec string is as clearly as possible describing the behavior. Including "should" in that description adds no value.) |
|
| 8 | 1 | ||
| 9 | 1 | Whenever possible, the spec strings should be written to conform to very basic English sentence structure: _subject + predicate_. The spec strings also uniformly use double-quotes, not single-quotes. The minimum number of words should be used to describe the behavior. Only make distinctions when they add significant value to understanding the behavior. This is explained further below. The general rule across all the specs is to use the least amount of detail to unambiguously describe behavior. Add to the detail conservatively. This is conceptually consistent with doing the simplest thing that could work. |
|
| 10 | 1 | ||
| 11 | 5 | Brian Ford | Ruby is a beautifully expressive language with _optional_ parentheses. There is a distinct preference for omitting parentheses in the specs whenever they are not needed. In other words, parentheses should _not_ be used unless necessary to make an expression syntactically or semantically correct. |
| 12 | 19 | Brian Ford | h2. Basic Principles |
| 13 | 1 | ||
| 14 | 19 | Brian Ford | h3. Error messages |
| 15 | 19 | Brian Ford | |
| 16 | 19 | Brian Ford | Do not check for specific error messages, just the exception type. Implementations should be free to enhance the error message, offer implementation-specific details, or even translate the error messages. The interface is the exception class raised, not the message that is provided for human consumption. |
| 17 | 19 | Brian Ford | |
| 18 | 19 | Brian Ford |
|
| 19 | 19 | Brian Ford | lambda { 1/0 }.should raise_error(ZeroDivisionError) |
| 20 | 19 | Brian Ford | |
| 21 | 19 | Brian Ford | # The following would not be preferred |
| 22 | 19 | Brian Ford | lambda { 1/0 }.should raise_error(ZeroDivisionError, "divided by 0") |
| 23 | 19 | Brian Ford | |
| 24 | 19 | Brian Ford | h3. One "should" per example |
| 25 | 19 | Brian Ford | |
| 26 | 19 | Brian Ford | This is a guiding principle, not a hard and fast rule. |
| 27 | 19 | Brian Ford | |
| 28 | 19 | Brian Ford | For expressing different aspects of a scenario, you can usually factor out the scenario into a helper method, and then have different examples using the helper method and asserting the specific different aspects. For example, setting up a blocked thread takes a lot of fixture code, and it would be tempting to check different aspects of a blocked thread in a single example. Instead, this principle can be honored by keeping the fixture code in method ThreadSpecs.status_of_blocked_thread in core/thread/fixtures/classes.rb, and with code like this in core/thread/status_spec.rb: |
| 29 | 19 | Brian Ford | |
| 30 | 19 | Brian Ford | |
| 31 | 19 | Brian Ford | describe "Thread#status" do |
| 32 | 19 | Brian Ford | it "describes a blocked thread" do |
| 33 | 19 | Brian Ford | ThreadSpecs.status_of_blocked_thread.status.should == 'sleep' |
| 34 | 19 | Brian Ford | end |
| 35 | 19 | Brian Ford | end |
| 36 | 19 | Brian Ford | |
| 37 | 19 | Brian Ford | |
| 38 | 19 | Brian Ford | and the following in core/thread/inspect_spec.rb: |
| 39 | 19 | Brian Ford | |
| 40 | 19 | Brian Ford | describe "Thread#inspect" do |
| 41 | 19 | Brian Ford | it "describes a blocked thread" do |
| 42 | 19 | Brian Ford | ThreadSpecs.status_of_blocked_thread.inspect.should include('sleep') |
| 43 | 19 | Brian Ford | end |
| 44 | 19 | Brian Ford | end |
| 45 | 19 | Brian Ford | |
| 46 | 19 | Brian Ford | |
| 47 | 19 | Brian Ford | Cases where it is OK to break this rule is when the functionality is expressable as a table for different values of the argument. For example, the following in language/regexp_spec.rb. Each "should" expresses the same theme, just different specific data-points. Breaking this up into individual examples would obscure the larger picture, ie. the "table". |
| 48 | 19 | Brian Ford | |
| 49 | 19 | Brian Ford | |
| 50 | 19 | Brian Ford | it 'supports escape characters' do |
| 51 | 19 | Brian Ford | /\t/.match("\t").to_a.should == ["\t"] # horizontal tab |
| 52 | 19 | Brian Ford | /\v/.match("\v").to_a.should == ["\v"] # vertical tab |
| 53 | 19 | Brian Ford | /\n/.match("\n").to_a.should == ["\n"] # newline |
| 54 | 19 | Brian Ford | /\r/.match("\r").to_a.should == ["\r"] # return |
| 55 | 19 | Brian Ford | /\f/.match("\f").to_a.should == ["\f"] # form feed |
| 56 | 19 | Brian Ford | /\a/.match("\a").to_a.should == ["\a"] # bell |
| 57 | 19 | Brian Ford | /\e/.match("\e").to_a.should == ["\e"] # escape |
| 58 | 19 | Brian Ford | end |
| 59 | 19 | Brian Ford | |
| 60 | 19 | Brian Ford | |
| 61 | 1 | h2. 1. Core and Standard Library |
|
| 62 | 1 | ||
| 63 | 1 | The specs for the Ruby core and standard libraries use one @describe@ block per method. For particularly complex methods, such as Array#[], more than one @describe@ block may be used according to the nature of arguments the method takes. |
|
| 64 | 1 | ||
| 65 | 1 | The @describe@ string should be "Constant.method" for class methods and "Constant#method" for instance methods. "Constant" is either a class or module name. For subclasses or submodules, the "Constant" name should be "Super::Sub". The @describe@ string should not include arguments to the methods unless absolutely necessary to describe the behavior of the method. Keep in mind that in Ruby duck-typing is a deeply embedded concept. Many methods will take any object that responds to a particular method or acts like an instance of a particular class. |
|
| 66 | 1 | ||
| 67 | 6 | Brian Ford | Nested @describe@ blocks should not be used. Various automated process scripts depend on the @describe@ string having the format explained above. Also, nested @describe@ blocks complicate the structure of the specs. If a particular situation appears to greatly benefit from nested blocks, open a discussion about it on the "mailing list":http://groups.google.com/group/rubyspec. |
| 68 | 6 | Brian Ford | |
| 69 | 1 | ||
| 70 | 1 | # This is correct |
|
| 71 | 1 | describe "String#eql?" do |
|
| 72 | 1 | it "returns true if other has the same length and content" do |
|
| 73 | 1 | ... |
|
| 74 | 1 | end |
|
| 75 | 1 | end |
|
| 76 | 1 | ||
| 77 | 1 | describe "Array#[]= with [index, count]" do |
|
| 78 | 1 | it "returns non-array value if non-array value assigned" do |
|
| 79 | 1 | ... |
|
| 80 | 1 | end |
|
| 81 | 1 | end |
|
| 82 | 3 | Brian Ford | |
| 83 | 1 | ||
| 84 | 4 | Brian Ford | Contrast the _good_ example above with the one below. The following example deviates from the conventions for @describe@ strings and uses "should" and single-quotes for the descriptions. |
| 85 | 4 | Brian Ford | |
| 86 | 3 | Brian Ford | |
| 87 | 1 | # This is NOT correct |
|
| 88 | 1 | describe "String#eql?(string)" do |
|
| 89 | 4 | Brian Ford | it 'should return true if other has the same length and content' do |
| 90 | 1 | ... |
|
| 91 | 1 | end |
|
| 92 | 1 | end |
|
| 93 | 1 | ||
| 94 | 4 | Brian Ford | describe 'Array#[]=(index, count)' do |
| 95 | 4 | Brian Ford | it 'returns non-array value if non-array value assigned' do |
| 96 | 1 | ... |
|
| 97 | 1 | end |
|
| 98 | 1 | end |
|
| 99 | 1 | ||
| 100 | 1 | ||
| 101 | 1 | The vast majority of the spec files for the core library have already been created. To create template files for the standard library classes, refer to the "mkspec":/wiki/mspec/Mkspec documentation. |
|
| 102 | 1 | ||
| 103 | 1 | h3. 1.1 Utility Classes |
|
| 104 | 1 | ||
| 105 | 1 | Many spec code examples refer to a particular class. To prevent name clashes with these different class definitions across all the specs, the classes should be scoped to a module. The convention is as follows: |
|
| 106 | 1 | ||
| 107 | 1 | ||
| 108 | 1 | module ObjectSpecs |
|
| 109 | 1 | class SomeClass |
|
| 110 | 1 | end |
|
| 111 | 1 | end |
|
| 112 | 1 | ||
| 113 | 1 | ||
| 114 | 1 | The module is named after the class for which the specs are being written. So, for the specs for _Object_, the module name is ObjectSpecs. |
|
| 115 | 1 | ||
| 116 | 1 | These utility classes are also referred to as _fixtures_. In the directory for each class, there is also a @fixtures@ directory. Refer to the existing files for examples. |
|
| 117 | 1 | ||
| 118 | 1 | h3. 1.2 Aliased or Identical Methods |
|
| 119 | 1 | ||
| 120 | 1 | Ruby has a significant number of aliased methods. True aliases are identical methods, so the specs should be exactly the same for each aliased method. The following illustrates the convention for specs for aliased methods (or just otherwise identical interfaces.) |
|
| 121 | 1 | ||
| 122 | 1 | In @rubyspec/1.8/core/array/shared/collect.rb@ |
|
| 123 | 1 | ||
| 124 | 1 | ||
| 125 | 14 | Brian Ford | describe :array_collect, :shared => true do |
| 126 | 14 | Brian Ford | it "returns a copy of array with each element replaced by the value returned by block" do |
| 127 | 14 | Brian Ford | a = ['a', 'b', 'c', 'd'] |
| 128 | 14 | Brian Ford | b = a.send(@method) { |i| i + '!' } |
| 129 | 14 | Brian Ford | b.should == ["a!", "b!", "c!", "d!"] |
| 130 | 14 | Brian Ford | b.object_id.should_not == a.object_id |
| 131 | 14 | Brian Ford | end |
| 132 | 1 | ||
| 133 | 14 | Brian Ford | it "does not return subclass instance" do |
| 134 | 14 | Brian Ford | ArraySpecs::MyArray[1, 2, 3].send(@method) { |x| x + 1 }.class.should == Array |
| 135 | 1 | end |
|
| 136 | 1 | end |
|
| 137 | 1 | ||
| 138 | 1 | ||
| 139 | 1 | In @rubyspec/1.8/core/array/collect_spec.rb@ |
|
| 140 | 1 | ||
| 141 | 1 | require File.dirname(__FILE__) + '/../../spec_helper' |
|
| 142 | 1 | require File.dirname(__FILE__) + '/shared/collect.rb' |
|
| 143 | 1 | ||
| 144 | 1 | describe "Array#collect" do |
|
| 145 | 5 | Brian Ford | it_behaves_like :array_collect, :collect |
| 146 | 1 | end |
|
| 147 | 1 | ||
| 148 | 1 | ||
| 149 | 1 | In @rubyspec/1.8/core/array/map_spec.rb@ |
|
| 150 | 1 | ||
| 151 | 1 | require File.dirname(__FILE__) + '/../../spec_helper' |
|
| 152 | 1 | require File.dirname(__FILE__) + '/shared/collect.rb' |
|
| 153 | 1 | ||
| 154 | 9 | Brian Ford | describe "Array#map" do |
| 155 | 11 | Brian Ford | it_behaves_like :array_collect, :map |
| 156 | 9 | Brian Ford | end |
| 157 | 15 | Shri Borde | |
| 158 | 15 | Shri Borde | |
| 159 | 15 | Shri Borde | h3. 1.3 Floating Point Values |
| 160 | 15 | Shri Borde | |
| 161 | 15 | Shri Borde | Writing specs that use floating point values poses a problem because two values that look the same when rendered to a string may not actually be bitwise equal. Also, floating point operations can result in a value that differs based on the way the FPU carried out the operations. |
| 162 | 15 | Shri Borde | |
| 163 | 15 | Shri Borde | Specs that compare floating point values should use @#should_be_close@ with the TOLERANCE constant. For floating point values that are exact, but larger than the precision formatted with #to_s (e.g. 1093840198347109283720.00), use the expanded float literal not the truncated precision format that #to_s provides (e.g. don't use 1.09384019834711e+21). |
| 164 | 15 | Shri Borde | |
| 165 | 15 | Shri Borde | h3. 1.4 Private methods |
| 166 | 16 | Shri Borde | |
| 167 | 16 | Shri Borde | Generally, no specs are written for private methods. A notable exeception are the specs for |
| 168 | 16 | Shri Borde | |
| 169 | 16 | Shri Borde | h3. 1.5 Ruby Ducktyping Interface |
| 170 | 18 | Shri Borde | |
| 171 | 16 | Shri Borde | Ruby method dispatch behavior calls |
| 172 | 16 | Shri Borde | |
| 173 | 16 | Shri Borde | The point of the RubySpecs is to describe behavior in such a way that if two different implementations pass a spec, Ruby code that relies on behavior described by the spec will execute with the same result on either implementation. |
| 174 | 16 | Shri Borde | |
| 175 | 16 | Shri Borde | If a spec asserts that a method calls |
| 176 | 16 | Shri Borde | |
| 177 | 16 | Shri Borde | It is conceivable that user code like the following exists: |
| 178 | 16 | Shri Borde | |
| 179 | 1 | ||
| 180 | 17 | Shri Borde | class Silly |
| 181 | 16 | Shri Borde | def method_missing(sym, *args) |
| 182 | 16 | Shri Borde | return 1 if sym == :to_int |
| 183 | 16 | Shri Borde | end |
| 184 | 16 | Shri Borde | end |
| 185 | 16 | Shri Borde | |
| 186 | 16 | Shri Borde | |
| 187 | 1 | In such case, the behavior of the following code would be different: |
|
| 188 | 16 | Shri Borde | |
| 189 | 18 | Shri Borde | |
| 190 | 16 | Shri Borde | # The implementation calls #to_int without checking #respond_to? |
| 191 | 16 | Shri Borde | [1, 2].at(silly) # => 2 |
| 192 | 16 | Shri Borde | |
| 193 | 16 | Shri Borde | # The implementation calls #respond_to? first |
| 194 | 16 | Shri Borde | [1, 2].at(silly) # => TypeError |
| 195 | 16 | Shri Borde | |
| 196 | 16 | Shri Borde | |
| 197 | 16 | Shri Borde | In the second case, the expected behavior is restored if the Silly class is modified to implement a |
| 198 | 16 | Shri Borde | |
| 199 | 16 | Shri Borde | The point is that it really is not sensible to implement an object that provides an interface but does not let the world know about it by either 1) defining the method properly, or 2) defining |
| 200 | 16 | Shri Borde | |
| 201 | 16 | Shri Borde | If real-world code exists that _depends_ on this silly implementation (i.e. cannot be coded in a more realistic way), then we can revisit the utility of specs that require |
| 202 | 16 | Shri Borde | |
| 203 | 1 | h2. 2. Language |
|
| 204 | 1 | ||
| 205 | 1 | For the language specs, there is nothing as convenient or as concrete as a particular method to spec. Review the discussion of the "organization":/wiki/rubyspec/Organization of the language specs. The general conventions apply here: use simple English to describe the behavior of the _language entities_ and only add detail as needed. Use a single @describe@ block initially and add distinguishing @describe@ blocks as necessary. Use @it@ rather than @specify@ whenever possible. |
