WHAT

WHY

HOW 

HOW DO YOu KNOW
THAt YOUR CODE
WORKS?

HOW DO YOu KNOW
THAt YOUR CODE
WORKS?

float sqrRoot(float a);


int main()
{
    float input = 4;
    float result = sqrRoot( input );
    printf("square root of %f is %f\n", input, result);

    return 0;
}

// square root of 4 is 2

We usually start with visual inspection...

What is unit testing?

A methodology to test that a UNIT of your code is doing what you want it to do.

A "unit testing framework" is a library that help us automating this process and reducing boilerplate in the code.

float sqrRoot(float a);


int main()
{
    float input    = 4.0;
    float expected = 2.0;
    float result   = sqrRoot( input );

    //warning, this might fail due to floating point comparison 
    if( result != expected ) 
    {
        printf("ERROR: square root of %f is %f instead of %f\n",
                  input, result, expected );
        return ERROR_CODE;
    }

    bool exception_thrown = false;
    try {
        sqrRoot( -1 );
    }
    catch(...) { 
       exception_thrown = true; 
    }

    if( !exception_thrown ){
        printf("ERROR: exception hasn't been thrown by sqrRoot(-1) \n");
        return ERROR_CODE;
    }

    return SUCCESS;
}

When we want to test more...

The "visual inspection" of larger applications

is even more time consuming.

Code

Compile

Observe result

WHy Do WE NEED UNIT TESTING THEN?

OVERALL cost

This is even more true when you consider:

  • the cost of debugging
  • the fact that you can break functionalities

Spend time preventing bug or fixing bugs?

CODE QUALITY

  • If you take it seriously, you are probably testing more edge cases than usual. 
  • Good tests "oblige" you to have modular code.
  • A complex test might be the symptom of a poor API

 

But of course there are also "bad" tests that are too superficial.

DETECT REGRESSIONS

"it was working yesterday..."

ACCOUNTABILITY

If your code is not tested and not documented,

I might not trust it.

YOu SHOULD NOT Do TESTING IF

  • Developing a "proof of concept" to prove an hypothesis
  • You are not sure that you (or someone else) will reuse that code
  • Cost of bug fixing is low... 

You and me know it is not

HOW TO TEST

PRELIMINARY RECOMMENDATIONS

  • Test a unit having a single responsibility
  • Test edge cases
  • The test must be as readable as a tutorial
  • If you find a new bug, write the corresponding test.

DON'T obsess

Sometimes 70% is better than 99.9%

GOOGLe testing framework

#include "gtest/gtest.h"
 
TEST(SquareRootTest, PositiveNos) 
{ 
    EXPECT_FLOAT_EQ (2.0, sqrRoot (4.0) );
    EXPECT_FLOAT_EQ (18.0, sqrRoot (324.0) );
    EXPECT_FLOAT_EQ (25.4, sqrRoot  (645.16) );
    EXPECT_FLOAT_EQ (50.3321, sqrRoot  (2533.310224) );
}
 
TEST (SquareRootTest, ZeroAndNegativeNos) 
{ 
    EXPECT_EQ (0.0, sqrRoot  (0.0) );
    EXPECT_ANY_THROW ( sqrRoot  (-1.0) );
    EXPECT_ANY_THROW ( sqrRoot  (-22.0) );
}
 
int main(int argc, char **argv) 
{
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
int main()
{
    float input    = 4.0;
    float expected = 2.0;
    float result   = sqrRoot( input );

    if( fabs(result - expected) < std::numeric_limit<float>::min() ) 
    {
        printf("ERROR: square root of %f is %f instead of %f\n",
                  input, result, expected );
        return ERROR_CODE;
    }

    bool exception_thrown = false;
    try {
        sqrRoot( -1 );
    }
    catch(...) { 
       exception_thrown = true; 
    }

    if( !exception_thrown ){
        printf("ERROR: exception hasn't been thrown by sqrRoot(-1) \n");
        return ERROR_CODE;
    }
    return SUCCESS;
}
TEST(SquareRootTest, MinimalTest) 
{ 
    EXPECT_FLOAT_EQ (2.0, sqrRoot (4.0));
    EXPECT_ANY_THROW ( sqrRoot  (-1.0))
}
// To use a test fixture, derive a class from testing::Test.
class DequeTest : public testing::Test {
 protected:  // You should make the members protected

  // virtual void SetUp() will be called before each test is run.
  virtual void SetUp() 
  {
    q1_.Enqueue(1);
    q1_.Enqueue(3);
    q1_.Enqueue(5);
  }

  // virtual void TearDown() will be called after each test is run.
  virtual void TearDown() {}

  void copy(List* dq1, List* dq2){
      l2->clear();
      for(int i=0; l1->size(); i++){
          l2->push_back( l1->at(i) );  
      }
  }

  // Declares the variables your tests want to use.
  Deque<int> q0_;
  Deque<int> q1_;
};

// When you have a test fixture, you define a test using TEST_F

TEST_F(DequeTest, DefaultConstructor) {
  EXPECT_EQ(0, q0_.Size());
  EXPECT_EQ(3, q1_.Size());
}

TEST_F(DequeTest, TestCopy) {
  copy( &q1_, &q0_);
  ASSERT_EQ(  q0_.size() == q1_.size() );

  for (int i=0; i< q0_.size(); i++){
      EXPECT_EQ( q0_.at(i), q1_.at(i) );  
  }
}

Fixtures

LIMITATIONS

No matter how many tests you write, you can not "prove" that your code is bug free.

 

Some bugs appear only in "production".

 

Summarizing...

References

All your tests are terrible

https://www.youtube.com/watch?v=u5senBJUkPc

Pragmatic unit testing in C++

https://www.youtube.com/watch?v=Y8YVSohnlgY​

Unit testing

By Davide Faconti

Unit testing

  • 243