Building an application with testing in mind

So, we now know that testing is important and we've also got to know about the different kinds of testing. But are there any important things that we need to do while building our application so that we are able to test it properly?

The answer to this question is a bit complicated. Although we can easily write the code in any particular manner we desire, and subject the code to testing through a number of procedures, for example, unit testing, it is still better to follow a general set of guidelines so that the code can be tested easily and efficiently. So, let's go ahead and take a look at the guidelines:

  • Every component should have one responsibility: For testing to be efficient and to cover all the possible code paths, every component should play a single responsibility. For example, take a look at the following code:
    def encrypt_password(password):
"""Encrypt the provided password.

Keyword arguments:
Password – The password to be encrypted

Returns:
Tuple
"""

salt = secrets.token_bytes(8)
passwd_hash = hashlib.pbkdf2_hmac('sha256', password.encode('utf 8'), salt, 10000).hex()
return (passwd_hash, salt)

In this code, we can see that the method is responsible for playing two different responsibilities of first generating a random salt that is used to pad the password with, and then generate a new password hash for the supplied password.
This kind of method can be tested using unit tests, which is hard because of the multiple roles it is playing. Changing even a single statement in the method can result in multiple unit tests having to be modified.

  • Avoid non-determinism: Going with the previous example, our method generates a random salt in every iteration, which is independent of the inputs we have provided to the method. This results in non-deterministic behavior, which might be hard to test because the output of the method might be different, even for the same kind of inputs. To have successful unit tests in place, avoiding non-determinism is important.
  • Avoid global state: While writing a particular component, do not make it depend on a global state. When a global state is used to coordinate between the different components, it creates a mess during the testing because this state needs to be maintained for all the unit tests to execute successfully. This kind of problem usually defeats the purpose of unit testing, which aims to test the components in isolation from each other without any kind of coordination between them.
  • Avoid inserting too much logic in constructors: When programming in an object-oriented language, constructors are called when the objects of a particular class need to be instantiated. The constructors are responsible for setting up the object instance and initializing it with any required data. While testing the method of a class, every test will instantiate a new object of the class. If your constructors are responsible for doing anything other than setting up the state of the newly constructed object, all these settings will need to be tracked in all the test cases, making the tests much more complicated.

So, we need to take a look at some of the tips that we can follow to make our code testable. So, let's go back to our previous example and see what can we do to make the method testable, for example:

def encrypt_password(password, salt):
"""Encrypt the provided password.

Keyword arguments:
password: The password to be encrypted
salt: The salt to be used for padding the password

Returns:
String
"""
passwd_hash = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 10000).hex()
return passwd_hash

In this example, we modified our encrypt_password() method a little bit. Our modification involved removing the logic for salt generation out of the method and replacing it with ingesting a salt through parameters. With this, we removed two problems from our method. The first of these problems was the same method having more than one responsibility—the generation of a salt and generating a hash of the password. The second problem was of non-determinism, where the salt generated could be different in every iteration of the test.

These were a few rules that if followed can not only make the process of testing easy, but also much more useful, because we are easily able to test the different edge cases of a particular component with quite some ease. During the development of some large scale applications, some of the teams even follow the principles of test-driven development (TDD).

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

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