CSC/ECE 517 Fall 2012/ch2b 2w69 as: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
 
(67 intermediate revisions by 2 users not shown)
Line 1: Line 1:
=Open/Closed Principle=
=The Open/Closed principle=
__TOC__  
__TOC__  


== Introduction ==
== Introduction ==
In [http://en.wikipedia.org/wiki/Object-oriented_programming object-oriented programming] the [http://en.wikipedia.org/wiki/Open/closed_principle Open/Closed principle states],
In [http://en.wikipedia.org/wiki/Object-oriented_programming object-oriented programming] the [http://en.wikipedia.org/wiki/Open/closed_principle Open/Closed principle] states<ref name = meyer />,


''Software entities (Classes, Modules, Functions, etc.) should be open for extension, but closed for modification.''
''Software entities (Classes, Modules, Functions, etc.) should be open for extension, but closed for modification.''


It sounds like a contradiction in terms, but it's not. All it means is that you should structure an application so that you can add new functionality with minimal modification to existing code. All systems change during their life cycles. One should keep this in mind when designing systems that are expected to last longer than the initial version.  What you want to avoid is to have one simple change ripple through the various classes of your application. That makes the system fragile, prone to regression problems, and expensive to extend. To isolate the changes, you want to write classes and methods in such a way that they never need to change once they are written.
At first it might sound like a contradiction in terms, but it's not. All it means is that you should structure an application so that you can add new functionality with minimal modification to existing code. All systems change during their life cycles. One should keep this in mind when designing systems that are expected to last longer than the initial version.  What you want to avoid is to have one simple change ripple through the various classes of your application. That makes the system fragile, prone to regression problems, and expensive to extend. To isolate the changes, you want to write classes and methods in such a way that they never need to change once they are written<ref name = microsoft />.


Open/Closed principle is one of the five principles basic of [http://en.wikipedia.org/wiki/OOD object-oriented design(OOD)] which are defined as the [http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29 S.O.L.I.D.]. The principles of SOLID are guidelines that can be applied while working on software to remove [http://en.wikipedia.org/wiki/Code_smell code smells] by causing the programmer to refactor the software's source code until it is both legible and extensible. It is typically used with [http://en.wikipedia.org/wiki/Test-driven_development test-driven development], and is part of an overall strategy of [http://en.wikipedia.org/wiki/Agile_software_development agile] and [http://en.wikipedia.org/wiki/Adaptive_Software_Development adaptive programming].
Open/Closed principle is one of the five principles basic of [http://en.wikipedia.org/wiki/OOD object-oriented design(OOD)] which are defined as the [http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29 S.O.L.I.D.]. The principles of SOLID are guidelines that can be applied while working on software to remove [http://en.wikipedia.org/wiki/Code_smell code smells] by causing the programmer to refactor the software's source code until it is both legible and extensible. It is typically used with [http://en.wikipedia.org/wiki/Test-driven_development test-driven development], and is part of an overall strategy of [http://en.wikipedia.org/wiki/Agile_software_development agile] and [http://en.wikipedia.org/wiki/Adaptive_Software_Development adaptive programming]<ref name = openclosed_video />.


=== Testing Approaches ===
== Motivation ==
Testing approaches must have a logico-mathematical form, i.e., have one right answer. There are different approaches to software testing and are classified into different levels, depending on the stage of Software Development Life cycle (SDLC) in which it is done. The different levels are unit testing, integration testing, system testing, system integration testing and performance testing.  
All software systems are subject to change. Thus designing a system which is stable is a very crucial task. When a single change to a program results in a cascade of changes to dependent modules, that program exhibits the undesirable attributes that we have come to associate with “bad”
design. The program becomes fragile, rigid, unpredictable and unreusable. The open-closed principle attacks this in a very straightforward way. Open-closed principle states that the code should be designed in such a way that it should not change. Whenever requirements change, the existing code should be extended by adding new code and leave the old working code intact.


==== Unit testing ====
Most of the times it easier to write all new code rather than making changes to existing code. Modifying old code adds the risk of breaking existing functionality. With new code you generally only have to test the new functionality. When you modify old code you have to both test your changes and then perform a set of regression tests to make sure you did not break any of the existing code.
[http://en.wikipedia.org/wiki/Unit_testing Unit testing] is a method by which individual units of source code are tested to validate that they are functionally correct. It tests the basic unit of software, which is often called “unit”, “module”, or “component”. In a procedural programming, a unit could be an entire module but is more commonly an individual function or procedure. In an object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method. Unit tests are created by programmers during the development of the individual units.


==== Integration testing ====
The Open Close Principle states that the design and writing of the code should be done in a way that new functionality should be added with minimum changes in the existing code. The design should be done in a way to allow the adding of new functionality as new classes, keeping as much as possible existing code unchanged. Thus following open-closed principle leads to good software design.
[http://en.wikipedia.org/wiki/Integration_testing Integration Testing] is the phase in software testing in which individual unit tested software modules are combined and tested together.


It occurs after unit testing and before system testing. Integration testing takes as its input modules that have been unit tested, groups them in larger aggregates, applies tests defined in an integration test plan to those aggregates, and delivers as its output the integrated system ready for system testing.  
== Description ==
Every module obeying open-closed principle have the following key attributes:
* <b>They are “Open For Extension”.</b> This means that the behavior of the module can be extended. That we can make the module behave in new and different ways as the requirements of the application change, or to meet the needs of new applications.
* <b>They are “Closed for Modification”.</b> The source code of such a module is inviolate. No one is allowed to make source code changes to it.


In its simplest form, two units that have already been tested are combined into a component and the interface between them is tested. A component, in this sense, refers to an integrated aggregate of more than one unit. In a realistic scenario, many units are combined into components, which are in turn aggregated into even larger parts of the program. The idea is to test combinations of pieces and eventually expand the process to test modules with those of other groups. Eventually all the modules making up a process are tested together.
At starting, these two points seem to be very contradictory to each other. How can the module be extensible when it is not supposed to me modified? The key idea to go about this is [http://en.wikipedia.org/wiki/Abstraction_(computer_science) abstraction]<ref name = abstraction_wiki> </ref>.


==== System Testing ====  
==== Abstraction ====
[http://en.wikipedia.org/wiki/System_testing System testing] of software or hardware is testing conducted on a complete, integrated system to evaluate the system's compliance with its specified requirements. System testing should require no knowledge of the inner design of the code or logic. <ref name = system-testing /> Once the components of a software are integrated, the system as a whole needs to be rigorously tested to ensure that it meets the Quality Standards.Thus the System testing builds on the previous levels of testing namely unit testing and Integration Testing.
In object oriented design, it is possible to create abstractions that are fixed and yet represent an unbounded group of possible behaviors. The abstractions are abstract base classes, and the unbounded group of possible behaviors is represented by all the possible derivative classes. It is possible for a module to manipulate an abstraction. Such a module can be closed for modification since it depends upon an abstraction that is fixed. Yet the behavior of that module can be extended by creating new derivatives of the abstraction.


In the SDLC  System Testing is the first level where the System is tested as a whole. The System is tested, in an environment that closely resembles the production environment where the application will be finally deployed, to verify if it meets the functional and technical requirements. The System Testing enables us to test, verify and validate both the Business requirements as well as the Application Architecture.
===== Abstraction Example =====


==== Software performance testing ====
  struct Square
[http://en.wikipedia.org/wiki/Software_performance_testing Software performance testing] is performed to determine if an application will meet the specified performance requirements. Performance testing can test load by simulating a heavy load, such as many users or large chunks of data going through the system. It can also perform stress tests to determine the upper limits of load that a system can endure. Performance tests monitor the attributes of a system such as throughput, system latency, or response time. These attributes can be monitored post-deployment to help troubleshoot or alert the team of performance delays that might occur. Performance testing can also serve to investigate, measure, validate or verify other quality attributes of the system, such as scalability, reliability and resource usage.
  {
    ShapeType itsType;
    double itsSide;
    Point itsTopLeft;
  };<br>
  //
  // These functions are implemented elsewhere
  //<br>
  void DrawSquare(struct Square*)
  void DrawCircle(struct Circle*);
  typedef struct Shape *ShapePointer;
  void DrawAllShapes(ShapePointer list[], int n)
  {
    int i;
    for (i=0; i<n; i++)
    {
      struct Shape* s = list[i];
      switch (s->itsType)
      {
        case square:
          DrawSquare((struct Square*)s);
          break; <br>
        case circle:
          DrawCircle((struct Circle*)s);
          break;
      }
    }
  }


=== Test Driven Development (TDD) ===
In the above example, the function DrawAllShapes does not conform to the open-closed principle because it cannot be closed against new kinds of shapes. If we wanted to extend this function to be able to draw a list of shapes that included triangles, we would have to modify the function. In fact, we would have to modify the function for any new type of shape that we needed to draw. Now let us look at a more better solution for this problem.
In [http://en.wikipedia.org/wiki/Test-driven_development Test Driven Development] or Test first programming, tests are written first even before the code. The code is written in a way that is just enough to pass those tests. Therefore, the first step in implementing any new functionality is to describe your expectations with failing test cases and only then write a code to implement the functionality and pass the described failure test. To write a test, the developer must clearly understand the feature's specification and requirements, it makes the developer focus on the requirements before writing the code, which is a subtle but important difference. There are different testing frameworks available in Ruby based on TDD e.g.  MiniTest, Riot which we will explain in the following sections.<ref name = TDD />


=== Behavior-Driven Development (BDD) ===
  class Shape
The focus of [http://en.wikipedia.org/wiki/Behavior-driven_development Behavior-Driven Development] (BDD) is on the languages and interactions used during the process of software development. It stresses on specifying behaviors that are easily understandable by people who are from non technical background. It allows non programmers to write test cases in a natural language. Behaviour-Driven Development encourages developers to think of the behavior of the component that they are developing, and of the other objects it interacts with. This allows the developers to focus on why the code should be created, rather than the technical details, and minimizes translation between the technical language in which the code is written and the domain language spoken by the" domain experts. Ruby has BDD based testing frameworks like Capybara, MiniSpec, Cucumber, RSpec etc. for testing rails applications. We will be explaining some of these in the next sections.<ref name = BDD />
  {
    public:
    virtual void Draw() const = 0;
  }; <br>
  class Square : public Shape
  {
    public:
    virtual void Draw() const;
  };<br>
  class Circle : public Shape
  {
    public:
    virtual void Draw() const;
  }; <br>
  void DrawAllShapes(Set<Shape*>& list)
  {
    for (Iterator<Shape*>i(list); i; i++)
    (*i)->Draw();
  }


== Testing in Ruby ==
Note that in the above solution, if we want to extend the behavior of the DrawAllShapes function in to draw a new kind of shape, all we need do is add a new derivative of the Shape class. The DrawAllShapes function does not need to change. Thus DrawAllShapes conforms to the open-closed principle. Its behavior can be extended without modifying it.


Ruby makes it simpler for the developers to write tests. Testing support was present into Ruby from the beginning. It provides a framework in its standard library for setting up organizing, and running tests called Test::Unit. There are many cases where a small bit of code needs to be run before and/or after each test. Test::Unit provides the setup and teardown member functions which are run before and after every test case.  
==== Strategic Closure ====
It should be clear that no significant program can be 100% closed. For example, consider the above DrawAllShapes example. What would happen to the DrawAllShapes function if we decided that all Circles should be drawn before any Squares. The DrawAllShapes function is not closed against a change like this. In general, no matter how “closed” a module is, there will always be some kind of change against which it is not closed. Since closure cannot be complete, it must be strategic. That is, the designer must choose the kinds of changes against which to close his design. This takes a certain amount of prescience derived from experience. The experienced designer knows the users and the industry well enough to judge the probability of different kinds of changes. He then makes sure that the open-closed principle is invoked for the most probable changes.


Ruby on Rails also has testing support woven into it from the beginning. While creating the models and controllers, it produces the skeleton test code in the background. Only running these rails tests can also ensure that the code adheres to a desired functionality even after major code refactoring. These tests can even simulate some browser requests and allow us to test an application’s response without testing it through a browser. As Rails applications interact with databases heavily, it provides a test database for testing sample data.  
==== Conventions to follow ====
In order to obey open-closed principle it is good to follow some conventions<ref name=pdf></ref>.
* <b>Make all Member Variables Private:</b> Member variables of classes should be known only to the methods of the class that defines them. Member variables should never be known to any other class, including derived classes. Thus they should be declared private, rather than public or protected. In light of the open-closed principle, the reason for this convention ought to be clear. When the member variables of a class change, every function that depends upon those variables must be changed. Thus, no function that depends upon a variable can be closed with respect to that variable. <br>
* <b>No global variables:</b> The argument against global variables is similar to the argument against pubic member variables. No module that depends upon a global variable can be closed against any other module that might write to that variable. Any module that uses the variable in a way that the other modules don’t expect, will break those other modules. It is too risky to have many modules be subject to the whim of one badly behaved one. <br>
* <b>Avoid run time type identification:</b> Another very common proscription is the one against dynamic_cast. It is often claimed that dynamic_cast, or any form of run time type identification is intrinsically dangerous and should be avoided to comply with the open-closed principle.


In Ruby, Test::Unit provides a set of different assertions listed below with meaning of each assertion. 
== Example ==
Suppose you are writing a module to approve personal loans and before doing that you want to validate the personal information, code wise we can depict the situation as:


{| border=1 cellspacing=0 cellpadding=5
public class LoanApprovalHandler
| assert( boolean, [message] )
  {
| True if ''boolean''
  public void approveLoan(PersonalValidator validator)<br />
|-
  {
| assert_equal( expected, actual, [message] )<br>assert_not_equal( expected, actual, [message] )
    if ( validator.isValid())
| True if ''expected == actual''
    {
|-
        //Process the loan.
| assert_match( pattern, string, [message] )<br>assert_no_match( pattern, string, [message] )
    }
| True if ''string =~ pattern''
  }
|-
}
| assert_nil( object, [message] )<br>assert_not_nil( object, [message] )
public class PersonalLoanValidator
| True if ''object == nil''
{
|-
  public boolean isValid()
| assert_in_delta( expected_float, actual_float, delta, [message] )
  {
| True if ''(actual_float - expected_float).abs <= delta''
    //Validation logic
|-
  }
|assert_instance_of( class, object, [message] )
}
| True if ''object.class == class''
|-
|assert_kind_of( class, object, [message] )
| True if ''object.kind_of?(class)''
|-
| assert_same( expected, actual, [message])<br>assert_not_same( expected, actual, [message] )
| True if ''actual.equal?( expected )''.
|-
| assert_raise( Exception,... ) {block}<br>assert_nothing_raised( Exception,...) {block}
| True if the block raises (or doesn't) one of the listed exceptions.
|-
| assert_throws( expected_symbol, [message] ) {block}<br>assert_nothing_thrown( [message] ) {block}  
| True if the block throws (or doesn't) the expected_symbol.
|-
| assert_respond_to( object, method, [message] )  
| True if the object can respond to the given method.
|-
| assert_send( send_array, [message] )
| True if the method sent to the object with the given arguments return true.
|-
| assert_operator( object1, operator, object2, [message] )
| Compares the two objects with the given operator, passes if ''true''
|-
|}


There are different testing frameworks available in Ruby<ref name = rubytool />:
So far so good. As we have already discussed, the requirements always change and now we are required to approve vehicle loans as well. So one approach to solve this requirement is to:
* [[#Riot|Riot]]
* [[#MiniTest|MiniTest]]
* [[#MiniSpec|MiniSpec]]
* [[#Capybara|Capybara]]
* [[#Jasmine|Jasmine]]


[[#top|Back to top]]
public class LoanApprovalHandler
{
  public void approvePersonalLoan (PersonalLoanValidator validator)
  {
    if ( validator.isValid())
    {
      //Process the loan.
    }
  }
  public void approveVehicleLoan (VehicleLoanValidator validator )
  {
    if ( validator.isValid())
    {
      //Process the loan.
    }
  }
  // Method for approving other loans.
}
public class PersonalLoanValidator
{
  public boolean isValid()
  {
    //Validation logic
  }
}
public class VehicleLoanValidator
{
  public boolean isValid()
  {
    //Validation logic
  }
}


=== <u>Riot</u> ===
We have edited the existing class to accommodate the new requirement and in the process we ended up changing the name of the existing method and also adding new methods for different types of loan approval. This clearly violates the Open/Closed principle. Lets try to implement the requirement in a different way
==== Overview and Functionality ====
This is a TDD based framework for testing Ruby applications. As opposed to some other popular testing frameworks in Ruby like Test::Unit, RSepc etc, Riot does not run the ''setup'' and ''teardown'' functions before and after each test. This speeds up test execution quite a bit, but also changes how you write your tests. In Riot, tests reside in contexts. Within these, a topic object is defined through a ''setup'' block. The actual assertions are then made with an asserts or denies block.<ref name = Riot-info />


==== Running Riot ====
/**
It is quite simple to run Riot tests. You can put the tests in any folder, but it is recommended to put it under the “test” folder. One can run the individual tests using the normal ruby command or the entire test suite can also be run. The normal ruby command would look like following:
* Interface Validator class Extended to add different validators for different loan type
*/
public interface Validator
{
  public boolean isValid();
}
/**
* Personal loan validator
*/
public class PersonalLoanValidator
  implements Validator
{
  public boolean isValid()
  {
    //Validation logic.
  }
}
/**
* Vehicle loan validator
*/
public class VehicleLoanValidator
  implements Validator
{
  public boolean isValid()
  {
    //Validation logic.
  }
}
/*
* Similarly any new type of validation can
* be accommodated by creating a new subclass
* of Validator
*/


  ruby test/units/array_empty_test.rb
Now using the above validators we can write a LoanApprovalHandler to use the Validator abstraction.


In order to be able to use Riot framework, you also need to change the test_helper.rb file under the test directory in your rails project. Add the following line into the test_helper.rb file.  
public class LoanApprovalHandler
{
  public void approveLoan(Validator validator)
  {
    if ( validator.isValid())
    {
      //Process the loan.
    }
  }
}


  require 'riot'
So to accommodate any type of loan validators we would just have create a subclass of Validator and then pass it to the approveLoan method. That way the class is CLOSED for modification but OPEN for extension.


==== Code Example ====
== Conclusion ==
We can write a simple test to see if an array is empty
In reality, achieving Open/Closed principle-compliance system-wide is not possible. There will always exist some change that the system is not closed against. Therefore, this principle must be applied judiciously to the areas of the system that are most complex and dynamic. Even partial Open Closed principle compliance results in more resilient systems. Isolating violations of Open Closed principles to specific classes, such as object factories, certainly serves to reduce the overall maintenance efforts.<ref name = journal/>


  context "An empty Array" do
This principle is at the heart of object oriented design in many ways, it motivated many heuristics associated with object oriented design<ref name = pdf/>. For example, “all member variables should be private”, or “global variables should be avoided”. Conformance to this principle yields some of the great benefits such as the application will be more robust because we are not changing already tested code, flexible because we can easily accommodate new requirements. Yet conformance to this principle is not achieved simply by using an object oriented programming language. Rather, it requires a dedication on the part of the designer to apply abstraction to those parts of the program that the designer feels are going to be subject to change.
    setup { Array.new }
    asserts("it is empty") { topic.empty? }
  end # An Array


As you can see, the '''setup''' block doesn’t use any instance variables to save the object under test — rather, the return value of the block is used as the ''topic''. This object can then be accessed in the assertions using the ''topic'' attribute. Furthermore, at their very basic level, assertions need only return a boolean. When using asserts, true indicates a pass while false indicates a fail; subsequently, when using denies, true indicates a failure whereas false indicates success.
== See Also ==
We can also nest contexts within one another. the setup blocks are executed outside-in; as in, the parents’ setups are run before the current context allowing for a setup hierarchy. '''teardown''' blocks are run inside out; the current context’s teardowns are run before any of its parents’.
Below are some of the patterns and principles that are used to structure code to isolate changes.


[[#top|Back to top]]
*[http://en.wikipedia.org/wiki/Single_responsibility_principle Single-Responsibility Principle]
 
*[http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern Chain of Responsibility Pattern]
=== <u>MiniTest</u> ===
*[http://en.wikipedia.org/wiki/Liskov_substitution_principle Liskov Substitution Principle ]
 
*[http://en.wikipedia.org/wiki/SOLID_(object-oriented_design) SOLID - Object Oriented Design]
====Overview and Functionality====
*[http://css.dzone.com/articles/openclosed-principle-real A real world example of Open/Closed Principle]
 
MiniTest is one of the more recent additions to the Ruby Testing frameworks.<ref name = previous/>  MiniTest was created to be a small, clean and fast replacement for the [[#Test::Unit|Test::Unit]] framework.
 
MiniTest is included in the standard library as of Ruby 1.9.  (If you are running Ruby 1.8, you can run [http://rubygems.org/gems/minitest gem install minitest] to get Mini::Test. Alternately, you can install the [http://rubygems.org/gems/test-unit test-unit gem] if you have Ruby 1.9 and want to get the Test::Unit functionality.)  MiniTest provides both a Test Driven Development framework and a Behavior Driven Development Framework.  Test cases in MiniTest::Unit are built similarly to those in Test::Unit - you create test cases with test methods containing assertions along with optional '''setup''' and '''teardown''' methods . 
 
Because Mini::Test was based on familiar frameworks, using it is relatively intuitive.  As creator, Ryan Davis says, "There is no magic". (View Ryan's [http://confreaks.net/videos/618-cascadiaruby2011-size-doesn-t-matter presentation on Mini::Test] at the Cascadia Ruby 2011 conference.)  Mini::Test is simple and straightforward.
 
Most of the assertions in Mini::Test::TestCase are the same as those in its predecessor with some new ones added.  The most notable difference is in the negative assertions.  In Test::Unit where you have an '''assert_not_''something''''' method, Mini::Test gave them a streamlined name of a '''refute_''something''''' method.  (assert_not_raise and assert_not_throws are no longer available.) Mini::Test::Unit provides the following assertions:
 
 
  {| style="background:#FFFFFB; margin-left: 4em;" border="1" cellpadding="4"  cellspacing="0"
  |-
  | assert|| assert_block || assert_empty || refute || refute_empty|| 
  |-
  | assert_equal || assert_in_delta|| assert_in_epsilon || refute_equal|| refute_in_delta || refute_in_epsilon
  |-
  | assert_includes|| assert_instance_of || assert_kind_of || refute_includes|| refute_instance_of|| refute_kind_of
  |-
  | assert_match || assert_nil|| assert_operator|| refute_match|| refute_nil|| refute_operator
  |-
  | assert_respond_to|| assert_same|| assert_output|| refute_respond_to|| refute_same ||
  |-
  | assert_raises|| assert_send|| assert_silent|| assert_throws|| ||
  |}
 
====Additional Features====
Minitest supports both TDD and BDD. Apart from it, MiniTest also provides benchmarking.
minitest/unit is a small and incredibly fast unit testing framework. It provides a rich set of assertions to make your tests clean and readable.
minitest/spec is a functionally complete spec engine. It hooks onto minitest/unit and seamlessly bridges test assertions over to spec expectations.
minitest/benchmark is an awesome way to assert the performance of your algorithms in a repeatable manner.
 
Aside from the API improvements, MiniTest also provides some additional features such as test randomization. In unit testing, tests should run independent from each other (i.e. the outcome or resulting state(s) of one test should not affect another).  By randomizing the order, MiniTest prevents tests from becoming order-dependent.  Should you need to repeat a particular order to test for such issues, MiniTest provides the current seed as part of the output and gives you the option to run the test using this same seed.
 
Mini::Test also gives you the ability to skip tests that are not working correctly (for debug at a later time) as well as additional options for determining the performance of your test suite.<ref name = MiniTest-Info />
 
====Code Example - Mini::Test:Unit====
We follow the same steps to create a test case that are done in Test::Unit, except that this class inherits from MiniTest:Unit::TestCase and requires 'minitest/autorun'.  (Requiring minitest/unit does not cause the test methods to be automatically invoked when the test case is run, hence we use minitest/autorun which provides this functionality.)  We also need to update the assertions to those provided with MiniTest - for this class the assert_not_nil was changed to refute_nil. 
Let us assume we have an Account class as follows:
 
  class Account
    @balance
    @name
 
    attr_accessor :balance
    attr_accessor :name
 
    def initialize(amount)
      @balance = amount
    end
 
    def deposit(amount)
      @balance += amount
    end
    def withdrawal(amount)
      @balance -= amount
    end
  end
 
The AccountTest test case is below.
 
require 'minitest/autorun'
require_relative 'account.rb'
class AccountTest < MiniTest::Unit::TestCase
  def setup
    @a = Account.new(100)
  end
  def test_deposit
    assert_equal(200, @a.deposit(100))
  end
 
  def test_withdrawal
    assert_equal(50, @a.withdrawal(50))
  end
end
 
[[#top|Back to top]]
 
=== <u>MiniSpec</u> ===
 
==== Overview and Functionality ====
Minispec is one more BDD based framework for testing Ruby on Rails applications. It is the BDD component of MiniTest and borrows concepts from existing BDD frameworks such as RSpec <ref name = Rspec /> and Shoulda <ref name = Shoulda />. MiniSpec is a small library built on top of Test::Unit to provide Rspec syntax for systems where Rspec may not be available (such as old versions of macruby, or when it's just not feasible to run Rspec).
 
MiniSpec contains the following expectations:
 
 
  {|  style="background:#FFFFFB; margin-left: 4em;" border="1" cellpadding="4"  cellspacing="0"
  |-
  | must_be|| must_be_close_to || must_be_empty || wont_be || wont_be_close_to || wont_be_empty
  |-
  | must_be_instance_of || must_be_kind_of|| must_be_nil || wont_be_instance_of|| wont_be_kind_of || wont_be_nil
  |-
  | must_be_same_as || must_be_silent || must_be_within_delta || wont_be_same_as|| wont_be_within_delta||
  |-
  | must_be_within_epsilon || must_equal|| must_include|| wont_be_within_epsilon|| wont_equal|| wont_include
  |-
  | must_match|| must_output|| must_raise|| wont_match|| ||
  |-
  | must_respond_to|| must_send|| must_throw|| wont_respond_to|| ||
  |}
 
 
====Code Example - MiniSpec====
 
Let us assume we have some accounts files, and we are trying to read a file from this group of files. If the file is not of accounts, the test case would fail. We can write something like following <ref name = MiniSpec-example />:
 
  require 'minitest/spec'
  require 'minitest/autorun'
  require './account_file'
  describe AccountFile do
    describe "parse" do
      it "will return an instance of AccountFile" do
        AccountFile.parse('test_file.txt').must_be_kind_of(AccountFile)
      end
    end
  end
 
[[#top|Back to top]]
 
=== <u>Capybara</u> ===
 
====Overview and Functionality====
This is a framework used for doing integration testing for rails applications. It is based on Behaviour Driven Development. It provides more english like way to write the test cases during development. It helps you test Rails applications by simulating how a real user would interact with your application.  It is agnostic about the driver running your tests and comes with Selenium (http://seleniumhq.org/) support built in. Capybara can be used with RSpec and Test::Unit and Cucumber. We will discuss steps of using Capybara with Test::Unit and Cucumber in our further discussion. <ref name = Capybara-README />
 
In order to be able to use this framework, you will need to add the following lines to the Gemfile of your project in rails.
  group :development, :test do
    gem 'rspec-rails'
    gem 'capybara'  < ---- Add this line.
  end
 
Now you have to run “Bundle install” . After that, you will need to add following line to test_helper.rb file.
 
  require 'capybara/rails'
 
For writing BDD based test cases, you can use Capybara with Cucumber. The cucumber-rails gem comes with Capybara support built-in. If you are not using Rails, manually load the capybara/cucumber module by adding following line:
 
  require 'capybara/cucumber'
 
====Code Example====
You can write a simple test case for login by using Capybara DSL as <ref name = capybara-example />:
 
  When /I sign in/ do
    within("#session") do
      fill_in 'Login', :with => 'user@example.com'
      fill_in 'Password', :with => 'password'
    end
    click_link 'Sign in'
  end
 
 
=== <u>Jasmine</u> ===
 
==== Overview ====
Jasmine is a Behavior Driven Development (BDD) based testing framework. It can be used to test the JavaScript code and can also be used in Rails framework. Jasmine ships as a Rubygem and leverages familiar rake tasks and generators to automate common workflows. Jasmine has a syntax very similar to RSepc. In order to be able to use Jasmine in Rails, you will need to add the following line in the Gemfile of your project. 
 
  gem "jasmine"
 
Then update your gems, add example specs to your project, and run them:
 
  $ bundle install
  $ bundle exec rails generate jasmine:install
 
Better Rails 3/RSpec 2 support is on master, but has not yet been released.
 
====Code Example====
Consider a JavaScript code for addition of two numbers. We can write a test case for checking if the addition is returned correctly.
We can write a test case in Jasmine as below:<ref name = jasmine-example />
 
  describe('JavaScript addition operator', function () { 
    it('adds two numbers together', function () { 
        expect(1 + 2).toEqual(3); 
    }); 
  });
 
[[#top|Back to top]]
 
== Conclusion==
 
Testing has always been an integral part of the Ruby landscape.  This culture of testing has given rise to a variety of different testing frameworks.  In this article we have reviewed only a few of the most popular offerings<ref name = rubytool /> representing both the Test Driven Development and Behavior Driven Development styles.  The matrix below provides a summary of the tools highlighted on this page.  Should none of these frameworks meet the Ruby programmer's needs, there are currently a number of others with more being developed every day.  See the [http://ruby-toolbox.com/categories/testing_frameworks.html Ruby Toolbox] for a list of additional Ruby testing options.
 
===Framework Matrix ===
 
{| class="wikitable" style="font-size: 100%; text-align: left; width: auto;"
|-
! Framework
! Website
! Documentation
! IDE Integration
! Type
! Ease of Use
|-
| ''' Riot '''
|[http://rubydoc.info/gems/riot/0.12.5/Riot RubyDoc]
|[http://rubydoc.info/gems/riot/0.12.5/Riot RubyDoc]
|Eclipse, RubyMine
|TDD
|Easy to learn; Easy to use
|-
| ''' MiniTest '''
|[http://github.com/seattlerb/minitest GitHub]
|[http://rdoc.info/stdlib/minitest/1.9.2/frames RubyDoc]
|RubyMine
|TDD/BDD
| Easy to learn; Easy to use
|-
| ''' MiniSpec '''
|http://rspec.info/
|[http://rspec.info/documentation/ http://rspec.info/]
|RubyMine
|BDD
|Easy to learn; Easy to use
|-
| ''' Capybara '''
| [https://github.com/jnicklas/capybara GitHub]
| [http://rubydoc.info/github/jnicklas/capybara/master RubyDoc]
| Eclipse, RubyMine
| BDD
| Slightly more difficult to learn due to BDD topology; Easy to use
|-
| ''' Jasmine '''
| [https://github.com/pivotal/jasmine/wiki Jasmine wiki]
| [https://github.com/pivotal/jasmine/wiki/A-ruby-project-%28with-or-without-rails%29 Jasmine with rails]
| RubyMine, Eclipse
| BDD
| Steeper learning curve; Complexity makes use a bit more challenging
|}


== References ==
== References ==
<references>
<references>
<ref name = rubytool> http://ruby-toolbox.com/categories/testing_frameworks.html </ref>
<ref name = meyer> [http://en.wikipedia.org/wiki/Bertrand_Meyer Meyer, Bertrand] (1988). Object-Oriented Software Construction. Prentice Hall. ISBN 0-13-629049-3. </ref>
<ref name = TDD> http://ruby.about.com/od/testdrivendevelopment/a/teststdd.htm </ref>
<ref name = pdf> Martin, R.C. (2000). "Design Principles and Design Patterns." http://www.objectmentor.com </ref>
<ref name = BDD> http://www.oreillynet.com/pub/a/ruby/2007/08/09/behavior-driven-development-using-ruby-part-1.html
<ref name = microsoft> http://msdn.microsoft.com/en-us/magazine/cc546578.aspx Patterns in Practice: The Open Closed Principle </ref>
</ref>
<ref name = openclosed_video> http://ruby-toolbox.com/categories/testing_frameworks.html </ref>
<ref name = Riot-info> http://thumblemonks.github.com/riot</ref>
<ref name = journal> http://www2.sys-con.com/ITSG/VirtualCD_Spring05/Java/archives/0702/knoernschild/index.html </ref>
<ref name = Capybara-README> https://github.com/jnicklas/capybara/blob/master/README.md </ref>
<ref name = abstraction_wiki> [http://en.wikipedia.org/wiki/Abstraction_(computer_science) Abstraction wiki] </ref>
<ref name = MiniTest-Info> http://bfts.rubyforge.org/minitest/ </ref>
<ref name = MiniSpec-example> http://rubysource.com/test-driven-to-distraction/ </ref>
<ref name = testing> E. F. Miller, “Introduction to Software Testing Technology,” Tutorial: Software Testing & Validation Techniques, Second Edition, IEEE Catalog No. EHO 180-0, pp. 4-16 </ref>  
<ref name = system-testing> http://en.wikipedia.org/wiki/System_testing#cite_note-ieee-0 </ref>
<ref name = Rspec> http://en.wikipedia.org/wiki/RSpec </ref>
<ref name = Shoulda> http://en.wikipedia.org/wiki/Unit-testing_frameworks_for_Ruby_(programming_language)#Shoulda </ref>
<ref name = capybara-example> http://rubydoc.info/github/jnicklas/capybara/ </ref>
<ref name = previous> http://expertiza.csc.ncsu.edu/wiki/index.php/CSC/ECE_517_Fall_2011/ch2_2e_kt </ref>
<ref name = jasmine-example> http://net.tutsplus.com/tutorials/javascript-ajax/testing-your-javascript-with-jasmine/ </ref>
 
</references>
</references>

Latest revision as of 20:20, 19 November 2012

The Open/Closed principle

Introduction

In object-oriented programming the Open/Closed principle states<ref name = meyer />,

Software entities (Classes, Modules, Functions, etc.) should be open for extension, but closed for modification.

At first it might sound like a contradiction in terms, but it's not. All it means is that you should structure an application so that you can add new functionality with minimal modification to existing code. All systems change during their life cycles. One should keep this in mind when designing systems that are expected to last longer than the initial version. What you want to avoid is to have one simple change ripple through the various classes of your application. That makes the system fragile, prone to regression problems, and expensive to extend. To isolate the changes, you want to write classes and methods in such a way that they never need to change once they are written<ref name = microsoft />.

Open/Closed principle is one of the five principles basic of object-oriented design(OOD) which are defined as the S.O.L.I.D.. The principles of SOLID are guidelines that can be applied while working on software to remove code smells by causing the programmer to refactor the software's source code until it is both legible and extensible. It is typically used with test-driven development, and is part of an overall strategy of agile and adaptive programming<ref name = openclosed_video />.

Motivation

All software systems are subject to change. Thus designing a system which is stable is a very crucial task. When a single change to a program results in a cascade of changes to dependent modules, that program exhibits the undesirable attributes that we have come to associate with “bad” design. The program becomes fragile, rigid, unpredictable and unreusable. The open-closed principle attacks this in a very straightforward way. Open-closed principle states that the code should be designed in such a way that it should not change. Whenever requirements change, the existing code should be extended by adding new code and leave the old working code intact.

Most of the times it easier to write all new code rather than making changes to existing code. Modifying old code adds the risk of breaking existing functionality. With new code you generally only have to test the new functionality. When you modify old code you have to both test your changes and then perform a set of regression tests to make sure you did not break any of the existing code.

The Open Close Principle states that the design and writing of the code should be done in a way that new functionality should be added with minimum changes in the existing code. The design should be done in a way to allow the adding of new functionality as new classes, keeping as much as possible existing code unchanged. Thus following open-closed principle leads to good software design.

Description

Every module obeying open-closed principle have the following key attributes:

  • They are “Open For Extension”. This means that the behavior of the module can be extended. That we can make the module behave in new and different ways as the requirements of the application change, or to meet the needs of new applications.
  • They are “Closed for Modification”. The source code of such a module is inviolate. No one is allowed to make source code changes to it.

At starting, these two points seem to be very contradictory to each other. How can the module be extensible when it is not supposed to me modified? The key idea to go about this is abstraction<ref name = abstraction_wiki> </ref>.

Abstraction

In object oriented design, it is possible to create abstractions that are fixed and yet represent an unbounded group of possible behaviors. The abstractions are abstract base classes, and the unbounded group of possible behaviors is represented by all the possible derivative classes. It is possible for a module to manipulate an abstraction. Such a module can be closed for modification since it depends upon an abstraction that is fixed. Yet the behavior of that module can be extended by creating new derivatives of the abstraction.

Abstraction Example
 struct Square
 {
   ShapeType itsType;
   double itsSide;
   Point itsTopLeft;
 };
// // These functions are implemented elsewhere //
void DrawSquare(struct Square*) void DrawCircle(struct Circle*); typedef struct Shape *ShapePointer; void DrawAllShapes(ShapePointer list[], int n) { int i; for (i=0; i<n; i++) { struct Shape* s = list[i]; switch (s->itsType) { case square: DrawSquare((struct Square*)s); break;
case circle: DrawCircle((struct Circle*)s); break; } } }

In the above example, the function DrawAllShapes does not conform to the open-closed principle because it cannot be closed against new kinds of shapes. If we wanted to extend this function to be able to draw a list of shapes that included triangles, we would have to modify the function. In fact, we would have to modify the function for any new type of shape that we needed to draw. Now let us look at a more better solution for this problem.

 class Shape
 {
   public:
   virtual void Draw() const = 0;
 }; 
class Square : public Shape { public: virtual void Draw() const; };
class Circle : public Shape { public: virtual void Draw() const; };
void DrawAllShapes(Set<Shape*>& list) { for (Iterator<Shape*>i(list); i; i++) (*i)->Draw(); }

Note that in the above solution, if we want to extend the behavior of the DrawAllShapes function in to draw a new kind of shape, all we need do is add a new derivative of the Shape class. The DrawAllShapes function does not need to change. Thus DrawAllShapes conforms to the open-closed principle. Its behavior can be extended without modifying it.

Strategic Closure

It should be clear that no significant program can be 100% closed. For example, consider the above DrawAllShapes example. What would happen to the DrawAllShapes function if we decided that all Circles should be drawn before any Squares. The DrawAllShapes function is not closed against a change like this. In general, no matter how “closed” a module is, there will always be some kind of change against which it is not closed. Since closure cannot be complete, it must be strategic. That is, the designer must choose the kinds of changes against which to close his design. This takes a certain amount of prescience derived from experience. The experienced designer knows the users and the industry well enough to judge the probability of different kinds of changes. He then makes sure that the open-closed principle is invoked for the most probable changes.

Conventions to follow

In order to obey open-closed principle it is good to follow some conventions<ref name=pdf></ref>.

  • Make all Member Variables Private: Member variables of classes should be known only to the methods of the class that defines them. Member variables should never be known to any other class, including derived classes. Thus they should be declared private, rather than public or protected. In light of the open-closed principle, the reason for this convention ought to be clear. When the member variables of a class change, every function that depends upon those variables must be changed. Thus, no function that depends upon a variable can be closed with respect to that variable.
  • No global variables: The argument against global variables is similar to the argument against pubic member variables. No module that depends upon a global variable can be closed against any other module that might write to that variable. Any module that uses the variable in a way that the other modules don’t expect, will break those other modules. It is too risky to have many modules be subject to the whim of one badly behaved one.
  • Avoid run time type identification: Another very common proscription is the one against dynamic_cast. It is often claimed that dynamic_cast, or any form of run time type identification is intrinsically dangerous and should be avoided to comply with the open-closed principle.

Example

Suppose you are writing a module to approve personal loans and before doing that you want to validate the personal information, code wise we can depict the situation as:

public class LoanApprovalHandler
{
  public void approveLoan(PersonalValidator validator)
{ if ( validator.isValid()) { //Process the loan. } } } public class PersonalLoanValidator { public boolean isValid() { //Validation logic } }

So far so good. As we have already discussed, the requirements always change and now we are required to approve vehicle loans as well. So one approach to solve this requirement is to:

public class LoanApprovalHandler
{
 public void approvePersonalLoan (PersonalLoanValidator validator)
 {
   if ( validator.isValid())
   {
     //Process the loan.
   }
 }
 public void approveVehicleLoan (VehicleLoanValidator validator )
 {
   if ( validator.isValid())
   {
     //Process the loan.
   }
 }
 // Method for approving other loans.
}
public class PersonalLoanValidator
{
 public boolean isValid()
 {
   //Validation logic
 }
}
public class VehicleLoanValidator
{
 public boolean isValid()
 {
   //Validation logic
 }
}

We have edited the existing class to accommodate the new requirement and in the process we ended up changing the name of the existing method and also adding new methods for different types of loan approval. This clearly violates the Open/Closed principle. Lets try to implement the requirement in a different way

/**
* Interface Validator class Extended to add different validators for different loan type
*/
public interface Validator
{
 public boolean isValid();
}
/**
* Personal loan validator
*/
public class PersonalLoanValidator
 implements Validator
{
 public boolean isValid()
 {
   //Validation logic.
 }
}
/**
* Vehicle loan validator
*/
public class VehicleLoanValidator
 implements Validator
{
 public boolean isValid()
 {
   //Validation logic.
 }
}
/*
* Similarly any new type of validation can
* be accommodated by creating a new subclass
* of Validator
*/

Now using the above validators we can write a LoanApprovalHandler to use the Validator abstraction.

public class LoanApprovalHandler
{
 public void approveLoan(Validator validator)
 {
   if ( validator.isValid())
   {
     //Process the loan.
   }
 }
}

So to accommodate any type of loan validators we would just have create a subclass of Validator and then pass it to the approveLoan method. That way the class is CLOSED for modification but OPEN for extension.

Conclusion

In reality, achieving Open/Closed principle-compliance system-wide is not possible. There will always exist some change that the system is not closed against. Therefore, this principle must be applied judiciously to the areas of the system that are most complex and dynamic. Even partial Open Closed principle compliance results in more resilient systems. Isolating violations of Open Closed principles to specific classes, such as object factories, certainly serves to reduce the overall maintenance efforts.<ref name = journal/>

This principle is at the heart of object oriented design in many ways, it motivated many heuristics associated with object oriented design<ref name = pdf/>. For example, “all member variables should be private”, or “global variables should be avoided”. Conformance to this principle yields some of the great benefits such as the application will be more robust because we are not changing already tested code, flexible because we can easily accommodate new requirements. Yet conformance to this principle is not achieved simply by using an object oriented programming language. Rather, it requires a dedication on the part of the designer to apply abstraction to those parts of the program that the designer feels are going to be subject to change.

See Also

Below are some of the patterns and principles that are used to structure code to isolate changes.

References

<references> <ref name = meyer> Meyer, Bertrand (1988). Object-Oriented Software Construction. Prentice Hall. ISBN 0-13-629049-3. </ref> <ref name = pdf> Martin, R.C. (2000). "Design Principles and Design Patterns." http://www.objectmentor.com </ref> <ref name = microsoft> http://msdn.microsoft.com/en-us/magazine/cc546578.aspx Patterns in Practice: The Open Closed Principle </ref> <ref name = openclosed_video> http://ruby-toolbox.com/categories/testing_frameworks.html </ref> <ref name = journal> http://www2.sys-con.com/ITSG/VirtualCD_Spring05/Java/archives/0702/knoernschild/index.html </ref> <ref name = abstraction_wiki> Abstraction wiki </ref> </references>