Overall Flashcards Preview

Testing > Overall > Flashcards

Flashcards in Overall Deck (68)
Loading flashcards...
1

Test Double vs Mock

Mock is only one type of Test Double
(Test Double refers to *ALL* type of "doubling", i.e. used in place of a production dependency)

A live dev env server is a test double for the production env, but not a "mock" system.

2

Unit Test Isolation

Use Test double to Isolate Mutable Dependencies
Avoid shared dependency to isolate unit tests

3

MUT vs SUT

Method Under Test is a method in System (class) Under Test

4

Pattern:
Unit Test: 3-phase (AAA) sequence

Arrange, Act, Assert

5

MUT vs SUT

Method Under Test is a method in System (class) Under Test

SUT usually is 1 class, but can be several tightly coupled classes forming a bubble

6

London style
vs
Classical Style in what "isolation" means

Hint
Test Double, Shared dependencies

London: isolation from ALL MUTABLE dependencies (OK to have IMMUTABLE dependencies in tests)
Classical: Isolation from each other (isolation from shared dependencies only, allow private dependency)

London Style: Every unit test test a class only, and isolated FROM ITS MUTABLE DEPENDENCY. I.e. in Each unit test, all mutable immediate dependencies of the SUT/MUT is a Test double
Each unit test only causes code in 1 class to run (other classes are all doubled)

Classical style: Every unit test is isolated FROM each other's (shared (mutable) dependencies) and can run in parallel (E.g the test cases should not have "shared" state like accessing same record in the database)
Each unit test can cause code in multiple class (in a controlled bubble) to run

7

London Guidelines

London isolation each unit test from all mutable dependencies.
A dependency does HTTP POST, it is a mutable dep
A dependency does HTTP GET , it is a immutable dep

8

Difficult to isolate test or complicated "arrange" stage ===> bad architecture

Do not have large class graph

9

Pattern: AAA

Arrange, Act, Assert

10

Pattern: Give, When Then

GWT

11

Pattern: (single) AAA per unit test

Arrange, Act, Assert

12

Pattern: (single) GWT per unit test

Given (arrangement) When (acted) Then (assert)

13

Avoid multiple AAA per unit test function
Avoid if/else

Multiple AAA ==> not run in isolation ==> testing multiple code sections ==> this is integration test.

14

Should there be a teardown stage after AAA?

maybe, but use RAII pattern to avoid

15

Naming test case: WRONG approach:
Using something similar:

[MethodUnderTest]_[Scenario]_[ExpectedResult]
Act_Arrange_Assert()
What_Given_Then()

Wrong Approach, examples:

getEmployeeIdByName_WhenNameEmpty_ReturnNull()

16

AAA Unit Test:: One ACT at at time
Avoid multiple AAA per unit test function
Avoid if/else

Multiple AAA ==> not run in isolation ==> testing multiple code sections ==> this is integration test.

17

Naming test case: WRONG approach:
Using something similar:

[MethodUnderTest]_[Scenario]_[ExpectedResult]
Act_Arrange_Assert()
What_Given_Then()

BAD: include the name of the SUT’s method in the test’s name.

Wrong Approach, examples:

getEmployeeIdByName_WhenNameEmpty_ReturnNull()

You test behavior, not implementation (Method name is considered implementation)

18

Naming test: remember each test case tests a "behavior", tells a story, do not test "implementation"

You test behavior, not implementation (Method name is considered implementation)

19

Naming guidlines:

* "[ClassName]Tests" as the test class:
* (be plain & flexible): Don't follow a Rigid naming policy
* (tell a story): as if describing the test case to a non-programmer.
* (use_snake_case)

Delivery_with_invalid_date_should_be_considered_invalid()
vs
Delivery_with_a_past_date_is_invalid()

20

Distinguish the SUT in tests by naming it "sut"

An object being test in a unit test case should be named as "sut" reguardless its class name.

21

Distinguish the SUT in tests by naming it "sut"

An object being test in a unit test case should be named as "sut" reguardless its class name.

I.e.

Calculator sut = new Calculator();
int result = sut.add(1, 2);
Assert(result).should().be(3);

22

Four pillars of a good unit test

1. Protection against regressions (Complete test coverage)
2. Resistance to refactoring (Test behavior not Impl)
3. Fast feedback (Small, iterative)
4. Maintainability (Good code)

23

Code is a liability.
What the code does (value of the code) for you is an asset.

bad code has high liability that may out-weight its value (cut loss and throw away)

A torn house is a liability, it must be fixed before it is an asset.

24

Four pillars of a good unit test

1. Protection against regressions (tests should have good coverage, especially around domain centric code)
** Do test everything. Trivial code such as Getter and Setters need not be tested.
2. Resistance to refactoring (Test behavior not Impl, Test should still pass if impl changes)
3. Fast feedback (Small, iterative)
4. Maintainability (Good code)

25

Four pillars of a good unit test
:
Regression
Refactoring
Agile|fast|small
Maintainable

Trade off between Regression vs Fast

Developer hourly tests focus on fast
Nightly test focus on regression (more thorough/more end2end tests)

All test should be resistant to refactoring (because you have to test against behavior, not impl) and maintainable.

1. Protection against regressions (tests should have good coverage, especially around domain centric code)
** Do test everything. Trivial code such as Getter and Setters need not be tested.

2. Resistance to refactoring (Test observable behavior not Impl, Test should still pass if impl changes. Check "end result" not "steps" of a method.

3. Fast feedback (Small, iterative)
4. Maintainability (Good code)

26

Four pillars of a good unit test
:
Regression
Refactoring
Agile|fast|small
Maintainable

Trade off between Regression vs Fast

Developer hourly tests focus on fast
Nightly test focus on regression (more thorough/more end2end tests)

All test should be resistant to refactoring (because you have to test against behavior, not impl) and maintainable.

1. Protection against regressions (tests should have good coverage, especially around domain centric code)
** Do test everything. Trivial code such as Getter and Setters need not be tested.

2. Resistance to refactoring (Test observable behavior not Impl, Test should still pass if impl changes. Check "end result" not "steps" of a method.

3. Fast feedback (Small, iterative)
4. Maintainability (Easy to understand, Easy to run)
* The fewer out-of-process dependency/collaborators the easier to (setup and) run the tests, the easier it is easy to maintain.

27

Mocking is bad?

mocks often result in fragile tests—tests that lack the metric of resistance to refactoring. But there are still cases where mocking is applicable and even preferable.

E.g. in the 4 pillars (Regression, Refactor, fast, Maintainability), Mock can improve score on fast and maintainability
1) replace out-of-process collaborators (e.g.) database with mocks (fast, easy, isolation)
2) fragile, because the "dependencies of an implementation" is hardcoded in test

E.g. In getNameById() test, if you mock a DB, you implies the Name is stored in a database, in this case the test case is not written to behavior (get the Name, regardless if the name is stored in db or on local file), but written to impl (mock the database, but if I switch to use local file as storage, the test will break)

28

Mocking is bad? It violates Resistance to Refactoring

When asserting on Mock() that mocks internal dependencies:
1) you assert that Mock().method() is called N times. Call Invocation verification is an implementation detail, not a "observable end result", this this type of test violates "Resistance to Refactoring" (Test against impl that is not observable)

mocks often result in fragile tests—tests that lack the metric of resistance to refactoring. But there are still cases where mocking is applicable and even preferable (e.g. in Integration Test).

E.g. in the 4 pillars (Regression, Refactor, fast, Maintainability), Mock can improve score on fast
1) replace out-of-process collaborators (e.g. in integration testing) database with mocks (fast, isolation)
2) fragile, because the "dependencies of an implementation" is hardcoded in test

29

* Mock vs Stub

You can assert on Mock (side-effect/write has happened)
Don't assert on Stub (Read must have happened otherwise the SUT must have failed due to lack of input from stub)

Mock mocks outgoing interactions to its dependencies that changes states of dependencies (like WRITE a database)

Stub stubs incoming interactions from its dependencies (i.e the READ from the database)

D


stub a read ==> provide fake value
mock a write ==> write to file vs actual database

30

To Mock or to Stub?

CQRS Command Query Separation (CQRS)

In a test case, the test can invoke either a command or a query to access services of its dependencies

Command: method that Return void with side effects (like HTTP POST, side effect on each call)

Query (idempotent) : method that Return value without side effect (like HTTP GET or PUT)

A command is doubled by a mock
A query is doubled by a stub