One of the most valuable benefits of tests is that they give you confidence that your code works as you expect it to work. Tests give you the confidence to do long-term development because with tests in place, you know that your foundation code is dependable. Tests give you the confidence to refactor your code to make it cleaner and more efficient.
Tests also save you time because tests help prevent regressions from being introduced and released. Once a bug is found, you can write a test for it, you can fix the bug, and the bug can never make it to production again because the tests will catch it in the future.
Another advantage is that tests provide excellent implicit documentation because they show exactly how the code is designed to be used.
Let’s take a look at how tests are best structured. All tests should follow the same basic structure.
Typically, methods perform some sort of operation upon data. So in order to test your methods, you’ll need to set up the data required by the method. This might be as simple as declaring a few variables, or as complex as creating a number of records in database.
Your tests should always create their own test data to execute against. That way, you can be confident that your tests aren’t dependent upon the state of a particular environment and will be repeatable even if they are executed in a different environment from which they were written.
If you find that many of your tests require very similar setup code, be sure to properly decompose the setup code so that you don’t repeat yourself.
Once you have set up the appropriate input data, you still need to execute your code. If you are testing a method, then you will call the method directly.
Verifying that your code works as you expect it to work is the most important part of testing. Tests that do not verify the results of the code aren’t true tests. They are commonly referred to as smoke tests, which aren’t nearly as effective or informative as true tests.
Environment always should be cleaned after a test running. That way, you can be confident that your next tests are not dependent upon the state of previous tests executing.
Test Driven Development (TDD) is not about writing tests. TDD is more than that, it’s a methodology. The main idea of TDD is to write tests before code.
This means that you have to have a failing test first. You can’t write any production code before ‘red’. Why? Because you have to know this test could fail in some circumstances and you have to know which change makes it pass.
Write code that is only needed to make the test pass. Now, try to run the test again. WOW, passed! Do you think this is a bad solution? Doesn’t work fine? Sure it works fine, because the test passed. There is YAGNI principal (YAGNI stands for You Ain’t Gonna Need It) which says ‘don’t write more than you need at this moment’. If you are sure you need more, write test for it and then implement this functionality.
Look at your code. Do you like it? Do you want to eat it? Do you want to f... it? If your answer to any of these questions was ‘no’, you should do something about that. Refactoring is changing code without changing its functionality.
Whole ‘red, green, refactor’ thing is about iteration, little programming cycles and fast feedback. When we write failing test we say ‘hey, my app should do that!’ Then we make it come true as fast as we can. It’s like in this game where you have to pass the ball to the next player before it ‘burns’ you. When you make a test pass, then you can relax and do refactoring. Change implementation, introduce design pattern and extract class or whatever you want. You have confidence that your code works all the time and that you didn’t break anything. This is the smallest programing cycle; this is exactly what TDD is about.
Specs in specific folder
Specific line in spec
We use the describe() method to define an example group.
The describe() method takes an arbitrary number of arguments and an optional block, and returns a subclass of RSpec::Core::ExampleGroup.
The context() method is an alias for describe().
The it() method takes a single String, an optional Hash and an optional block.
String with 'it' represents the detail that will be expressed in code within the block.spec/array_spec.rb
Use before and after hooks to execute arbitrary code before and/or after the body of an example is run
Run before each examplespec/array_spec.rb
Run one time only, before all of the examples in a groupspec/array_spec.rb
Use let to define a memoized helper method. The value will be cached across multiple calls in the same example but not across examples.
Note that let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked. You can use let! to force the method's invocation before each example.
Use subject in the group scope to explicitly define the value that is returned by the subject method in the example scope.spec/array_spec.rb
If the first argument to the outermost example group is a class, the class is exposed to each example via the described_class() method.
For checking the expectations, expect(obj).to and expect(obj).not_to methods are used.
The eq methods are used to express values equivalence, and equal are used when you want the receiver and the argument to be the same object.
Spec::Expectations adds two methods to Object:
Both methods take an optional Expression Matcher.
When should receives an Expression Matcher, it calls matches?(self). If it returns true, the spec passes and execution continues. If it returns false, then the spec fails with the message returned by matcher.failure_message.
Similarly, when should_not receives a matcher, it calls matches?(self). If it returns false, the spec passes and execution continues. If it returns true, then the spec fails with the message returned by matcher.negative_failure_message.
RSpec ships with a number of useful Expression Matchers. An Expression Matcher is any object that responds to the following methods:
Passes if given and expected are of equal value, but not necessarily the same object.
Passes if given and expected are the same object (object identity).
Passes if given == expected +/- delta
When dealing with floating points, it's convenient to use matcher be_close(), which takes two arguments: the floating point number you are expecting and the precision you require.
For checking strings to match regular expressions, the match is used. This can be very useful when dealing with multiple-line expectations.
A Ruby predicate method is a method that ends with a “?” and returns a boolean value, like string.empty? or regexp.match? methods. For these cases Rspec allows us to describe expectations with be_something matcher. When using a be_something matcher, RSpec removes the “be_”, appends a “?” and calls the resulting method in the receiver.
Alternately, for a predicate method that begins with "has" like Hash#has_key?, RSpec allows you to use an alternate form since "be_has_key" makes no sense.
Passes if given includes expected. This works for collections and Strings. You can also pass in multiple args and it will only pass if all args are found in collection.
Passes if given.exist?
Sometimens you expect some code (wrapped in a proc) to change the state of some object. There is a convenient way to check it with rspec:
RSpec provides several matchers that make it easy to set expectations about the size of a collection. There are three basic forms:
These work on any collection-like object-the object just needs to respond to #size or #length (or both). When the matcher is called directly on a collection object, the #items call is pure syntactic sugar. You can use anything you want here. These are equivalent:
You can also use this matcher on a non-collection object that returns a collection from one of its methods. For example, Dir#entries returns an array, so you could set an expectation using the following:
When you find that none of the stock Expectation Matchers provide a natural feeling expectation, you can very easily write your own using RSpec’s matcher DSL or writing one from scratch.
Imagine that you are writing a game in which players can be in various zones on a virtual board. To specify that bob should be in zone 4, you could say:
But you might find it more expressive to say:
You can create such a matcher like so:
Also you can override the failure messages and the generated description:
You can also create matchers that obey a fluent interface using the chain method:
And now it can be used as follows:
A test double is an object that stands in for another object in an example.
The argument is a name, used for failure reporting, so you should use the role that the double is playing in the example.
Use the as_null_object method to ignore any messages that aren't explicitly set as stubs or message expectations.
A method stub is a method that we can program to return predefined responses during the execution of a code example.
Method stubs can return different values on different calls: this will return 5.5% when a message for interest_rate is received for the first time, but will return 3% for subsequent calls/messages.
The mock object can be created with stubbed methods at once.
Stub methods can return values depending on the arguments.
Besides returning a value, method stub can yield a block, raise an exception, or throw the message.
Removes a stub. On a double, the object will no longer respond to message. On a real object, the original method (if it exists) is restored.
Support is provided for stubbing constants. Stubbed constant names must be fully qualified; the current module nesting is not considered.
Use any_instance.stub on a class to tell any instance of that class to return a value (or values) in response to a given message. If no instance receives the message, nothing happens.
Messages can be stubbed on any class, including those in Ruby's core library.
The stub_chain method lets you to stub a chain of methods in one statement - and there is no need to stub each method in the dependency chain represented by a chain of messages to different objects.
Stubs in before(:all) are not supported. The reason is that all stubs and mocks get cleared out after each example, so any stub that is set in before(:all) would work in the first example that happens to run in that group, but not for any others.
A message expectation is an expectation that the test double will receive a message some time before the example ends. If the message is received, the expectation is satisfied. If not, the example fails.
For a negative expectation
Arguments that are passed to with are compared with actual arguments received using ==. In cases in which you want to specify things about the arguments rather than the arguments themselves, you can use any of the matchers that ship with rspec-expectations.
The implicit expectation is that the message passed to should_receive will be called once. You can make the expected counts explicit using the following
When specifying interactions with a test double, the order of the calls is rarely important. In fact, the ideal situation is to specify only a single call. But sometimes, we need to specify that messages are sent in a specific order.
This example will pass only if the count( ) and add( ) messages are sent with the correct arguments and in the same order.
Whether you are setting a message expectation or a method stub, you can tell the object precisely how to respond. The most generic way is to pass a block to stub or should_receive:
When the double receives the msg message, it evaluates the block and returns the result.
The same responses are available, as for stub method:
When working with a partial mock object, you may occasionally want to set a message expecation without interfering with how the object responds to the message.
Codebreaker is a logic game in which a code-breaker tries to break a secret code created by a code-maker. The code-maker, which will be played by the application we’re going to write, creates a secret code of four numbers between 1 and 6.
The code-breaker then gets some number of chances to break the code. In each turn, the code-breaker makes a guess of four numbers. The code-maker then marks the guess with up to four + and - signs.
A + indicates an exact match: one of the numbers in the guess is the same as one of the numbers in the secret code and in the same position.
A - indicates a number match: one of the numbers in the guess is the same as one of the numbers in the secret code but in a different position.
A great way to get started gathering user stories is to do a high-level brain dump of the sorts of things we might like to do. Here are some titles to get started:
When a new game was started, the game generates secret code. The code should have 4 items.
The code-breaker propose a guess, and the system replies by marking the guess according to the marking algorithm.
The code-breaker propose a guess that matches the secret code exactly. The system responds by marking the guess with four + signs.
After some number of turns, the game tells the code-breaker that the game is over (need to decide how many turns and whether to reveal the code).
After the game is won or lost, the system prompts the code-breaker to play again. If the code-breaker indicates yes, a new game begins. If the code-breaker indicates no, the system shuts down.
At any time during a game, the code-breaker can request a hint, at which point the system reveals one of the numbers in the secret code.
After the game is won or lost, the code-breaker can opt to save information about the game: who (initials?), how many turns, and so on.
Now we're going to describe the expected behavior of instances of the Game class.spec/spec_helper.rb spec/codebreaker/game_spec.rb
So let's add some code structurelib/codebreaker.rb lib/codebreaker/game.rb
In game_spec.rb, we want to specify that when we start the game, it sends the right messages to the output.spec/codebreaker/game_spec.rb
The failure message tells us that output never received puts. Here’s what we need to do to get this example to pass:lib/codebreaker/game.rb
The following failing step is the next thing to work on: And I should see "Enter guess:".spec/codebreaker/game_spec.rb
This time, the output didn’t receive puts('Enter guess:'). Resolve that as follows:lib/codebreaker/game.rb
We’ve told the double in the first example to expect puts( ) with “Welcome to Codebreaker!” and we’ve satisfied that requirement, but we’ve only told it to expect “Welcome to Codebreaker!” It doesn’t know anything about “Enter guess:”spec/codebreaker/game_spec.rb
"Refactoring is the process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure."
In this case, we have a very clear break between what is context and what is behavior, so let’s take advantage of that and move the context to a block that is executed before each of the examples.spec/codebreaker/game_spec.rb