Lecture 3 Miscellaneous Ruby and Testing 1
Sublime Text Guide I wrote a quick Sublime Text Guide that will help with Rubocop offenses It ll walk you through: Using spaces instead of tabs by default Using 2 spaces in Ruby files by default Enabling Rubocop linting within the Sublime Text Sublime Text Setup Guide 2
Homework 1 Grades were released on Saturday! TAs provided feedback on best practices, but did not take off points Keep the comments in mind for future homework assignments Any questions? 3
Homework 2 README was missing a significant part of the assignment It has since been updated Homework 2 will be due on Thursday @ 11:59pm If you submit it tonight, I will give you a free late day to use on any homework assignment this semester Any questions? 4
Remaining Office Hours Wednesday, 4:30-5:30pm, Moore 100 (Jackie) Wednesday, 8-9pm, Harrison Mezz (Sanjana) Thursday, 6-7pm, Moore 100 (Jackie) 5
Variables 6
Constants Constants are like variables, but their value should never be changed once they re initialized Constants start with a capital letter By convention, they should be in all upper case and words separated with underscores (e.g. THIS_CONSTANT) Ruby will allow you to change them, but it will output a warning They can also be accessed from another class or module with :: class Foo BAR = 'bar' end p Foo::BAR #=> "bar" 7
Global Variables Denoted with a $ in the beginning of the identifier They are (mostly) accessible throughout the entire Ruby program and never go out of scope They generally provide information you would want regardless of where in the program you are For example, $0 returns the name of the startup file for the program Never use global variables unless you know what you're doing 8
Regular Expressions 9
Regular Expressions Regular expressions are usually instantiated with // from the Regexp class They match patterns For example, if I wanted to find all letters in the string 'h3l1o w0r1d' 'h3l1o w0r1d'.scan(/[a-za-z]/) #=> ["h", "l", "o", "w", "r", "d"] 10
Methods to Match String#scan Returns an array of all matches Returns an empty array if there is no match String#=~ or Regexp#=~ This will return the index where the regexp matches the string It will return nil if there is no match '123hello' =~ /[a-z]/ #=> 3 String#match or Regexp#match This will return a MatchData object, which can be used to get more information about the match It will return nil if there is no match 11
How to Write Regexes Use Rubular to experiment You can match with: literal characters dot wildcard characters character classes 12
Literal Characters The exact characters in the regular expression are used 'abcd' =~ /a/ #=> 0 'abcd' =~ /ab/ #=> 0 'abcd' =~ /ac/ #=> nil 13
Dot Wildcard Characters Using a dot (.) will match any character in that place To match for an actual dot, escape it with the backslash (\) This is also true for any other special characters 'cat bat data'.scan(/.at/) #=> ["cat", "bat", "dat"] 'hello. so. nooooo'.scan(/o\./) #=> ["o.", "o."] 14
Character Classes A list of characters can be denoted with square brackets ([]) 'cat bat sat'.scan(/[cb]at/) #=> ["cat", "bat"] These also support ranges with a hyphen (-), so to search for all letters, use [a-za-z] 'hi bye'.scan(/[a-za-z]/) #=> ["h", "i", "b", "y", "e"] 15
Common Character Classes Instead of constantly typing out the same list of character classes, some have been abstracted into special characters For example: \s denotes whitespace characters \d denotes digits '12hello3'.scan(/\d/) #=> ["1", "2", "3"] 16
Callable Objects 17
What is a block? Blocks are one of the few things in Ruby that are not objects They are the pieces of code between the do and end keywords or {} 3.times do print 'hi' end 18
How to use blocks in your own methods? Denote an argument as a block by starting it with a & Each method can only have one block passed to it It must be the last argument in the method signature The block can be called with either the call method or the yield keyword in a method The yield keyword is faster and preferred in methods Outside of methods, use the call method def call_block(&block) block.call yield end call_block { print 'hi ' } #=> "hi hi " 19
Passing arguments to blocks Arguments can be passed to the call method or yield keyword Parameters are then defined in the block between pipelines This should not surprise you since you've already had plenty of practice with this with iterators def call_block(&block) yield('foo') end call_block { elem p elem } #=> "foo" 20
Blocks are flexible It turns out that any number of arguments can be passed to blocks If too many arguments are passed in, the remaining arguments are "thrown away If not enough arguments are passed in, the remaining parameters are defaulted to nil 21
def call_block(&block) yield yield(1, 2) end call_block do x p x end #=> nil #=> 1 22
What is a proc? A Proc is a type of callable object, and it is instantiated with a block It can be instantiated with Proc.new or the proc method The proc method is preferred Since it can be stored in variables, it is a good way to write blocks in a DRY way a_proc = Proc.new { p 'foo' } a_proc.call #=> "foo" another_proc = proc { p 'bar' } another_proc.call #=> "bar" 23
Passing procs to methods The & denotes the argument is a proc a_proc = proc { p 'foo' } def bar(&baz) yield end bar(&a_proc) #=> "foo" 24
What does & do? It turns out that it: denotes the argument stands in for a block calls to_proc on the argument When to_proc is called on a proc, the proc simply returns itself However, any class that defines to_proc that returns a proc can be used instead of a block One important example is symbols. Calling to_proc on a symbol returns a proc of the method with the same "name" as the symbol 'hello'.split.map { c c.upcase }.join #=> "HELLO" 'hello'.split.map(&:upcase).join #=> "HELLO" 25
Procs as closures A closure is a function that retains the environment from which it is created In the case of Ruby, a proc is a closure def foo(x) y = 10 proc { z x + y + z } end first_proc = foo(1) second_proc = foo(2) first_proc.call(3) #=> 14 second_proc.call(3) #=> 15 26
What is a lambda? A lambda is a special type of proc (it's still an instance of the Proc class) It will throw an error when an incorrect number of arguments is passed in Lambdas treat explicit return keywords differently In lambdas, a return will just exit the lambda In normal procs, a return will exit the method it's in or throw an error if not in a method 27
def foo puts 'calling lambda' lam = lambda { return } lam.call puts 'calling proc' pr = proc { return } pr.call puts 'all done!' end foo #=> "calling lambda" #=> "calling proc" 28
Instantiating Lambdas The lambda method with a block can be used similar to the proc method for procs Another way is to use ->, the stabby lambda Stabby lambdas are preferred for one liner lambdas The lambda method is preferred for multi-line lambdas Lambdas are generally preferred over procs unless a specific behavior of normal procs is desired 29
Stabby Lambda Pass a block to the stabby lambda to instantiate the lambda Pass arguments in parentheses to the stabby lambda to pass them to the block lam1 = -> { p 'hi' } lam1.call #=> "hi" lam2 = ->(x, y) { x + y } lam2.call(1, 2) #=> 3 30
Command Line Interface (CLI) Use the gets method to collect user input It will have a newline character at the end, so call strip on the output of gets to get rid of it This is useful for writing dynamic command line interfaces 31
Testing 32
Why do we write tests? Fewer bugs in new and existing code Allows us to refactor They make for good documentation Improve design 33
Test-Driven Development (TDD) Process that involves writing tests before writing the code being tested Steps: 1. Write a very small test for code that does not yet exist 2. Run the test & watch it fail 3. Write just enough code to make the test pass 4. Once the test passes, refactor as necessary 5. Repeat 34
Behavior Driven Development (BDD) Started as a way to better understand & explain TDD Puts the focus on behavior instead of structure BDD Triad: Given some context When some event occurs Then I expect some outcome 35
Ruby Testing Frameworks MiniTest RSpec* Cucumber 36
RSpec RSpec is a productive Ruby test framework Made of three independent gems: rspec-core: overall test harness that runs the specs rspec-expectations: provides a readable, powerful, syntax for checking properties of your code rspec-mocks: makes it easy to isolate code being tested from rest of system 37
Getting Started gem install rspec Run bundle exec rspec --init Creates.rspec file Creates spec/spec_helper.rb which contains configuration options 38
Specs RSpec tests are known as specs Short for specifications because they specify the desired behavior of your code Specs are placed in files ending in _spec.rb inside of the spec directory Serve two purposes: Documenting what the object can do Checking that the object does what it s supposed to 39
Example Groups You create example groups in RSpec using describe Define what you re testing & keep related specs together You can pass a string, Ruby class, module, or object to describe describe User do describe '#valid?' do... end end 40
Examples You create examples using it You pass it the description of the behavior you re specifying describe User do describe '#valid?' do it 'returns true if the user is valid' do... end end end 41
context context is an alias for describe Groups together examples that are related to a shared situation or condition Useful to improve readability describe User do describe '#valid?' do context 'when the user is valid' do it 'returns true' end end end 42
Expectations Expectations are like assertions in other languages Each RSpec example should contain at least one expectation Most of the time they should contain exactly one Primary goal is for clarity 43
Expect You create expectations using expect Parts: Subject what s being tested Matcher an object specifying what you expect to be true about the subject Custom Failure Message this is optional expect(post.valid?).to be true, 'post is not valid' expect(post.valid?).not_to be true, 'post is valid' 44
Matchers Similar to regular expressions Define a category of objects that meet a certain specification Examples: be_empty eq( ) raise_error include( ) be_kind_of( ) List of Built-in Matchers 45
Running Specs Run tests by running the rspec command Formatters: You can optionally use formatters for displaying the output of your specs Documentation formatter: rspec --format documentation Running specific directories/files: rspec spec/{directory/file_name} 46
Hooks Great for common setup/teardown code Types specify when the hook should run relative to the examples: before will run before every example after will run after every example around will sandwich an example Scope allows you to specify how often the hook gets run :each this is the default, will run once per example :all will run once per example group 47
Hooks before(:each) do... end after(:all) do... end around do... example.run... end 48
let let binds a name to the result of a computation They are useful for assigning variables at the beginning of an example group describe User do describe '#valid?' do let(:user) { User.new } it 'is not valid without a name' do expect(user.valid?).to be_false end end end 49
Resources RSpec API Documentation RSpec Built-In Matchers Better Specs 50
Test Coverage We want to strive for 100% test coverage Can use the simplecov gem to check coverage To use: Add gem 'simplecov', require: false to testing group of Gemfile and bundle install Add the following at top of spec/spec_helper.rb: require 'simplecov SimpleCov.start Run tests and open coverage/index.html file in browser 51
Homework 3 You will be writing tests for a Command Line Interface Grading: Correctness (15 pts) You must have at least one test for every method All tests must pass You must reach 100% test coverage Style (5 pts) Best Practices (5 pts) These will be both Ruby Best Practices & testing Best Practices 52
Homework 3 https://classroom.github.com/a/-3wuhd3y 53
Next Week: Ruby on Rails! 54