Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In unit testing loosely typed languages, should the return type of methods be checked?

In strongly typed languages such as Java, there is no need to explicitly check the type of object returned since the code cannot compile if the return types do not match method signature. Ex. You cannot return a boolean when an integer is expected.

In loosely typed languages such as Ruby, JavaScript, Python, etc., anything can be returned. Would it make sense to write a unit test that checks the type of object returned from a method? This, in my opinion, will ensure that a boolean is returned where a boolean is expected.

Is it even necessary to have the Unit test below?

=============================

An attempt at Ruby example:

first_module.rb:

module FirstModule
  TypeA = Struct.new(
    :prop1,
    :prop2)

  self.create_type_a
    TypeA.new(
      'prop1Val',
      'prop2Val')
  end
end

type_a_repository.rb:

module TypeARepository
  def self.function_to_test
    FirstModule.create_type_a  # This will return TypeA object
  end
end

type_a_repository_spec.rb:

RSpec.describe '' do
  describe '' do
    before do
      allow(FirstModule).to receive(:create_type_a)
          .and_return(FirstModule.create_type_a)
    end

    it '' do
      result = TypeARepository.function_to_test
      expect(result).to be_a(FirstModule::TypeA) # is this necessary?
    end
  end
end
like image 372
Ali Avatar asked Oct 18 '18 16:10

Ali


People also ask

What should be tested in unit testing?

The purpose of a unit test in software engineering is to verify the behavior of a relatively small piece of software, independently from other parts. Unit tests are narrow in scope, and allow us to cover all cases, ensuring that every single part works correctly.

What are the differences between strongly typed and loosely typed data type implementations?

Strongly typed means, a variable will not be automatically converted from one type to another. Weakly typed is the opposite: Perl can use a string like "123" in a numeric context, by automatically converting it into the int 123 . A strongly typed language like python will not do this.

What is loosely typed programming language?

A programming language is loosely typed, or weakly typed, when it does not require the explicit specification of different types of objects and variables. The "looser" typing rules in weakly typed programming languages can produce erroneous or unpredictable results. It can execute implicit type conversions at runtime.

Which method given in the options is used for unit testing?

Unit tests can be performed manually or automated. Those employing a manual method may have an instinctual document made detailing each step in the process; however, automated testing is the more common method to unit tests.


1 Answers

If you employ programming by contract then the answer's usually "no", as in so long as the return value meets expected criteria, which are often really loose, then you can't complain.

For example:

# Adds together zero or more integer values and returns the sum
def sum(*numbers)
  numbers.inject(0,:+)
end

When testing you'd do something like this:

assert_equal 0, sum
assert_equal 1, sum(1)
assert_equal 0, sum(1, -1)

Now what happens when you supply non-integer values?

sum('1')
# => Exception: String can't be coerced into Integer

The original contract didn't specify that as a valid use case, so the exception is warranted. If you want to expand the scope:

# Adds together zero or more numerical values and returns the sum
def sum(*numbers)
  numbers.map(&:to_i).inject(0,:+)
end

Now you can add together non-integer values:

assert_equal 6, sum(1, 2.0, '3')

Note that the whole time so long as the result passes the assert test you're satisfied. In Ruby 6.0, 6, and "6" are all different, non-equivalent, so there's no worry about getting the wrong type.

This may not be true in other languages so you may need to be more specific about your results. The important thing is to avoid literal tests if you can, and instead just use the result as intended. For example:

assert_equal "this is amazing", "this is " + amazing_string_result

So long as whatever comes out of amazing_string_result can be appended to a string and the result matches that's an acceptable response.

This often comes into play when you want literal true or false instead of some truthful value like 1:

assert_true some_method?(:value)

Where if that returns a truthful but non-literal true value the contract is broken and it fails.

Remember, there's an unlimited amount of paranoia you can have. Are you sure your values add up properly?

assert_equal 6, 1 + 2 + 3
assert_equal 6, 6
assert_equal 6, '6'.to_i
assert_true true

At some point you're no longer testing your code but are instead running regression tests on the programming language or hardware you're executing this code on, which is a complete waste of time if you're writing tests for your code.

The best unit tests:

  • Demonstrate how the code is supposed to be used by clearly describing the expectations for inputs and outputs.
  • Identify and verify behaviour at any and all boundary conditions relevant to the problem being solved.
  • Illustrate failure modes when the code is used in improper ways that must be expressly disallowed.
  • Avoid demonstrating all of the infinite ways in which the code will not work.
like image 183
tadman Avatar answered Oct 02 '22 14:10

tadman