The main purposes of this Ruby project:
- Encapsulate all my practical knowledge of Ruby into a Living Document of RSpec unit tests and Cucumber Features.
- Refine my TDD, BDD, and SBE-focused approach to solving problems.
- Practise vital programming skills -- Git, information security, etc.
In short, it goes some way to answering the question, "What problems can you solve, and how do you solve them in Ruby"?
- Install Ruby 2.6.4
- Clone this repository: https://github.com/clockworkpc/cpc-ruby.git
- Navigate to the root directory of project.
- Run
bundle installin the command line to install the project gems. - To run the tests you have the following options:
bundle exec rspecto run all the RSpec unit tests;bundle exec cucumberto run all the RSpec unit tests;bundle exec guardto run Guard in the background, which will run the RSpecs or Cucumber tests whenever a file is changed or saved, if you want to run a specific test,bundle exec rspec {_spec.rb filepath}to execute a specific RSpec file.bundle exec cucumber {_.feature filepath}to execute a specific Cucumber feature file.
This functionality requires the following:
-
A paid subscription to https://isbndb.com.
-
An authenticated API Key stored in the
.envfile:ISBN_DB_API_KEY={your API key}
Accordingly, Specs that require the above are ignored by the default scope group in the Guardfile.
The Guardfile has the following scope groups:
:offline=> runs Specs taggedoffline: true. (Default):online=> runs Specs taggedonline: true.
The Guardfile does not track Cucumber features, even though it could, because features should be tested by specific events, not merely by a change to the file. This way, active development is done through TDD in RSpec, and when all the Specs pass, then I go back to the Feature. Without this strict separation of concerns, RSpec and Cucumber will do each other's job.
The following Rake tasks are available if you want to run Specs and Features, with HTML reports:
$ bundle exec rake -T | grep -i "test"
rake test:all_tests # Execute all Specs and Cucumber Features
rake test:offline_features # Execute Cucumber features tagged @offline
rake test:offline_specs # Execute all Specs tagged @offline, output to terminal
rake test:offline_specs_html # Execute all Specs tagged @offline, output to HTML
rake test:offline_tests # Execute all Specs and Cucumber Features tagged @offline
rake test:online_features # Execute Cucumber features tagged @online
rake test:online_specs # Execute all Specs tagged @online, output to terminal
rake test:online_specs_html # Execute all Specs tagged @online, output to HTML
rake test:online_tests # Execute all Specs and Cucumber Features tagged @online- DRY Ruby code in
lib - Test-driven development (TDD) provided with RSpec unit tests in
spec, - Behaviour-driven development (BDD) with Cucumber Feature and Step files in
feature. - Proper namespacing and inheritance (as can be seen from
lib/cpcand its child directories) - Documentation (i.e. this
README) - Management of dependencies with the
Gemfile - Handling of events upon file system modifications with
guard
- Create
.featurefile in thefeaturesfolder, wherein to define the problem and desired output in plain English [1], containing a large number of scenarios. - Run
cucumberin the terminal and get the missing Steps - Create a
*_steps.rbfile in thefeature/step_definitionsfolder and define how the Feature inputs will be passed to the methods. - Create a
*_spec.rbfile in thespecfolder, and add as inputs and expectations a sample of the those in the from the Cucumber Feature. - Create a
.rbfile in thelibfolder: - Class, if the functionality requires instantiation or to keep track of state.
- Module if the problem can be solved with static methods.
- Apply TDD (Red-Green) until the RSpecs are satisfied.
- Run
cucumberon all the scenarios and make adjustments to the Ruby code or the RSpecs until all the scenarios in the Feature are satisfied.
[1] Gherkin tends to become more pseudo-code than English in real life, but it's a great tool for putting the big picture into words. (And besides, that's what a lot of companies work with)
There are three main namespaces:
Cpc::UtilCpc::ToolkitCpc::Api
Cpc::Util contains Modules whose methods are abstractions of generally useful things that I use in my day-to-day coding.
Cpc::Toolkit contains Classes that have the option of include-ing Cpc::Util Modules. Each Class contains the functionality needed to solve a particular real-life problem.
Cpc::Api contains Classes that invoke the custom API wrapper Cpc::Util::ApiUtil.
As a further demonstration, there is a fourth namespace: Cpc::Codewars, which contains solutions to a few Codewars puzzles. These are either Modules or Classes, as the above-described considerations require.
More for discipline than expedience, wherever possible, I try to write methods in Modules rather than Classes, in order to clarify whether my methods really need to keep track of state. A Module tends to house the most abstracted functionality, e.g. Cpc::Util::ApiUtil, whereas a Class contain application-specific attributes and methods, e.g. Cpc::Api::GoogleSheets.
To quote the excellent answer on Stack Overflow:
A class should be used for functionality that will require instantiation or that needs to keep track of state. A module can be used either as a way to mix functionality into multiple classes, or as a way to provide one-off features that don't need to be instantiated or to keep track of state. A class method could also be used for the latter.
With that in mind, I think the distinction lies in whether or not you really need a class. A class method seems more appropriate when you have an existing class that needs some singleton functionality. If what you're making consists only of singleton methods, it makes more sense to implement it as a module and access it through the module directly.