Code smell: long, procedural background jobs (and how to fix it)

by Jason Swett,

I’ve worked on a handful of Rails applications that have pretty decent test coverage in terms of model specs and feature specs but are lacking in tests for background jobs. It seems that developers often find background jobs more difficult to test than Active Record models—otherwise the test coverage would presumably be better in that area. I’ve found background jobs of 100+ lines in length with no tests, even though the same application’s Active Record models had pretty good test coverage.

I’m not sure why this is. My suspicion is that background job code is somehow viewed as “separate” from the application code. For some reason the background job code doesn’t need tests, and for some reason the background job code wouldn’t benefit from an object-oriented structure like the rest of the application.

Whatever the root cause of this issue, the fix is fortunately often pretty simple. We can apply the extract class refactoring technique to turn the long procedure background job into one or more neatly abstracted classes.

Just as I would when embarking on the risky task of refactoring any piece of untested legacy code, I would use characterization testing to help me make sure I’m preserving the original behavior of the background job code as I move it into its own class.

  •  
  •  
  •  
  •  

One thought on “Code smell: long, procedural background jobs (and how to fix it)

  1. Ben

    Thanks for writing this. It’s a good jumping off point for thinking about service objects. I’ve bounced around between having separate service objects (Command pattern?) and using my ActiveJob class as the service object. I’ve gotten quite handy at manually invoking jobs (either MyJob.new.perform or using the ActiveJob test helpers) in tests, so for me, it’s come down to two things:

    1. Do I need to be aware of the job adapter/worker for some functionality or is it just some Ruby code I need to defer/offload?
    2. Do I also need to reuse that service object somewhere else, especially if I need to access the return value?

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *