Would You Rather ____ ?

Would You Rather…


Our family sometimes plays a game after dinner called “Would You Rather”.  It was introduced to us by “Uncle Kent”, and it goes something like this:  Someone starts by asking another person at the table a question.  “Would you rather X or Y” (where X and Y are two different options.)  For instance, I might say to my wife “Would you rather go on a vacation to Rome or Paris?”  My wife would then answer.  We’d probably all chat about what she said.  Then it would be her turn.  Uncle Kent’s questions usually goes something like this…  “Would you rather eat rusty nails or worms?”  That’s Uncle Kent, for ya!

So…  let’s play a game of “Would You Rather…” right now.  Dear reader, in your next IoT project, would you rather support this python code running on a Raspberry Pi:

#!/usr/bin/env python
############################################################# This code uses the Beebotte API, you must have an account.# You can register here: http://beebotte.com/register############################################################import sys
import getopt
import httplib
import time
import Adafruit_DHT
from beebotte import *
import datetime
import RPi.GPIO as GPIO ## Import GPIO Libraryimport time ## Import 'time' library.  Allows us to use 'sleep'GPIO.cleanup()
### Replace API_KEY and SECRET_KEY with those of your accountbbt = BBT('a2d79fbe39c3c231a7b2b84ffb105049', '7211c45499bf9cb6819b1d8a9776fc61e49e0fc9ec66ddaf2b6371e4571d49c7')
webapp = ""monitorName = ""period = 60 ## Sensor data reporting period (1 minute)pin = 4 ## Assuming the DHT11 sensor is connected to GPIO pin number 4
GPIO.setmode(GPIO.BOARD) ## Use BOARD pin numberingGPIO.setup(11, GPIO.OUT) ## Setup GPIO pin 11 to OUT
### Change channel name and resource names as suits youtemp_resource   = Resource(bbt, 'rasberrypi', 'temperature')
humid_resource  = Resource(bbt, 'rasberrypi', 'humidity')
iterations = 445speed = 1
def run():
    # Turn led off.  We'll turn it back on if everything works.    GPIO.output(11, False) ## Turn on GPIO pin 7    print "Arguments passed: {0} ".format(sys.argv)
    webapp = sys.argv[1]
    monitorName = sys.argv[2]
    ts = time.time()
    st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
    humidity, temperature = Adafruit_DHT.read_retry( Adafruit_DHT.DHT22, pin )
    if humidity is not None and temperature is not None:
        print "{2:s} Temp={0:f}*C  Humidity={1:f}%".format(temperature, humidity,st)
        try:
          #Send temperature to Beebotte          temp_resource.write(temperature)
          #Send humidity to Beebotte          humid_resource.write(humidity)
          #Send it all to aws          sendWeb(webapp, monitorName, temperature, humidity)
          #  Turn light on to indicate success           GPIO.output(11, True) ## Turn on GPIO pin 7
        except Exception as e:
          ## Process exception here          s = str(e)
          print "Error while writing to Beebotte {0}".format(s)
    else:
        print "Failed to get reading. Try again!"
def sendWeb(webapp, monitorName, temperature, humidity):
   try:
     print "Send request to {0} monitor name {1}".format(webapp, monitorName)
     headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
     conn = httplib.HTTPConnection(webapp)
     conn.request("POST", "/dhtmonitor/temperature/{0}/humidity/{1}/sensor/{2}".format(temperature, humidity, monitorName), "", headers)
     response = conn.getresponse()
     print "dhtmonitor Server Response {0} {1}".format(response.status, response.reason)
   except Exception, e:
      print e

run()

Or this python code running on a Raspberry Pi:


#!/usr/bin/env pythonimport sys
import DhtHardware
import DhtNetwork
import DhtUtils

webapp = ""monitorName = ""

def run():
    print "Arguments passed: {0} ".format(sys.argv)
    webapp = sys.argv[1]
    monitorName = sys.argv[2]
    hardware = DhtHardware.DhtHardware()
    network = DhtNetwork.DhtNetwork()
    utils = DhtUtils.DhtUtils()
    hardware.initialize()

    humidity, temperature = hardware.readDhtValues()
    if humidity is not None and temperature is not None:
        print "Reading taken.  Sending values."
        url = utils.GetUrl(monitorName, temperature, humidity)
        network.sendWeb(webapp, url)
        network.sendBBT(humidity, temperature)
    else:
        print "Failed to get reading. Try again!"
run()

I know which one I’d pick!  If you said the second one, then you are not crazy.  That first one is a mess.  Why is it such a mess?  Because I hacked it together without any regard for doing things correctly.  I was “just trying to get it to work.”  That is fine as a learning experiment…  but it hardly qualifies as engineering “craftsmanship”.  (http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)

So, how did I get to the second one?

It all started when I decided that I should write a unit test.

I talked about testing in the previous post.  There are a number of different kinds of tests that engineers can write.  The simplest one is unit tests.  Here are few things that are important to keep in mind about unit testing:

1.  Unit tests validate the behavior of a very small amount of code.

2.  Unit tests are not intended to run on the target system.

3.  Lots of little unit tests is a great rule of thumb.

4.  It probably does NOT make sense to write a unit test for every line of code in your app.  (Have no fear, we will test all the lines…  we just won’t do all the testing exclusively with unit tests.)

When I approached the problem of writing unit tests for the code in the first option above, I had to decide how to break down the problem.  There is a bunch of code in that first example that will ONLY run on the Raspberry Pi.  Also, there is other code that sends messages over the internet.  It is not necessary to unit test the code that depends on the Raspberry Pi.  That code is provided by a third party (and hopefully unit tested.)  Furthermore, if I intend to run these tests as part of an automated build process, the tests will probably NOT be running on a Raspberry Pi at some point.  In the case of the networking code, again that code is provided by a third party.  Also, to effectively test that code, I’d need to actually send a message, and then check and see if the message was received and was correct.  That would be a great functional test…  but it is too big and crosses too many boundaries to really be considered a unit test.

There is, however, one part of the program that makes sense to test with a unit test.  The code needs to get measurements from the DHT22 thru some libraries, and then reformat that information into a URL so that it can be sent over the internet.  In order to test that code, I need to get the formatting code out of the main python script.  I need to do this so that it is decoupled from the code that depends on the hardware and networking specific libraries.  The reason is simple.  I am writing the code in a python development environment that is not running on a Raspberry Pi.

Here is the line of code in the first example that includes the reformatting that I want to test.

 conn.request("POST", "/dhtmonitor/temperature/{0}/humidity/{1}/sensor/{2}".format(temperature, humidity, monitorName), "", headers)

The code is buried inside of code that makes the network call.  As I mentioned, I don’t want to test the network call right now.  Let’s try and write the test and see if we can figure out a way to get things re-organized to make this work.  Here is a test for that reformatting functionality:

def test_format_url(self):
    utils = DhtUtils.DhtUtils()
    req = utils.GetUrl("sensor1", 20.20, 10.10)

self.assertEqual(“/dhtmonitor/temperature/20.2/humidity/10.1/sensor/sensor1”, req)

Here is some code that can work with that test…

class DhtUtils:

   def GetUrl(self, sensor, temperature, humidity):
       return  "/dhtmonitor/temperature/{0}/humidity/{1}/sensor/{2}".format(temperature, humidity, sensor)

Lastly, I can use this code in the main python script for the program like this…

url = utils.GetUrl(monitorName, temperature, humidity)

Writing this simple test has had an interesting impact on my code…   It has started a process of refactoring out bits of code that are functionally related.  This process of grouping like code together is referred to as decoupling.  If you remember back to the good ‘ole days of computer programming, decoupled and highly cohesive code is generally a good thing.  Ward Cunningham does an excellent job of delving into the ideas of coupling and cohesion in this article:  http://c2.com/cgi/wiki?CouplingAndCohesion

The whole point of this rather technical post is that by adding a single unit test to a big nasty bunch of code (the first option in the would-you-rather question), I ended up with a newly structured set of code that is going to be much easier to maintain.  The exercise gave me much greater confidence in my code actually working.  And, lastly, I found a BUNCH of other bugs, dead code, and other issues  in the process of refactoring the code so I could write a test.

Now, you may be thinking something along the lines of “That’s great, but that was python running on a Raspberry Pi.  What about Arduino or ____ ?”  I believe that the principles still apply to those other platforms.  In terms of Arduino, the language is a bit different.  We will need to probably write our Arduino code in C++ (and not use the Arduino IDE), and use a framework like “CUTE” or “gtest” for our testing framework.  We would need to be creative about how we organize our code for the Arduino platform.  I do believe that the process would be completely worth it in the end.

At your next dinner party, if you find a lull after the main course and before the dessert, feel free to ask the person next to you…  “On your next IoT project, would you rather support a big monolithic tightly coupled mess of code, or write some unit tests and end up supporting smaller, easier to understand modules that you have a high level of confidence in because you tested them?”

Ok.  Maybe you won’t do that at your next dinner party.  I do hope this post will encourage you to consider trying to write some unit tests for your next IoT project.

Author: Miles Porter

Miles Porter is a senior consulting software engineer in the Minneapolis, MN. He is interested in pragmatic software development practices, embedded software, and cloud computing. In his free time he likes to travel with his family, play upright and electric basses and study Taekwondo.

Leave a comment