Popular node unit testing frameworks emphasize both spying and mocking in unittests. Because so much IO regularly occurs in node projects, planning a strategy to mock on IO in unittests should be a high priority. When testing it's usually pretty clear what methods need to be spied on (any that make IO requests :) But if code isn't architected to be testable from the beginning, then spies, mocks and patches end up being applied in roundabout ways. Lately I've been seeing a common mistake made with spying on exported module objects. This blog posts assumes understanding of testing spies.
What are some ways to spy on exported module methods?
Assume we have a module resources.js. All of its methods are being exported for unittesting.
buildRequest is easily tested because it has no IO calls. The difficulty arises when we try to test getData and makeRequest. Our goal for this blog is to write a test that asserts getData calls makeRequest a single time with the correct arguments. Since makeRequest performs IO, and IO is a no-no for our unittests, we have to mock it in some way. Our fist attempt at doing so is:
In our test resources.js is required and then makeRequest is mocked and spied on, so the test can make assertions on it, and no IO is performed. The output of running the above test is provided, which results in an error. This is the initial attempt that I've been seeing very frequently. The issue is the spy is being created on the exported objects property and NOT the makeRequest defined in and used in resources.js.
To illustrate this; if resources.js were to use the object it is exporting in its calls, then the spy would be created and used as expected!!!
The above sample modifies resources.js to use the object it is exporting, which is the same object the spy is being applied to. The output of running the test in test.spec.js against the above resources.js file is shown. The test PASSES! (In the near future I plan on having a blog that shows, where, why, how objects are exported/required referencing node core code)
While the above works, I personally don't think it is very clear, (and haven't really seen modules that use module.exports in its function implementations. I also think it is dangerous to reference module.exports internally to a module because, clients of that module can mutate it!!!!
A similar way to mock makeRequest is to export a reference to an object used internally.
The above code, defines a utils object in resources.js and exports a reference to it. Because it is a reference, the test can be updated to spy on resources.utils.makeRequest and the utils object in resources.js will be mutated.
I think this way is cleaner than using module.exports directly, but is still susceptible to being mutated by a client!
Dependency injection is a strong tool for creating node code that can be easily tested; by providing a clean way to mock IO dependencies. It requires that the caller provides (injects) a functions dependencies. In this case getData depends on functions that perform IO, makeRequest. Refactoring it would require the caller of getData to provide a makeRequest function. This would seamlessly allow a test to provide a mocked makeRequest method (that doesn't make IO calls), and the actual code to provide a different makeRequest method (which does make IO calls).
getData now requires that a requestor method is provided by the client. The test code is free to provide a spy as the requestor, while the production clients will be required to provide makeRequest
getData(resource, resources.makeRequest, callback);
For the above example, getData only has a single dependency, while frequently in real world code methods may have multiple IO dependencies. While functions can usually be decomposed and refactored to achieve making a single call or two, doing so after code is already in production is dangerous. Because of how easy it is to create extremely nested node.js code it is very beneficial to design testable code from the start. A powerful tool to do this is dependency injection, something I plan on writing a lot more about very soon.
Keep testing, happy noding!!!
Showing posts with label software design. Show all posts
Showing posts with label software design. Show all posts
Sunday, November 29, 2015
Friday, September 21, 2012
Introduction to Unit Testing Using Python Unittest
Unit Testing is an extremely powerful tool. It directly helps to ensure the 3 aspects of good software: Verifiability, Maintainability, and Extensibility. Unit testing is as much a process of software design as it is a tool. There are many great tutorials on HOW to use python unittest, and of course python documentation is an excellent resource. I am going to focus on WHY to test your software. Below are a couple short unit testing examples using python's built in unittest package.
Verfiability
How does one verify that their program works? With unit testing we can create specific functions or groups of functions to target our code. For example:
We can easily create a test for this using python's built in unittest module. Tests are created by subclassing unittest.TestClass
On python 2.7+ running python -m unittest discover in our package will automatically search for all test.py files and run the TestClasses. There are a couple of things to note in the above example. All test classes must subclass unittest.TestClass. The focal point of any test is its assertions. The assertions are what dictate whether a test passes or fails. Although this is a contrived example it displays how easy it is to isolate our methods and control exactly what inputs they receive! If we were to create one test method (or more) for every method in our project we would quickly grow a test suite. When making ANY changes to our code it becomes trivial to run through every single function in our project and verify that nothing has broken! Imagine if we had a web app and every time we added a feature we would have to run through EVERY possible page/action!? It could take a long time doing it manually.
Maintainability
Bugs are a part of software development. It is extremely important to minimize bugs but when they do happen it is important to create fixes very quickly. Because bugs will occur in code it is important to have a process set up that helps to isolate the bug so that it is easy to reproduce, easy to correct and easy to verify the bug has been fixed. Unittesting helps to do all of these. Suppose the sum_numbers function is at the heart of a website. It gets all sorts of user input data, and occasionally some faulty data slips through. If a string is passed as one of the parameters it will result in a TypeError!! It is trivial to isolate and reproduce this bug. I guess we need a little thought about what should be returned if there is an invalid input. For this example lets return None. We then can create another test method like:
Running the above code reproduces the string error. Since we haven't fixed our code yet this test will fail. We can then change our sum_numbers method to handle a type error:
Running our test again will result in two passing tests. We successfully isolated the bug, reproduced the bug and verified the bug has been fixed!! We now also have a test trail assuring us the big has been addressed. Pretty cool.
Extensibility
With tests it becomes very easy to help an app grow. A test suite provides a safety net for an application. We can programatically run through every function of an app in a short amount of time. This could take hours to do manually. Some test suites can take hours to run, it wouldn't even be feasible to manually test a large codebase!! As long as we keep designing our apps in a modular unit based way we can easily add functions and tests for those individual functions. Another aspect of unittesting is how easy it is to refactor code. Suppose we had thought it was a good idea at the time to write our original function like:
Assuming we had the same test method as before:
This test is focused on the output of our function. It is assuring us the output is as expected. This allows us to easily change what is happening in our function and still have the test acting as a safety net. We can rewrite (refactor) the internals of our methods and guarentee they are still functioning in the way we originally tested them! Our method would pass the test because it is performing the action that we want it to. We could change the method to remove the list and sum function
We cleaned up our function and ensures that it functions in the way we designed it to!!
Testing is a powerful tool that should be very heavily considered. It helps verify our functions are working the way we intended them, help us easily maintain our applications and help us extend our applications. Correct aplication design will help us isolate our problems. Testing can take a significant amount of time, but the benefits it offers far outweigh any downsides.
Verfiability
How does one verify that their program works? With unit testing we can create specific functions or groups of functions to target our code. For example:
def sum_numbers(num_one, num_two): """return an integer, the sum of two numbers""" return num_one + num_two
We can easily create a test for this using python's built in unittest module. Tests are created by subclassing unittest.TestClass
from mymodule.functions import sum_numbers class TestFunctions(unittest.TestClass): def test_add_numbers_success(self): self.assertEqual(sum_numbers(2, 2), 4)
On python 2.7+ running python -m unittest discover in our package will automatically search for all test.py files and run the TestClasses. There are a couple of things to note in the above example. All test classes must subclass unittest.TestClass. The focal point of any test is its assertions. The assertions are what dictate whether a test passes or fails. Although this is a contrived example it displays how easy it is to isolate our methods and control exactly what inputs they receive! If we were to create one test method (or more) for every method in our project we would quickly grow a test suite. When making ANY changes to our code it becomes trivial to run through every single function in our project and verify that nothing has broken! Imagine if we had a web app and every time we added a feature we would have to run through EVERY possible page/action!? It could take a long time doing it manually.
Maintainability
Bugs are a part of software development. It is extremely important to minimize bugs but when they do happen it is important to create fixes very quickly. Because bugs will occur in code it is important to have a process set up that helps to isolate the bug so that it is easy to reproduce, easy to correct and easy to verify the bug has been fixed. Unittesting helps to do all of these. Suppose the sum_numbers function is at the heart of a website. It gets all sorts of user input data, and occasionally some faulty data slips through. If a string is passed as one of the parameters it will result in a TypeError!! It is trivial to isolate and reproduce this bug. I guess we need a little thought about what should be returned if there is an invalid input. For this example lets return None. We then can create another test method like:
class TestFunctions(unittest.TestClass): def test_add_numbers_success(self): self.assertEqual(sum_numbers(2, 2), 4)
def test_add_numbers_string_bug(self):
self.assertEqual(sum_numbers('a', 2), None)
Running the above code reproduces the string error. Since we haven't fixed our code yet this test will fail. We can then change our sum_numbers method to handle a type error:
def sum_numbers(num_one, num_two): """return an integer, the sum of two numbers, can't trust user input"""
try: return int(num_one) + int(num_two)
except ValueError:
return None
Running our test again will result in two passing tests. We successfully isolated the bug, reproduced the bug and verified the bug has been fixed!! We now also have a test trail assuring us the big has been addressed. Pretty cool.
Extensibility
With tests it becomes very easy to help an app grow. A test suite provides a safety net for an application. We can programatically run through every function of an app in a short amount of time. This could take hours to do manually. Some test suites can take hours to run, it wouldn't even be feasible to manually test a large codebase!! As long as we keep designing our apps in a modular unit based way we can easily add functions and tests for those individual functions. Another aspect of unittesting is how easy it is to refactor code. Suppose we had thought it was a good idea at the time to write our original function like:
def sum_numbers(num_one, num_two): """return an integer, the sum of two numbers""" return sum([num_one, num_two])
Assuming we had the same test method as before:
def test_add_numbers_success(self): self.assertEqual(sum_numbers(2, 2), 4)
This test is focused on the output of our function. It is assuring us the output is as expected. This allows us to easily change what is happening in our function and still have the test acting as a safety net. We can rewrite (refactor) the internals of our methods and guarentee they are still functioning in the way we originally tested them! Our method would pass the test because it is performing the action that we want it to. We could change the method to remove the list and sum function
def sum_numbers(num_one, num_two): """return an integer, the sum of two numbers""" return num_one + num_two
We cleaned up our function and ensures that it functions in the way we designed it to!!
Testing is a powerful tool that should be very heavily considered. It helps verify our functions are working the way we intended them, help us easily maintain our applications and help us extend our applications. Correct aplication design will help us isolate our problems. Testing can take a significant amount of time, but the benefits it offers far outweigh any downsides.
Sunday, September 9, 2012
Why Unit Testing is important
One of the most controversial topics in programming is Unit Testing, or testing in generally. There are a number of strong arguments on both sides of the issue.
For the past 10 months I have been freelancing. During this time I have been exposed to a wide variety of code created by many individuals of vastly varying skill levels. All of these projects have been php websites and webapps. Of course this had led to tons and tons of different architectures. All of these projects have had one troubling thing in common: The projects were created with absolutely no thought about maintainability. This is, in part, because of the industry. Boutiques and contractors are not maintaining the app. The goal is to ship a working product in as little time as possible. Unit testing code doesn't play into this because the time investment involved. A significant time investment is required on determining testing strategy and writing the actual tests. I have had many instances where writing tests takes AS LONG AS writing my actual functions!!! Having to budget in up to 50% more time to write tests is undesirable for every party involved.
This test-less, unmaintainalbe strategy actually works pretty well (it is an industry standard) as long as the sites never need features added. During a 3 month contract with a php boutique we had a number of recurring contracts. This involved maintaining web sites which were created 5-10 years ago. Many of these sites were from a different era, relying on registered_globals, and completely prone to sql injection. So the solution is simple right? Fix the security holes, add new features, and deploy!? No. Many of the sites did not have any sort of structure to the programs. Each file generally had 1 or no functions and hundreds of lines of code. Fixing bugs meant wading through lots of code that was more or less unrelated to the problems. Why not fix this code soup? The issues are there are set deadlines. People don't see refactoring the whole site into something that can be maintained as a good usage of time.
Writing code as if it were going to be unit tested will resolve many issues. Unit testing is important because it helps us think in terms of maintaining code. The most important aspects of unit testing are usually overlooked:
Thinking about program structure. This NEEDS to be thought about. Even for small websites. Sitting down and writing whatever comes to ones head is a sure way to reduce the quality of code and to remain a mediocre programmer.
Designing units, what are logical sections
I think a simple way to do this is to go through long code and comment what each section does. For example
When doing this it becomes very very apparent what should comprise a "section". This would makes sense to have a login_user function. Or a check_permission function. Even if they are only used one time, it still makes sense to create functions for these. This helps with the next point.
Thinking in terms of maintaining code.
- how willl this code be added to?
Actually thinking about program design will make maintaining and extending your code so much easier. Say that in addition to the facebook login that version 1 of the site uses a client wants to add twitter auth too. With a login_user function this is pretty easy. All login code can be located in this function.
Learning to create software as a series or related units takes practice, but it pays off in the long run as code is easier to work on and easier to extend.
For the past 10 months I have been freelancing. During this time I have been exposed to a wide variety of code created by many individuals of vastly varying skill levels. All of these projects have been php websites and webapps. Of course this had led to tons and tons of different architectures. All of these projects have had one troubling thing in common: The projects were created with absolutely no thought about maintainability. This is, in part, because of the industry. Boutiques and contractors are not maintaining the app. The goal is to ship a working product in as little time as possible. Unit testing code doesn't play into this because the time investment involved. A significant time investment is required on determining testing strategy and writing the actual tests. I have had many instances where writing tests takes AS LONG AS writing my actual functions!!! Having to budget in up to 50% more time to write tests is undesirable for every party involved.
This test-less, unmaintainalbe strategy actually works pretty well (it is an industry standard) as long as the sites never need features added. During a 3 month contract with a php boutique we had a number of recurring contracts. This involved maintaining web sites which were created 5-10 years ago. Many of these sites were from a different era, relying on registered_globals, and completely prone to sql injection. So the solution is simple right? Fix the security holes, add new features, and deploy!? No. Many of the sites did not have any sort of structure to the programs. Each file generally had 1 or no functions and hundreds of lines of code. Fixing bugs meant wading through lots of code that was more or less unrelated to the problems. Why not fix this code soup? The issues are there are set deadlines. People don't see refactoring the whole site into something that can be maintained as a good usage of time.
Writing code as if it were going to be unit tested will resolve many issues. Unit testing is important because it helps us think in terms of maintaining code. The most important aspects of unit testing are usually overlooked:
Thinking about program structure. This NEEDS to be thought about. Even for small websites. Sitting down and writing whatever comes to ones head is a sure way to reduce the quality of code and to remain a mediocre programmer.
Designing units, what are logical sections
I think a simple way to do this is to go through long code and comment what each section does. For example
// log in a user
// check user permissions
// get friends of user
// etc
When doing this it becomes very very apparent what should comprise a "section". This would makes sense to have a login_user function. Or a check_permission function. Even if they are only used one time, it still makes sense to create functions for these. This helps with the next point.
Thinking in terms of maintaining code.
- how willl this code be added to?
Actually thinking about program design will make maintaining and extending your code so much easier. Say that in addition to the facebook login that version 1 of the site uses a client wants to add twitter auth too. With a login_user function this is pretty easy. All login code can be located in this function.
Learning to create software as a series or related units takes practice, but it pays off in the long run as code is easier to work on and easier to extend.
Subscribe to:
Posts (Atom)