Unit Testing - JohnnyBigert.se

Download Report

Transcript Unit Testing - JohnnyBigert.se

What, why and how?

Johnny Bigert, 2012-10-25 [email protected]

My View on Unit Testing

 Part of something bigger – testing on many levels 1.

  Fast and early feedback!

Tight feedback loop, results within seconds Early implies lower cost 2.

3.

 Confidence!

Less fear of refactoring  Better code!

Testability requires decoupling and usable APIs 4.

 Reliability!

Possible to remove all non-determinism

Unit test, functional test, system test – what does it mean?

Definitions and Terminology

 There are no definitive answers  Different dimensions of automated tests:  Level – Unit, component, integration, system  API – Developer test, external test  Characteristic – Functional, non-functional  Acceptance testing, regression testing

What Level Are We Testing On?

 Unit – The smallest unit of program code, normally a single function, class with methods or data structure with functions  Component – A set of units integrated together, normally a module or subsystem comprising an isolated set of classes or files.

 Integration – Several layers of a stack integrated, normally a stand-alone application (e.g. client or server)  System – Several stand-alone applications integrated (e.g. clients and servers), normally the full system

What API Are We Using For Testing?

 Developer tests – Function calls to an internal or external interface, normally test and production code running in the same process  External tests – Invokation of a non-programming interface, normally running in separate processes or machines. For example, UI (simulated user input), network (HTTP etc), IPC.

What Aspect Are We Testing?

 Functional – The behavior of the system, normally expressed as a step-by-step instruction with some alternative paths  Non-functional – The characteristics of the system, normally expressed as measurements and numbers. For example, latency, round-trip time, memory/CPU/bandwidth consumption, scalability, availability.

Functional

Unit Component Integration System

Developer

“unit test” “component test” N/A

External

N/A “functional test” “system test” “Component test” – personal favorite. Provides stability just like a “functional test” (test code less brittle than unit tests). Provides flexibility, reliability and fast feedback just like a “unit test” (quick to write, easy to mock out problematic parts, results within milliseconds).

Non-Functional

Developer

Unit Component e.g. test threading model of a component Integration System N/A

External

N/A e.g. measure CPU consumption e.g. “load test”

Choose How to Test

 When choosing, consider  Cost (of writing, maintaining, execution resources etc.)  Feedback loop (time)  Reliability  Trade-off example: Mocking or using system x?

 Mocking: cost of writing and maintaining, risk of modeling wrong behavior.

 Using: cost/risk of non-determinism, cost of test resources and longer execution times.

Regression And Acceptance Testing

 Definition: “regression” or “regression bug”  Something that was once considered “done” but does not fulfill requirements anymore  Acceptance testing: the use of a test to determine if a feature fulfills the requirements  Acceptance tests becomes part of the regression test suite once a feature is “done”

Necessities of effective unit and component testing

Dependencies

 Assumption: we want to achieve fast feedback and reliable tests  Note, there are other kinds of tests that also have merit  Remove non-determinism: we need to be in control  Randomness, time, file system, network, databases, multi-threading  Remove dependencies to non-deterministic components

Dependencies

 What kind of problematic dependencies to you have in your code base?

Getting Rid of Dependencies, C++

 Example: Randomness  class A { public: }; void foo() { } bool coinflip = rand() % 2 == 0; if(coinflip) …  Problem: dependency to non-deterministic component (randomness)

Getting Rid of Dependencies, C++

 C++ solution: introduce interface and use dependency injection  class IRandomness { virtual bool coinflip() = 0; };

Getting Rid of Dependencies, C++

 class A { public: A( IRandomness &r ) : randomness(r) {} // ctor inject void foo() { if(randomness.coinflip()) … } private: IRandomness &randomness; };

What Is a Mock Object?

 Conforms to interface  Programmable behavior  For example, “if someone calls your send function with argument ‘hello’, return 7”  Can verify function invocations  Mock frameworks available  E.g. gmock (Google)  Much more elegant in reflective languages

Getting Rid of Dependencies, C++

 Test code:  } void testFoo() { RandomnessMock random; // inherits IRandomness A a(random); a.foo(); …

Getting Rid of Dependencies, C++

 Production code:  } void main() { Randomness A a(random); random; // inherits IRandomness …

Bi-Directional Dependencies

Application

(inherits) sendData

INetworkReceiver

onDataReceived

Network INetworkSender

(inherits)

Getting Rid of Dependencies, C

 Function pointers = hard-to-read code  Link-time tricks  Link with other translation unit defining e.g. rand()

Getting Rid of Dependencies, C

 RandMock m; // mixed C/C++ test code RandMock &getRandMock() { return m; // used by test case to access mock } } int rand() { m.rand(); // forward to mock object  Drawbacks: hard to understand test case code, one test executable per component to test = harder to maintain  Or clean C solution

Code Coverage

 Don’t focus on high levels of code coverage per se  Necessary, but not sufficient  Instead, focus on covering the most important user scenarios  After that, use code coverage to determine which code has not been covered  Analyze to understand what user scenarios are missing

Unit Test Frameworks

Test runners/frameworks  GTest  UnitTest++  CppUnit  C Unit Testing Framework Mock frameworks  GMock  Hippo  Mockpp  Amop

Agree on what to aim for

Fast Feedback Example

From a previous job:  Proprietary hardware/software/OS platform  Very expensive hardware = bottleneck in testing  Emulator to test software locally on Linux machine  Ran unit tests in emulator, turn-around time = 15 mins  Unacceptable

Fast Feedback Example

 Main problem: emulator start-up time  Challenge: proprietary types used (e.g. string classes)  Solution: fake proprietary types (string with std::string, many others just empty implementations)  Ran unit tests on local machine, turn-around time < 15 seconds (from 15 mins)

Fast Feedback Example

 Invest to speed up feedback  Invest to remove testability obstacles  Unit testing should be easy, fast and fun

Development Excellence

 Defect prevention  Continuous improvement - feedback from bugs in order to improve software development process  Relentless testing  Regression detection  Continuous integration running all regression tests often  Focus on both functional and non-functional requirements  Always releasable  Keep CI builds green  Keep the product working

Continuous Integration

 Staged  Stage 1 – build, unit tests, code coverage and functional smoke tests – 10 mins  Stage 2 – extensive functional tests, smoke non functional tests – 1 hour  Stage 3 – full functional and non-functional regression tests , static and dynamic analysis – 12 hours  Run stages per  Commit (fine-grained tracability, expensive), or  Regularly (1. commit, 2. hourly, 3. nightly)

Code Analysis Tools

 Static analysis (run on source code)  Lint – free, local analysis only, lots of false positives    Dynamic analysis (need representative data)  Klocwork – commercial, global analysis Coverity – expensive, global analysis, good precision IBM Rational Purify – commercial, memory leaks, double frees, buffer overflow, use of uninitialized memory. Instrumentation.

 Valgrind – free, memory problems, heap, usage, cache usage, call graphs, threading problems. No instrumentation.