Test development tips, tricks, and hints

While we continue to explore the theory and practice of testing Backbone.js applications, it remains true that creating test architectures and writing good test specifications are more of an art than an exact science. Many lessons can only be learned through experience, particularly as your applications encounter bugs and development mishaps. In the meantime, we can start you off with some techniques and suggestions.

Isolating and excluding tests

Application development is a journey guaranteed to include inexplicable errors, sudden application crashes, and complex test failures. When these pitfalls happen, it is important to have some directions on how to debug the problems and move things forward.

A common scenario during software development is when application changes break one or more existing unit tests. In this case, a good practice is to run the test suite one test at a time, fix the test, and then move on to the rest. Mocha provides two avenues to help in this regard:

  • Grep: As we have discussed in Chapter 1, Setting Up a Test Infrastructure, you can click on a single test in the test reporter HTML page or directly navigate to a test page URL with a grep query parameter appended, such as test.html?grep=PATTERN.
  • Only: Another alternative is to temporarily modify your Mocha test specifications to run only a single test using the only helper, skipping all other tests and suites. Let's look at an example:
    it("doesn't run this test", function () {
      expect(true).to.be.true;
    });
    
    it.only("runs this test", function () {
      expect(false).to.be.false;
    });

On the other side of this scenario, sometimes we want to ignore a few failing tests while still using the rest of the test infrastructure. In this case, we look towards skip.

  • Skip: The skip modifier omits a single specification from the test run and can be applied to multiple specifications. Skipped tests are also considered pending and can be visually distinguished in a Mocha HTML test report:
    it.skip("doesn't run this test", function () {
      expect(true).to.be.true;
    });
    
    it("runs this test", function () {
      expect(false).to.be.false;
    });

Writing testable application code

Beyond the practical aspects of writing tests, an equally important component of developing a test infrastructure is writing testable application code. The topic of testable JavaScript code is quite large—we will only introduce the issue here and start you off with a general goal of developing application code that works in harmony with the tests that support it.

A comprehensive treatment of the subject is available in the book Testable JavaScript by Mark Ethan Trostler (http://shop.oreilly.com/product/0636920024699.do), which covers topics such as application code complexity, event-based architectures, and debugging. Also consider general JavaScript application guides such as Maintainable JavaScript by Nicholas Zakas (http://shop.oreilly.com/product/0636920025245.do) and the seminal JavaScript: The Good Parts by Douglas Crockford (http://shop.oreilly.com/product/9780596517748.do).

Some Backbone.js application development hints and good practices for testable code include:

  • Decouple components and limit dependencies: Many Backbone.js components have optional dependencies on other components. For example, a Backbone.js view can optionally have a model declared in the view class (for example, model: Foo) or a model object can be passed to a view on instantiation (for example, new View({model: foo})). The latter technique often opens up more opportunities for injecting mocks or test-friendly models into the view code. The same logic also applies to the el property in Backbone.js views—it is often more test-friendly to provide values via a view object instance than in the view class definition.
  • Isolate configuration information: Any pure configuration data should get its own application file and facilitate the overriding of specific configurations. Canonical examples include the backend server host and port information, logging levels, and in the case of the Notes application, the name of the localStorage datastore. The previous examples that override notes/app/js/app/config.js provide a good introduction on how to both create a configuration file and supersede values for testing purposes.
  • Decompose large functions: Monolithic functions that try to do everything are often difficult to test. Break up large functions into smaller ones, test them, and then aggregate the smaller functions into your application.
  • Avoid hidden state: Using techniques such as closures and anonymous functions, JavaScript permits classes and code to have a state that is unchangeable and inaccessible to other parts of the application and tests. For example, if a class has an internal counter, make it a member variable and not a closure-wrapped variable. While this is something of a debated topic, it is generally preferred to expose some amount of internal state for test (and application) use. At the same time, our tests should focus on the application behavior and avoid deliberately using a internal state that is not part of the overall expected functioning of the application.

Please note that these tips are heuristics and not hard and fast rules. Many development situations will favor doing exactly the opposite of one or more of these suggestions. Hopefully, some of these guidelines will help make your early application decisions easier to live with as your application and test code bases grow over time.

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

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