DEV Community

loading...

Can someone experienced in writing tests expand on the mental models of unit tests?

cristiano profile image cristiano ・1 min read

Hey all, recently I've been trying to introduce tests to an app I'm working on in Rails 6 which uses MiniTest.

After going through a few guides I was still struggling and I think it's not because of the syntax of the methods available for testing but because I don't quite get the reasoning behind figuring out what to test and what is the best to way to do it.

Could someone give me an example of their thinking when they need to write tests for a method/something else? I would be very grateful! 🙇🏻‍♂️

Discussion

pic
Editor guide
Collapse
pentacular profile image
pentacular

There are a number of problems tests can address.

  • Forcing a use-case oriented mindset.
  • Correctness
  • Working examples
  • Regression

Of these, regression is the biggest problem.

Protecting the system from errors due to future changes.

Working examples help as a kind of documentation.

Correctness and use-case orientation are mostly useful for programmers starting out.

Collapse
cristiano profile image
cristiano Author

Thank you this also helps! However, I was aiming at more at an explanation of what would be the thinking process behind what to test exactly, for example considering that when a new product is created it should never have a price with a value below zero or equal to zero but it a price with a value equal or greater than 0.01 is expected.

In this example I though of testing the following:

  • Creating a product with a price that is less than 0 should is considered invalid
  • Creating a product with a price that is equal to 0 should is considered invalid
  • Creating a product with a price equal or greater than 0.01 should be accepted

In Ruby on Rails using Minitest I would write this test:

require 'test_helper'

class ProductTest < ActiveSupport::TestCase
  test "product price must be positive" do
    # Creating a product.
    product = Product.new(
      title: "A book title",
      description: "What a nice book!",
      image_url: "cover.jpg"
    )

    # Setting the price to less than zero.
    product.price = -1
    # This price value should render the product invalid.
    assert product.invalid?
    assert_equal ["must be greater than or equal to 0.01"], product.errors[:price]

    # Setting the price to zero.
    product.price = 0
    # This price value should render the product invalid.
    assert product.invalid?
    assert_equal ["must be greater than or equal to 0.01"], product.errors[:price]

    # Setting the price to a value above 0.01.
    product.price = 1
    # This price shouldn't keep the product from being valid.
    assert product.valid?
  end
end

Would this be a healthy way to go think about it and implementing a test for that value? Or should I address other casesand be asking different questions?

Thanks a lot!

Collapse
ahferroin7 profile image
Austin S. Hemmelgarn

Your basic approach appears to be a good starting point. Other cases I'd consider testing include:

  • What happens right at the boundary? More specifically, is 0.00999... rejected and 0.01 accepted?
  • What happens if you use Infinity or NaN as a price?
  • What happens when you use any of various non-numeric values as prices?

There may be other cases I'm not thinking of (and there's at least two I can think of but am fairly certain are irrelevant based on what I know of how Ruby handles floating point values), but what you want to do once you have obvious pass/fail cases worked out is enumerate all the edge cases that could cause things to go wrong. Then, you watch out for edge cases you did not think of, and add test cases for those too.

Thread Thread
cristiano profile image
cristiano Author

Thanks Austin, so it seems that when writing tests we should be making sure that values that should be accepted do pass and values which should not, won't.

Also its important to have in consideration that the test will evolve over time as we think of other cases to test, is this correct?

Considering everything together with your additions, provided that all statements are true the test itself should pass:

  • Price that is less than 0 has to be invalid
  • Price that is equal to 0 has to be invalid
  • Price equal or greater than 0.01 has to be valid
  • Price is Infinity or NaN has to be invalid
  • Price is equal to non-numeric value has to invalid

I think this was one aspect that confused me, the goal is not to get the test to pass with all the values that would typically be accepted and needed for the app to work but we also should try our code with the values that shouldn't pass and would make the app break. Then we can make adjustments to our code to make sure a feature works as it should, not the test.

If this is correct than this is really helpful! 🙏

Thread Thread
pentacular profile image
pentacular

For correctness you want to test the transitions between valid and invalid, yes.

There are generally an infinite number of valid and invalid values, but usually a finite number of discontinuities in the domain of a function.