Creating clear hierarchies

To test any code base, we would likely need to write a large number of assertions. Theoretically, we could have a long list of assertions and nothing else. However, doing this may make it quite difficult to read, write, and analyze the reports of tests. To prevent such confusion, it is common for testing libraries to provide some scaffolding abstractions around assertions. For example, BDD-flavoured libraries such as Jasmine and Jest supply two pieces of scaffolding: the it block and the describe block. These are just functions to which we pass a description and callback, but together, they enable a hierarchical tree of tests that makes it far easier to comprehend what's going on. Testing a sum function using this pattern might be done like so:

// A singular test or "spec":
describe('sum()', () => {
it('adds two numbers together', () => {
expect( sum(8, 9) ).toEqual( 17 );
});
});

Behaviour-Driven Development (BDD) is a style and methodology of testing that, similar to TDD, enforces a regime where we write tests first and implementation second. More than this, however, it focuses on the importance of behaviors over implementation, since behaviors are easier to communicate and are more important from the perspective of the user (or stakeholder). BDD-style tests will hence usually use language such as Describe X » It does Y when Z occurs...

Non-BDD libraries tend to surround groups of assertions with simpler infinitely-nestable test blocks, like so:

test('sum()', () => {
test('addition works correctly', () => {
assert(sum(8, 9) == 17, '8 + 9 is equal to 17');
});
});

As you can see, the naming of the BDD-flavored it and describe terms can help us to craft descriptions for our test suites that read like full English sentences (for example Describe an apple » It is round and sweet). This isn't enforced but gives us a useful nudge toward better descriptions. We can also infinitely nest describe blocks so that our descriptions can reflect the hierarchical nature of the thing we're testing. So, for example, if we were testing a math utility called myMathLib, we may imagine the following test suite with its various sub-suites and specifications:

  • Describe myMathLib:
    • Describe add():
      • It can add two integers
      • It can add two fractions
      • It returns NaN for non-numeric inputs
    • Describe subtract()l:
      • It can subtract two integers
      • It can subtract two fractions
      • It returns NaN for non-numeric inputs
    • Describe PI:
      • It is equal to PI at fifteen decimal places

This hierarchy naturally reflects the conceptual hierarchy of the abstraction we're testing. The reporting provided by the testing library will usefully reflect this hierarchy. Here's an example output from the Mocha testing library in which every test of myMathLib passes successfully:

myMathLib
add()
✓ can add two integers
✓ can add two fractions
✓ returns NaN for non-numeric inputs
subtract()
✓ can subtract two integers
✓ can subtract two fractions
✓ returns NaN for non-numeric inputs
PI
✓ is equal to PI at fifteen decimal places

Individual assertions come together to form tests. Individual tests come together to form test suites. Every test suite provides us with clarity and confidence regarding specific units, integrations, or flows (within E2E tests). The composition of these test suites is vital to ensuring that our tests are simple and comprehensible. We must take the time to think about how we will express the conceptual hierarchy of whatever we're testing. The test suites we create also need to be intuitively placed within the directory structure of our code base. This is what we'll explore next.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.214.60