Quarry Test/Specifation
Standard Sections
Indented sections of a specification are always executable code. Each code section is executed in order within a wrapper that captures any failures or errors. If neither of these occur, the code "passes".
For example, the following passes:
(2 + 2).assert == 4
While this would have failed (failures are signaled by raising an Assertion error):
expect Assertion do
(2 + 2).assert == 5
end
And this would have raised a NameError:
expect NameError do
nobody_knows_method
end
Macros and Neutral Code
Macros, and any other non-testable code, can be designated using the *MACRO:* indicator. Because the context in which a specification is run is a self-extended module, reusable macros can be created simply by defining a method.
MACRO: Macro’s contain code to executed but not tested.
def assert_integer(x)
x.assert.is_a? Integer
end
Okay. Now lets try out our new macro.
assert_integer(4)
Let’s prove that it can also fail:
expect Assertion do
assert_integer("4")
end
Before and After Macros
Quarry supports before and after macros in a specification through the use of *BEFORE:* and *AFTER:* indicators. Before and after clauses are executed at the beginning and at the end of each test step.
BEFORE: We use a before clause if we want to setup some code at the start of each step.
@before = "BEFORE"
AFTER: And an after clause to teardown objects after a step.
@after = "AFTER"
Let verify this is the case.
@before.should == "BEFORE" @after.should == nil
And now.
@after.should == "AFTER"
There can only be one before or after clause at a time. So if we define a new BEFORE: or AFTER: section later in the specification, it will replace the current clause in use.
BEFORE: As an demonstration of this:
@before = "BEFORE AGAIN"
We will see it is the case.
@before.should == "BEFORE AGAIN"
Only use before and after claues when necessary —specifications are generally more readible without them. Indeed, some developers make a policy of avoiding them altogether. YMMV.
Tabular Steps
Finally we will demonstrate a tabular step. A ‘TABLE:’ indicator is used for this. We also supply a file name in parenthesis telling Quarry where to find the data table to be used in the test. All table files are looked for in a tables/ directory alond side the test/specification file. If no name is given ‘default.yaml’ is assumed.
The first row in a table defined the variable names to assign the values given in the subsequent rows. Each row is assigned in turn and run through the coded step. Consider the follwing example:
TABLE:(default.yaml) Evvery row in ‘tables/default.yaml’ will be assigned to the header names as variables and run through the following assertion.
x.upcase.assert == y
This concludes are basic specification of Quarry’s specification system. Yes, we eat our own dog food.
Quarry’s Stubbing Facility
Require stub.rb library.
require 'quarry/stub'
Stubs via Delegation
Delegation provides the most robust form of delegation. In this example we will stub-out a simple string.
@obj = "hello"
We can create a reusable stub module by instantiating a new Stub.
@stb = Quarry::Stub.new @stb.upcase == "HeLLo"
Now we apply the stub module to the object we want to stub.
@alt = @obj.stub(@stb)
And get the newly stubbed object that delegates to the original.
@alt.upcase.assert == "HeLLo"
And as you can see, the original is still intact.
@obj.upcase.assert == "HELLO"
Reusing Stubs via Object Extension
Stubs are modules, so they can also be used via #extend. For example a new string:
@obj = "hi"
Can be extended with the stub we used in the previous section.
@obj.extend(@stb) @obj.upcase.assert == "HeLLo"
We can change the stub dynamically too.
@stb.upcase == "hI" @obj.upcase.assert == "hI"
And remove it if we need the object to return to it’s original behavior.
@obj.remove_stub(@stb) @obj.upcase.assert == "HI"
Quick Stubs
Each object is also given one special built-in stub, accessible via its #stub method.
@obj = "hey" @obj.stub.upcase == "HeY" @obj.upcase.assert == "HeY"
Under the hood, the effect is the same as +obj.extend(obj.stub)+. We can remove this special stub via #remove_stub by leaving out the stub argument.
@obj.remove_stub
This imples +obj.remove_stub(obj.stub)+.
@obj.upcase.assert == "HEY"
And as you can see, we are back to the normal String behaivor.
Quarry’s Mocking Facility
Quarry’s mocks are not like mocks in other frameworks. Traditional mocks are too closely knit to underlying implementation. Quarry mocks are instead light-weight pre-assertion containers.
Require mock.rb library.
require 'quarry/mock'
Mocks via Delegation
As with stubs, delegation provides the most robust means for implemention mocks. In this example we will mock-out a simple string.
@obj = "hello"
We can create a reusable mock module by instantiating a new Mock.
@mck = Quarry::Mock.new @mck.upcase == "HeLLo"
Now we apply the mock module to the target object and we get a new mocked object, which delegates to the original.
@alt = @obj.mock(@mck)
When to invoke the target method, it will apply our assertion about the method, testing if the original object indeed produces the mocked result. In this case, the method #upcase produces "HELLO" and not "HeLLo" (ie, "HeLLo" != "HELLO"), so an Assertion exception is raised.
expect(Assertion){ @alt.upcase }
You can see that the original object is in no ways affected by the mock.
@obj.upcase.assert == "HELLO"
Reusing Mocks via Object Extension
Macks are modules, so they can also be reused via #extend. For example a new string:
@obj = "hi"
Can be mocked with the Mock object we used in the previous section.
@obj.extend(@mck)
An Assertion error will be raised again because "hi".upcase != "HeLLo".
expect(Assertion){ @obj.upcase }
We can change the mock on the fly.
@mck.upcase == "HI"
Now we can call the #upcase method and nothing will be raised, since "hi".upcase == "HI".
@obj.upcase
The mock can be removed from the object using #remove_mock.
@mck.upcase == "GO BOOM" @obj.remove_mock(@mck) @obj.upcase
Quick Mocks
Each object is also given one special built-in mock, accessible via its #mock method.
@obj = "hey"
@obj.mock.upcase == "HeY"
expect(Assertion){ @obj.upcase }
Under the hood, the effect is the same as +obj.extend(obj.mock)+. We can remove this special mock via #remove_mock by leaving out the argument.
@obj.remove_mock @obj.upcase.assert == "HEY"
This imples +obj.remove_stub(obj.mock)+. And as you can see, we are back to the normal String behaivor.