We have all bought into the idea of writing tests for our code. But are we writing our code in a way that make our tests better?
The full deck from Andrew Trebble's DrupalCamp Ottawa Presentation July 2016.
2. HOW TO WRITE TESTABLE CODE
What we will discuss today…
• What we won’t talk about
• Some terminology
• Single Responsibility
• Open/Closed
• Liskov Substitution Principle
• Interface Segregation
• Dependency Inversion
• Review
• Questions
4. • Why to test
• How to write tests
• What to test
• How to integrate tests into your workflow
• How to do Object Oriented Programming
WHAT WE WONT TALK ABOUT
7. • Class
• Instance
• Inheritance
• Base class
• Abstract class
• Concrete class
• Interface
OBJECT ORIENTED PROGRAMMING TERMS
8. Unit Tests
• Tests a single class in complete isolation
• All inputs are fake
Integration Tests
• Test the components together to make sure they play well with each other
• Test the interaction of your code with other parts of the system
Functional Tests
• Test to make sure the system actually does what it is supposed to do
• Run with a full system behind it
TYPES OF TESTS
9. What does that mean?
• Your objects should do only one thing
• It should be the only object in the code base that does that one job
S.O.L.I.D: SINGLE RESPONSIBILITY
10. Why?
• Reduces code complexity
• Ensures that bugs are isolated and easier to find and fix
• Reduces the number of tests that you need to write to cover the class
S.O.L.I.D: SINGLE RESPONSIBILITY
11. Open? Drupal’s already open isn’t it?
• Not that kind of open
• Open to extension
• Closed to change
S.O.L.I.D: OPEN / CLOSED
12. Why go to the trouble
• Reduce the number of regressions you experience
• Makes the code more mockable
• No need to rewrite tests to account for the new functionality
S.O.L.I.D: OPEN / CLOSED
13. Oh yeah, of course! Goes without saying… What’s the Liskov Substitution Principle?
• First proposed by Barbra Liskov of MIT in the late 80’s
• States that in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of
type S without altering any of the desirable properties of that program
• Makes code more resilient to changes in it’s dependencies
S.O.L.I.D: LISKOV SUBSTITUTION PRINCIPLE
14. Makes sense. Can you expand on why I need this, please?
• Makes your classes independent of the implementations of their dependencies
• Makes mocking possible
S.O.L.I.D: LISKOV SUBSTITUTION PRINCIPLE
15. I’m getting tired of trying to come up with ways to say “What’s that”?
• A class should have as few methods as is feasible to implement its interface
• More small interfaces instead of few big interfaces
S.O.L.I.D: INTERFACE SEGREGATION
16. Once again, Why?
• Smaller interfaces are easier to write tests for.
• Smaller interfaces have less reason to change
S.O.L.I.D: INTERFACE SEGREGATION
17. Please tell me more
• The complement of the Liskov Substitution Principle
• Dependencies should be abstract base classes or interfaces. Never a concrete implementation of one of those
• Forces loose coupling
S.O.L.I.D: DEPENDENCY INVERSION
18. Go ahead and inform me of the why
• Loose coupling helps enforce dependency injection
• Makes your classes independent of the exact types of its dependencies
• Makes mocking possible through dependency injection
S.O.L.I.D: DEPENDENCY INVERSION
19. Dependency Injection
• Make your classes dependent on abstract base classes or interfaces
• Don’t ever instantiate another object inside of your object
• Don’t rely on anything from outside your class unless it was passed in
• Pass in all dependencies as instantiated objects
• Your hooks will basically become just a bunch of setup that then pass to a object to do the
heavy lifting
REVIEW
20. Mocking
• Write mock classes that implement the same interface or extend the same base class.
• Mock objects do not do anything. They only pretend to.
• Mock objects return the data that will put your class through it’s paces. You may need multiple tests to ensure all
code paths are executed and tested.
REVIEW
21. • Monthly meetup
• First Wednesday of every month
• From 6pm to 8pm
• New and exciting topics
• Expert speakers and presenters from the community
• Meet new people passionate about Drupal
• Get support from the local community
• FREE pop, beer & pizza
DRUPALYOW
22. OPIN Software is the company behind The Open Mic
Podcast, which is released bi-weekly and discusses
anything & everything Drupal!
We will be hosting a live podcast today at 4pm, in the
business track room, and welcome everyone to attend and
contribute by discussing what you learned today and what
you are hoping for from the community in the future.
To listen to The Open Mic Podcast, please visit:
http://theopenmic.libsyn.com/
THE OPEN MIC PODCAST
23. OPIN Software is offering free Drupal 8 training sessions to
the attendees of DrupalCamp Ottawa. Sessions will
include:
• Drupal 8 Fundamentals
• Drupal 8 Module Development
• Drupal 8 Theming
If you are interested, please visit the OPIN booth upstairs.
FREE DRUPAL 8 TRAINING
Hello all and welcome to my talk on how to write testable code.
Today we will start with a quick introduction then move on to talking about what we will not be talking about. Then we will move on to each letter in the SOLID acronym. Followed by a review of the two major outcomes of the SOLID principles. And then some Q&A.
My name is Andrew Trebble and I am a developer. I have been in the web development industry for a long time. Too long. Lets just say that if I had had a kid when I first started working on websites professionally, that kid would be taking a year off university to find himself while backpacking across Europe by now.
I currently work for OPIN Software as a senior developer and resident metal head. MAIDEN RULES!
I also swear like a drunken sailor. I’ll be on my best behavior for you all today but if I slip up I give you all permission to heckle me.
So to start this talk off with a bunch of negativity, these are the tings I will not be talking about. I am assuming you have all already drank the testing kool-aid so I’m not going to try to impress on you how important testing is. I’m going to talk about a philosophy that will help you to write code that can be easily, quickly, and accurately tested.
I will also not be talking about the technical details behind OOP. I know a lot of Drupal developers are not all that down with objects but they really do make writing testable code much simpler. In fact it’s pretty much required for dependency injection and mocking. Besides D8 is much more OO than D7 and future releases are not going to go backwards to procedural programming. I highly recommend that you learn the ins and outs of OOP. Seriously. Learn it. Anyone disagree? No? Good.
SOLID. Catchy acronym, huh?
What is it? Glad you asked.
Solid is a set of principles that if you follow them your code will be much more testable, expandable, and resistant to regression errors. I want to point out that these are not in a do this first then this type order. Instead they all to be considered and done at the same time. Unfortunately for me, following the acronym means I will be talking about a few things in reverse from the order that they are usually talked about. That said, let’s go through each letter to see what’s up.
By the way I figured I am going to do something totally radical and not show any actual code. Weird, huh.
But before we get started I want to cover some terminology
Before I get started on the SOLID philosophy I want to lay out a little bit of terminology. First the OOP terminology
Class – a description of an object. Lists the member variables and methods. Important to remember that this is just the code representation of an object.
Instance – When a class is turned into an object in a running program. Several objects can share the same class.
Inheritance – A class that uses another class as it’s own starting point is called an inherited class.
Base class – A class that is used as a basis for creating other classes.
Abstract class – A class definition that is only used to describe a base class it can never be instantiated. It is used solely as a descriptor that other classes can use as a jumping off point
Concrete class – A class that is fully defined and can be instantiated
Interface – A ‘contract’ of sorts that lists the functions that a class must have. This allows objects classes that have the same interface to be swapped out for each other
Before I get started on the SOLID philosophy I want to lay out a little bit of terminology. I want to talk about the difference between unit, integration, and functional tests. The methodologies I will be talking about lend themselves to unit testing and will be the bedrock upon which a full testing methodology can be built up for your projects.
Unit tests are tests that test a class or component in complete isolation. All inputs are falsified (mocked) to isolate the class from the outside world. The mocked inputs send the class a series of fake data to test it’s response to different conditions.
Integration tests are for testing to make sure your components can talk to each other correctly. Yeah your class may pass all it’s unit tests but when it comes time to actually interacting with the system it may fall flat on it’s face. Maybe the you built it on a Mac OS computer with a file system that is not case sensitive but running on a Linux server with a case sensitive file system will cause it to fail. Unit tests cannot expose this sort of problem.
Functional test are concerned with the operation of the system as a whole. When I submit this form, are the contents actually saved to the database?
Single responsibility means that you write objects that have one job, one job only, and they do that job well. Now I don’t mean that you write classes that have one member function. What I mean is that your classes each have total domain over one specific aspect of your application.
And to extend that idea, your class is the only place in the entire code base where anything involving that aspect is performed.
For example lets say you are writing a class that acts as an interface to a remote server for fetching some sort of data that is used throughout your application.
The first bullet point states that the class does this one job and nothing else. Likely operations that the class can do are things like opening a connection; closing a connection; issuing query to server; and receiving results. Things that it wont do would be caching data sets; parsing queries; transforming returned data into a php array; error reporting; etc.
The second bullet point states that this class is the only way your code has to interact with the service. Even if it would be a couple of lines to issue a curl call to grab a small chunk of data don’t do it. You must use this class.
This first principle has probably already caught a few out out with code that is duplicated in various places throughout your app. We’re all guilty of it. No need to beat yourself up. Yet.
But, I hear you asking, Why?
Simple. It reduces the complexity of your code and makes sure that if there is a problem it’s not smeared out all over your app in 15 places where you would have to make sure you fix all 15 places without making a mistake. Fix it in one place and it’s fixed for the whole application
Also it makes it easier to write the tests for the class. By limiting the scope of an class you don’t have to write as many tests, the tests are simpler and
The open closed principle states that once you have finalized the implementation of a class you never again change that class (bug fixes not withstanding). If 6 months down the road your boss comes to you and asks you to add some function to an existing class you say ‘No’. Oh. You want to keep your job? Don’t worry. This principle has you covered. You can take that new functionality and create a new class that extends the original class.
Combined with the first principle this creates a series of small interwoven classes that work together to get the job done.
You should think about making use of interfaces if you have a lot of classes that need to do similar jobs. PHP interfaces are basically abstract classes that define a set of member functions that classes must implement if they implement the interface but do not provide any implementation. This give you the ability to define the interfaces that a series of interrelated classes expose to the rest of the app and make it easier to swap the classes for each other.
If you were to alter a class then anywhere that class is used will now potentially be a regression bug. However if you leave the implementation that is being used alone then there will be no regressions. Makes sense huh?
By using extended classes and interfaces you are setting yourself up to have mockable code. When your object doesn’t need a specific implementation of a class as a dependency, instead only needing something that satisfies an interface you get the ability to swap out the dependencies with mocks. We will talk about mocks more in a couple of slides.
The LSP was first proposed by MIT researcher Barbra Liskov in the late 80’s and expanded upon in a 1994 paper co-written with Jeannette Wing.
States that If you have a class A, then any class that is dependent on that class should be able to accept any other class that implements the same interface. So if you implement your classes either as a class hierarchy or using interfaces to ensure that the classes and subclasses all work the same way then your dependent class shouldn’t care one bit which class it was passed. It will happily interact with either and the functioning of your application will not be impacted negatively.
For example lets say you are writing a caching implementation. You want the cache to be able to write and retrieve its caches from several different types of storage: db, flat file, memory, stone tablets, etc. You would start by defining a interface that says all my cache implementations will have the same functions defined. The interface does not dictate how each class will work behind the scenes just how external code will interact with it. Then you create a series of classes representing all the different storage options you want to support. This give you the flexibility to implement additional storage types later as well as any class that has a dependency on your caching system will be able to accept any storage option without needing to know anything about what’s going on behind the scenes.
The big upshot of this principle is mocking. I’m not talking about making fun of people. I’m talking about mock objects. Mocks are versions of your classes that return fixed datasets and/or results. They do no actual work just return canned data or simulate hard to replicate error conditions. For example a database result set mock would return a fixed dataset that triggers the code paths that you want to run for that test. It’s important to note that a mock does not do any actual work. In this example the mock will not actually talk to the DB to get data. Instead the data is encoded right into the mock. Your tests should not be testing giant data sets, just enough data to ensure that your class behaves correctly.
A design that follows the LSP can have its dependencies replaced with mocks giving you confidence that your class will work correctly when real dependencies are passed in.
The interface segregation principle states that you want to make your classes as simple as possible. As few methods as possible to meet it’s requirements as defined by its interfaces. No more no less. This leads to the second point of having several small interfaces is better than a few large interfaces. There’s not much more to say about this slide so lets move on to the why slide for ISP.
Why do this?
Firstly a small interface is easier to write tests for. Remember that your mocks need to implement every function defined in the interface. If your interface has 15 functions then you need to implement each one even if you are only testing a class that uses two of them. Which is easier writing a mock for a 15 function interface or a mock for a 5 function interface?
Secondly, a large interface is a magnet for change requests. When a new requirement comes down the pipe then a large interface is a more likely target for receiving the changes. And if you remember the Open / Closed Principle we want to avoid changing our classes once they are finished. If a change does happen to an interface or implementation then you have to retest everywhere that uses that interface. The larger the interface the more places that is.
Dependency inversion states that your classes should never depend on a specific implementation of a class. Instead it is dependent on an abstract base class or an interface. This separates your code from the specifics of an implementation. In practice this translates to a concept called dependency injection. You may have heard of this before. In fact another talk happening on dependency injection by David Pascoe-Deslauriers in this very room in following this session called What the heck is "dependency injection"? and other Drupal 8 mysteries. I encourage you to stick around for that.
To ensure this principle is followed you should make sure that your class follows these simple techniques:
pull in all dependencies either via the constructor or a setter function
Make those dependencies either interfaces or abstract base classes and enforce this via type hinting.
Never instantiate another object inside of your object
Don’t call any functions that return objects
So why all the fuss?
If a test fails while testing your class you want to be sure that the problem is in your class and not in one of it’s dependencies. Makes sense right? Unfortunately if your code is tightly coupled then you are forced to test not only your class but also any tightly coupled classes that are there as well. Tight coupling does not allow you to substitute in mocked objects so you have to trust that the dependency is not experiencing errors.
Also it allows you to substitute other implementations of the same base class. Want to swap out your caching mechanism? No problem just pass a different cache object and your class will not have any issues.
So in review, by following the SOLID principles your code should come out the far end much more testable, resistant to change, and safe from regressions. And you will know that you are testing your code and not someone else’s.
All this boils down to two techniques that will serve you well. The first is dependency injection.
Dependency injection involves writing your classes to always take in all dependencies as abstract base classes or interfaces and never instantiate objects inside your classes or use any constants or globals defined outside of your class. Remember that your class will be completely self contained and any thing that it needs to do it’s job will be passed in as a fully instantiated object.
The second useful technique is mocking. This comes as a result of having a class that makes full use of dependency injection. If your classes are resilient to changing the types of objects that are passed into them then they wont know that you passed in a fake object that returns canned data. It will just happily do it’s thing, crunching its numbers and giving results. By controlling the inputs with an iron fist via mocking you will be able to control the outputs and thus you can determine whether or not the class is doing it’s job correctly.
Just make sure that your mocks don’t actually do anything. If you are testing something that needs a database result set, make sure that your are not actually fetching results from the DB. Instead your mocked result set just returns a fixed set of data that is hard coded into it.