Code Monkey home page Code Monkey logo

uspec's Introduction

Uspec

Uspec is a shiny little testing framework for your apps!

Gem Version Build Status Code Climate

Philosophy / Why Uspec?

Uspec is just Ruby!

  • There's no need for special matchers
  • You never have to worry that your tests lack assertions
  • There is no monkey patching of core classes

No more digging around in your test framework's documentation to figure out what matcher you're supposed to use. Because you just use Ruby!

Uspec is well under 500 lines of code. Most of that is there to gracefully handle the weird edge cases that pop up all the time during development and testing of software. Uspec will catch issues at every stage and display a nicely formatted message to provide hints at what might have gone wrong.

Uspec is tiny, painless, and easy to use. Download it and give it a try!

Writing Tests

Uspec is deceptively simple. You only need to remember one method: spec.

Writing a spec is easy:

spec 'AwesomeMcCoolname.generate creates a cool name' do
  AwesomeMcCoolname.generate.include? 'Cool'
end

That's it!

Quickstart

  1. Install in the typical way using Rubygems or Bundler:
    • gem install uspec
    • echo 'gem "uspec"' >> Gemfile && bundle install
  2. Create a uspec directory to keep your specs in
  3. Name your specs ending with _spec.rb
  4. Write some specs in Ruby using the spec method (example above)
  5. Use the included uspec executable to run your specs

And always remember that Uspec is just Ruby!

Commandline Usage

$ uspec --help
uspec - minimalistic ruby testing framework
usage: uspec [<file_or_path>...]
  • Without arguments the uspec command will automatically look for a uspec directory and load any *_spec.rb files inside it.
  • You can also pass in arbitrary files and it will attempt to run them as specs.
  • If you pass in directories uspec will scan for and run any specs inside them.
  • Uspec will return the number of failures as its exit status code to the or 0 if none.

Output

Uspec's output is in beautiful ANSI technicolor, with red for failures, green for successes, and yellow for pending specs.

uspec examples

A brief explanation of uspec's output to show you what it can do!

Success

If a spec passes (returns true):

 -- AwesomeMcCoolname.generate creates a cool name: true

Failure

If a spec fails (returns false):

 -- AwesomeMcCoolname.generate creates a cool name: false

Exception

If the spec encounters an error (raises an Exception):

 -- AwesomeMcCoolname.generate creates a cool name: Exception

    Encountered an Exception while running spec
    in spec at uspec/awesome_mc_coolname_spec.rb:3: in `<main>'

    RuntimeError < StandardError: 'wtf'

    /Users/Dude/Projects/Awesome/lib/awesome_mc_coolname.rb:18:in `explode'
    uspec/awesome_mc_coolname_spec.rb:4:in `block in <main>'

Non-boolean values

If you create a spec that doesn't return a boolean value (nil doesn't count either!) like this:

spec 'AwesomeMcCoolname.generate creates a cool name' do
  AwesomeMcCoolname.generate =~ /Badass/
end

Then Uspec will let you know so you can debug it:

 -- AwesomeMcCoolname.generate creates a badass name: Failed

    Spec did not return a boolean value
    in spec at uspec/awesome_mc_coolname_spec.rb:6: in `<main>'

    Integer < Numeric: 5

Pending

If you aren't ready to fill out a spec, maybe as a reminder to add functionality later, just leave off the block and it will be marked as pending:

spec 'a feature I have not implemented yet'

When you run the test Uspec will helpfully display:

 -- a feature I have not implemented yet: pending

Reusing Test Code

Test code reuse doesn't require doing anything special. It's just like any other Ruby code. But here are a few examples!

Hint: A lot of people put require_relative 'spec_helper' at the top of their test files and put shared code and helper methods in a file called spec_helper.rb.

Methods

If you find yourself repeating the same code in tests several times you can extract that code into a method and then call it within your spec blocks.

def new_generator
  AwesomeMcCoolname.new max_length: 15
end

spec 'generates a cool name' do
  new_generator.generate.include? 'Badass'
end

Instance Variables

This also works for instance variables!

@favorite_color = 'fuschia'

spec 'remembers favorite color' do
  ColorDB.fetch(:favorite) == @favorite_color
end

Memoized Methods

By combining the previous two capabilities of Ruby, it is trivial to memoize method output as well:

def reusable_generator
  @reusable_generator ||= AwesomeMcCoolname.new max_length: 15
end

spec 'generates a cool name' do
  reusable_generator.generate.include? 'Badass'
end

This is all the same kind of code that you use when writing any other Ruby object. You can put methods into objects and use those if you like too!

Test Matching in Plain Ruby

When the spec block is evaluated the return value is used (in a very Ruby-like way) to determine if the test has passed or failed. Standard Ruby comparisons are your friend!

Because there's no matchers and only one method there's no need for specialized reference documentation, but here are some ideas to get you going!

String matching

Instead of =~ (which returns either an Integer index or nil) Ruby has the nifty include? method, which returns a boolean:

spec 'AwesomeMcCoolname.generate creates a cool name' do
  AwesomeMcCoolname.generate.include? 'Badass'
end

Regex matching

If you really need to regex, you can always use Ruby's !! idiom to coerce a boolean out of any result, but its more precise to specify the index if you know it. And you can always toss in an || to drop in more information if a comparison fails too:

spec 'AwesomeMcCoolname.generate creates a cool name' do
  index = AwesomeMcCoolname.generate =~ /Badass/
  index == 0 || index
end

Exceptions

What if you want to test that an error has occured? Just use Ruby!

spec 'calling AwesomeMcCoolname.awesomeness without specifying the awesomeness level should explode' do
  begin
    AwesomeMcCoolname.awesomeness
  rescue ArgumentError => error
    error.message.include?("Needs awesomeness level!") || raise
  end
end

If there's no error, then Uspec will see the result of the method call (whatever it might be).

If the wrong Exception is raised, then because of reraising (by just calling raise without parameters), Ruby will dutifully pass along the error for Uspec to display.

Mocks, Spies, Stubs, and More!

Since uspec is a very straight forward testing utility it is easy to use any of the standard Ruby mocking frameworks with it. However, the Impasta gem was made specifically for simple but comprehensive mocking, stubbing, and spying.

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Author

Anthony M. Cook 2013-2024

uspec's People

Contributors

acook avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar

uspec's Issues

Return and Break in the body of a test are not caught

Currently if you put break or return in the body of a spec block then it will immediately end the test, creating an unpleasant visual artifact in the test output, and without showing that the test was skipped or counting it in the summary.

While it is easy to avoid this, mistakes could be hidden in larger test suites. Additionally being able to do an intentional early return would be useful, if it were possible to capture the early return value.

I've been using uspec for years and it never occurred to me to try until today when I was doing a step-by-step integration test that seemed to call for multiple assertions. By design, uspec is philosophically opposed to multiple assertions for unit tests. However, for integration tests, it makes more sense. Either way, it should still show the proper test count and at the very least show a failed or pending test.

Example code:

spec "does things" do
  break false
  true
end

Output:

integration_spec:
 -- does thingstest summary: 0 successful, 0 failed, 0 pending 

Note that the return values (true and false in this example) are immaterial and have no behavioral effect on this issue.

Ensure that rescuing Exception doesn't prevent SIGNALs

Uspec catches all Exceptions at certain points in order to provide useful error messages, however this may also prevent responding to SIGINT or other SIGNALs during the execution of those blocks which could be annoying.

Incompatible with Ruby 2.0 for various reasons

Depending on the patch level, the tests fail in different ways, so the failures on CI are different than on my development VM.

https://travis-ci.org/acook/uspec/jobs/546236040#L554

It has something to do with BasicObject, ancestors, or binding methods.

This same issue probably breaks 1.9.x as well.

I don't consider fixing pre Ruby 2.1.x issues high priority but I'm open to PRs that don't slow down newer Rubies or add a lot of code complexity.

Idea: Proxy object wrapper using let/subject syntax

The proxy object would catch certain methods passed to it, like a to or should method as a basis of comparison, but could also pass normal methods through to the object inside, mutate it and store the result, returning self - the proxy.

Example:

spec Cheese do
  subject :swiss do
    spec_object.new type: 'swiss'
  end

  spec 'type is swiss' do
    swiss.type.should == 'swiss'
  end
end

It is supposed to be completely transparent. The swiss variable if you accessed it with pry, would reveal itself to be a Proxy object.

Alternatively, we avoid the full proxy and just wrap it, allowing only explicit method passing through a gateway for better encapsulation.

Example:

spec Cheese do
  subject :swiss do
    spec_object.new type: 'swiss'
  end

  spec 'type is swiss' do
    swiss.has :type, eq('swiss')
    # or as a method(!?)
    swiss :type, eq('swiss')
    # or returning another proxy and abusing ruby syntax even further for more DSLing
    swiss :type eq('swiss')
  end
end

Consider replacing `at_exit` with another strategy

There are currently a few work-arounds I've had to implement to make at_exit behave right:

Having it in the library is also a problem since we can't easily test without overriding the exit code of another test framework or easily switch between testing and development modes.

A better solution would be, if we really needed at_exit that it would be in the runner only. But most likely it is not necessary at all.

I came across this article talking about it, though it provides examples of use cases, it doesn't explain alternatives.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.