Style Guide

Version 4 (Brian Ford, 05/13/2008 10:20 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 1
12 1
h2. 1. Core and Standard Library
13 1
14 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.
15 1
16 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.
17 1
18 1
19 1
  # This is correct
20 1
  describe "String#eql?" do
21 1
    it "returns true if other has the same length and content" do
22 1
      ...
23 1
    end
24 1
  end
25 1
26 1
  describe "Array#[]= with [index, count]" do
27 1
    it "returns non-array value if non-array value assigned" do
28 1
      ...
29 1
    end
30 1
  end
31 3 Brian Ford
32 1
33 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.
34 4 Brian Ford
35 3 Brian Ford
36 1
  # This is NOT correct
37 1
  describe "String#eql?(string)" do
38 4 Brian Ford
    it 'should return true if other has the same length and content' do
39 1
      ...
40 1
    end
41 1
  end
42 1
43 4 Brian Ford
  describe 'Array#[]=(index, count)' do
44 4 Brian Ford
    it 'returns non-array value if non-array value assigned' do
45 1
      ...
46 1
    end
47 1
  end
48 1
49 1
50 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.
51 1
52 1
h3. 1.1 Utility Classes
53 1
54 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:
55 1
56 1
57 1
module ObjectSpecs
58 1
  class SomeClass
59 1
  end
60 1
end
61 1
62 1
63 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.
64 1
65 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.
66 1
67 1
h3. 1.2 Aliased or Identical Methods
68 1
69 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.)
70 1
71 1
In @rubyspec/1.8/core/array/shared/collect.rb@
72 1
73 1
74 1
shared :array_collect do |cmd|
75 1
  describe "Array##{cmd}" do
76 1
    it "returns a copy of array with each element replaced by the value returned by block" do
77 1
      a = ['a', 'b', 'c', 'd']
78 1
      b = a.send(cmd) { |i| i + '!' }
79 1
      b.should == ["a!", "b!", "c!", "d!"]
80 1
    end
81 1
82 1
    it "does not return subclass instances" do
83 1
      MyArray[1, 2, 3].send(cmd) { |x| x + 1 }.class.should == Array
84 1
    end
85 1
  end
86 1
end
87 1
88 1
89 1
In @rubyspec/1.8/core/array/collect_spec.rb@
90 1
91 1
require File.dirname(__FILE__) + '/../../spec_helper'
92 1
require File.dirname(__FILE__) + '/shared/collect.rb'
93 1
94 1
describe "Array#collect" do
95 1
  it_behaves_like(:array_collect, :collect)
96 1
end
97 1
98 1
99 1
In @rubyspec/1.8/core/array/map_spec.rb@
100 1
101 1
require File.dirname(__FILE__) + '/../../spec_helper'
102 1
require File.dirname(__FILE__) + '/shared/collect.rb'
103 1
104 1
describe "Array#map" do
105 1
  it_behaves_like(:array_collect, :map)
106 1
end
107 1
108 1
109 1
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. Specs that compare floating point values should use @#should_be_close@ with the TOLERANCE constant.
110 1
111 1
h2. 2. Language
112 1
113 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.