Unit Testing for Designers
Tamar Nachmany at Playgrounds Conference, 2017
Hey, guys, how's it going?
I'm so excited to be here with such amazing speakers. Can I get a round of applause for the speakers. Yeah, I was really excited to speak at this conference, because of the amazing people who are speaking here, and also the opportunity to come to Australia.
So I wanted to start by doing a little poll, since this talk is about working with designers, teaching software development practices to designers, who here is an iOS developer? Raise your hand. That makes sense. Who here is a designer? Yes! Who here is an iOS developer and designer. Couple of hands, awesome. Yeah, looks like the same people. Yeah, I guess that's why you would be at this conference.
So since before I was a software developer, I've been an artist. I think that's part of why I love working with designers. I work at Tumblr, which is a very design-focused company. At Tumblr, I always try to find opportunities to teach software development concepts to the designers I work with. One concept I've taught in the past is how we create and layout views.
So it occurred to me, designers, they give us designs of screens to develop for the app. So I gave a talk at work about Auto Layout and declarative programming, and how we declare relationships between views, and sort of the technology behind the interface. At some point, one of the designers I work with closely, who's one of my favorite people, a very curious person, who's very confident, and is confident enough to always be asking great questions. He saw a conversation happening on Slack about unit testing in the iOS channel and he asked, "What is unit testing?" So I took him to my desk and I talked with him about it. I basically realized that unit testing, in a weird way, is an amazing topic to teach designers. So that's what this talk is about.
You can call me Tim Tam. My name is Tamar, and my Australian name is Tim Tam. Like I said, I'm an artist and I love art, so this whole presentation is full of distracting, yet beautiful art. I work at Tumblr. Part of why I was attracted to work at Tumblr is that Tumblr is filled with beautiful weird art. Design is incredibly important at Tumblr for a couple of different reasons. One reason is the beautiful artwork that people put on their Tumblrs. Our users give us a lot of responsibility. They give us the responsibility of creating an environment to display their creative works in. Whether that's a beautiful embroidered leaf, a really amazing meme that works its way through Tumblr for a while until it explodes everywhere else. We really want to create a visual environment that makes that work look beautiful.
We also develop software at Tumblr that supports communities. This is a very tricky design problem as well. It's not just about how the interface looks. Just to go back to what I said before, even that is not just about how the interface looks. We're creating tools for creating art, creating content, that's a very tricky design challenge. In addition, we create tools that enable people to form communities, and that's one of the most interesting design challenges.
Tumblr is a very interesting product. If you guys aren't familiar with it, it's basically a platform that lets people post stuff like images, videos, text, kind of talk about issues that they care about. One thing that makes Tumblr really interesting is that a lot of Tumblr users don't know each other in real life. So when we're designing Tumblr, we're designing a platform that makes people feel close to other random people on the internet, and want to share their personal experiences with them, talk with them about their life, share jokes, and ultimately feel close and have a relationship. I also just wanted to include this slide, to include this image. So that's another reason why design is very important at Tumblr specifically.
So this talk, again, teaching unit testing to designers, you're at teacher training. This is an interesting talk, I think, because I don't know what everyone here knows about iOS development, what kind of iOS development practices they follow, but it's kind of like a meta-talk where I'm hoping to express to you the ways I think about certain areas of software development.
But everything that I say, I want you to ask yourself these questions. What you would normally ask yourself when you're listening to a talk is like, "Do I understand this concept? Do I agree with this interpretation? Do I want to keep listening?" Maybe. In this talk I want you to ask yourself those questions, and then I also want you to ask yourself, "Do the designers I work with know this concept? Have I taken an opportunity in the past to teach them this concept or any other software development concept?" If not, do you agree with the way that I am explaining the concept? Maybe you want to use that explanation with your designers.
So for each slide I want you to go through that thought process, and just remember that anyone can be a teacher, you just have to decide to do it. You can be a teacher in any situation that you're in. You could teach people about your life experience. You could teach people about the type of software engineering you do. You just need to make the choice to do it, learn about your audience, and start talking.
So today I'm going to be using unit testing to teach modularity and architecture. The end goal is not just teaching unit testing. It's really teaching unit testing to demystify what software architecture is, and really draw a connection between software architecture and design. Specifically, I'm going to try to cover these topics: What is unit testing? What is modularity? How does unit testing impact software architecture? And I'll go through a really small example. Modularity and change as the theme. And then why? Why am I so passionate about this? Why do I think that you should, and can teach these topics to the designers that you work with?
So what is unit testing? In order to teach unit testing we need to step back for a second and talk about what software developers do. Because there's some assumptions that you might make about what someone knows when you're teaching them. But you should sort of start as basic as you can. So what is software development? Software development is the expansion and adaptation of a system, right? It's also the reuse of a logic and types across a system. These are really basic ideas, but do the designers that you work with, are they aware of these basic truths about software development?
One reason I wanted to include these slides is that, as you can see, this sounds a lot like what designers do, right? Designers of an iOS app, they're not designing static screens, like they're not designing a greeting card, for example. They're designing a system that users exist within, interact within. So if you start by framing software development that way, it doesn't seem so forward. And if you think about it, that really makes sense, right? A designer is developing an interface that users interact with, and your software should have a relationship with the interfaces being developed. So they're both really systems being designed.
So let's define unit testing. I'm just going to read this. I'm not going to read all my slides, but I'm going to read this one, especially the definitions. So unit tests validate individual units of code by executing logic and asserting expected results. If you've written a unit test before, this sounds familiar. You're basically writing a function, the function is at a different target in your app. It doesn't get shipped with your app. But it is code that you write that uses the frameworks of your app.
What you do in a unit test is you're basically creating a type, or maybe an instance of a type, or maybe a few, and you are testing some aspect of the logic of that type. So maybe you call a function on it and you want to look at the end at the result and ensure that the result is what you expect. Unit testing is just one style of software testing. In unit testing, a unit is an isolated piece of code, sad, sad, isolated piece of code, all alone in the world. The idea is, this is a style, an aesthetic of testing. The idea being that we want to validate individual pieces of our code base. This pairs really nicely with additional types of testing like UI testing, which tests interactions with the app.
This is at a higher level. This is the kind of thing that I like to do when I teach someone who's not familiar with the implementation details of a field, is give them a story and a broad explanation of what something is. Unit testing is really a vehicle for change. If we think back on the idea that a code base is basically a system that reuses types. What unit testing does is empower you to reuse types. Maybe you start to work at a company, and your code base doesn't really have so much reuse. It solves the same problem everywhere. And you want to modify it and make it better. It's so much easier to do that when you know what you won't break everything, and when the trade-off isn't, you know, I can do the right thing and make this better, make this make more sense and easier to change and better in every way, but it's going to break the app. It solves, to some extent, that trade-off.
I'm going to explore this issue a little bit more because, if you're familiar with unit testing, you know that when you write unit tests, you have to write more code. So there's a question there, is change easier or harder when you write unit tests. We'll get into that more. Just another broad concept around unit testing is that it automates identifying new issues. Unit tests don't write themselves unfortunately. No, that's fine. But what they do is they create this sort of safety net around your code, that the way we do unit testing at Tumblr where I work is we, number one, developers run unit tests on the branch that they're developing. We also run unit tests in that continuous integration process, so that before a piece of code can be merged, before a branch can be merged into develop, we know that it's not introducing new issues that are unit tested.
Not all software can be unit tested. That's part of how architecture comes to play. This is just a really basic example. Everyone learns differently, teacher training, thinking about teaching and education. So I'm just going to walk through this code and just it's very simple but there's some important ideas in here as well. Okay, so we have a function that we want to test. This is based on something I actually did at Tumblr. We developed an iMessage app when the iMessage app store launched, that was really exciting. One thing we wanted to do was reuse awesome code from our app for this iMessage app. So we have, it's really like a media library, but for this example I'm calling it a photo library. The iMessage app allows you to create gifs from your burse and all kind of content that you have on your phone that's gif-able. I usually say gif, but someone judged me the other day, so I'm trying to get better.
Okay, so in this function, what are we doing? The function takes in as one of its parameters a photo library, so it's a type of View Controller. Then based on the configuration property of this photo library, we present it different. I call this “present”, but it's not a modal presentation for both. In iMessage, one of our technical limitations was we had to add this View Controller as a child View Controller of the sort of principal View Controller of the app. If you've worked with that technology at all, or I guess, if you've done that at all you know that in the same way that presenting a View Controller modally or adding it as a child, those two techniques are different. Similarly, dismissing them has to be different, and if you add something as a child and then you dismiss it as if it's a modal, it's not going to perform the way that you want.
So this is the kind of thing that's really great to test. It's very, very easy to test. The consequences of it breaking are serious, you know, maybe you won't be able to interact with the app because the root view of the child View Controller was never removed, you know, and then no one can use your app after they try to post a photo, which for a product like Tumblr would be a big problem.
So let's talk about how we would unit test this. This is a really simple test, what are we doing here? So we're making a presenting View Controller. This is the View Controller that has that function we just wrote. We are making a photo library. We're calling ‘present’ on it, and passing in that photo library. Just to go back, we're passing in a configuration of iMessage, so what that's expressing is, we want to add it as a child View Controller. Then we present it and then we assert that the parent View Controller of the photo library is a PresentingViewController. That would be false if we presented something modally. So that's really simple. Again, we want things to be simple. We don't want to explain things in a complicated way, in general, we don't want to create weird boundaries between ourselves and the people we work with by saying things in a complicated way that they don't understand. We also don't want to do that with the designers we work with, especially when they don't know all the iOS lingo as much.
So how is this test even functioning? How are we able to test the state on this photo library? Well, this is a public property on this type, and that is what allows us to compare it with a PresentingViewController. That is a type of software architecture. If we didn't have that property, if it wasn't public, how would we test it? You know, this is subclassed as UIViewController. In this case, because we're adding it as a child View Controller, you could test other things, for example, you could test like, is the root view of the child View Controller a sub view of the root view of the child view a sub view of the root view of the parent View Controller? Things like that. But again, those are sub views, it's a public property.
There are other techniques for testing when you write your own functions, if you don't want something to be a public property, because maybe it makes your app, it adds complexity to your app or adds ... I'm going to go more into that in a second. But there are trade-offs and choices you make when you're writing your code yourself and not subclassing something that Apple wrote, you can actually decide what you want to expose on a type in order to test it.
This is, again, a fundamental idea, and it makes a lot of sense and it's almost a basic idea but it deserves to be said, tests reflect the construction of a code base or a type. We have to make decisions about how we design a type as we think about testing it. So I talked about what unit testing is. How do I look at the time here?
Okay, cool. I just see like the time of day right now on my computer. So now I want to talk about modularity. One way to think about it in terms of what we just talked about is, modularity is how units become units. It's a philosophy of software development that basically says that the name of the type should express what it does, what it does should be something very specific, and if you have “and” in a type name or some concatenation of ideas, probably not modular. You really want something that has one purpose.
Another aspect of it is that has minimal dependencies. I think dependencies and dependency injection would be a really cool topic to teach designers. I'm going to go into that a tiny bit, but that's like for the next class. So software architecture is partly how developers communicate with one another. So if we look at these types, this would be something that you would see at the end of a type name, you know, View, Model, Parser, Determiner, Provider. These are really interesting. The way that we name types usually is they either map to a program in concept, they map to a concept in our app or user experience, or they map to a general description of something that does a certain type of thing. So maybe there's something in real life, like a parser maybe, maybe isn't a good example, like a filter, yeah a filter would be a good example where there's an object in real life, it also relates to this thing we do in programming.
I think it's really empowering, actually, to make sure that our designers understand that these are mostly just ways we communicate with one another. That's another example of how software architecture is a form of design. Talked to you about that. So just to put it very bluntly, modular code, so code that has minimal number of dependencies and serves a very explicit purpose is easier to understand. If you have a type and it is a provider, you know based on the name what the type does, and also as importantly, what it shouldn't do.
I'm going to continue to talk about this, but a lot of what we do as software developers is plan for the future and plan future changes. So in a code base if you see a provider, you know that the provider should not be a parser, because it has name, and the name expresses what it does. The documentation adds detail to that name, and the functions, you know, you expect if I hear about a provider, like a cell provider, I think I'm going to see a function that returns a type of cell. I started laughing because it's that simple. It's not that these words that we use correspond to very advanced technical concepts. They're ways we communicate with each other.
Modular code is also easier to unit test. It follows the philosophy of unit testing where we try to test things that are independent from one another, and it's easier because it doesn't require us, for example, to create 20 instances in order to create one type that we then test, which is what might happen if you have lots and lots of parameters somewhere, and then if each of those parameters each has a lot of parameters, I think you probably have seen this before. But it's easier to unit test that is isolated. It's easier to change? Question mark.
I think this is really fascinating. This is another theme that I think really will resonate with the designers you work with. In preparing this talk, it was really, really fun for me because I've been thinking about these themes for a while and as I started preparing the talk, I had a lot of great conversations with a diverse group of people that I collaborate with. I spoke with a designer about the concept of patching that we have in software development. She said, "We have that in design too. I used to work at a design shop and we create a design for a company and then when they don't want to pay us anymore and we're not working with them, they patch on all this stuff to the design to the point where it's unrecognizable." My friend told me she doesn't consider those her designs anymore because they are not deeply solving the user experience problems, they're just adding stuff on top of an existing design.
As I said, the purpose of a single-purpose object, as you might imagine, single-purpose is another way of talking about modularity, something that serves one purpose, the purpose of a single-purpose type is easier to understand. The name should express its purpose and something like, part of why people talk at conferences a lot about View Controllers being terrible, is that that name and that concept is not very specific. So it doesn't communicate to developers on a code base how that type should be used, and that can often lead to a lot of problems and make it hard to develop new features.
Like designers, developers negotiate innovation and communication. I'm really passionate about having diverse teams. It's important that on a diverse team, you actually let people express their creativity, their ideas, their philosophies of how code should be sort of architected and stuff like that. This is a really interesting question because we want to negotiate innovation, enabling all the members of a team to be empowered and introduce new ideas with the idea of communication where if someone has seen a concept before they already understand it, right? So you know, you want to give people an opportunity to be very creative and also have a balance where a new person can come to a team and understand how things work.
This is very much true in interface design as well. If you work on user-facing parts of your iOS app, you know that designers think all the time, "How can we make this interface very innovative, very special, a unique environment for our users, but also take advantage of concepts they already understand?" They already understand probably that a gear means ‘settings’. Someone did that once, now we all know that that means settings. In itself, a gear doesn't actually mean that, but through seeing it over and over we know that that's what that's supposed to mean. So it's this really interesting balance.
You know, again, with all these slides, I want you to go back and think, "Do I understand this? Do my designers understand this? How would I teach this to my designers? Is it the way that Tamar's explaining it, is it a different way?" This is the kind of thing that really makes our work so similar, and it's something that can be explained very simply and you could have a very engaging conversation with the designers you work with about something like this, "How do we balance innovation and basically homogeny?"
So how does unit testing impact software architecture? We touched on this a little bit when we looked at it at specific test. But let's go into a little more detail. Actually, this does go into more detail, but it starts a little bit of a high level. Testing promotes safety. But what is safety in a code base? Safety is of course the reduction of bugs and crashes, that's a very traditional idea of what a safe app doesn't do, it doesn't crash and it doesn't have bugs. Unit tests don't fix bugs, but they of course, help you identify when bugs start to happen. Hopefully you identify that as you're changing your code, not when your users are writing in the app store or something like that.
But there's another form of safety as well in a code base. I really wanted to include this crazy slide. Safety is the reduction of chaos. Any of us who are really passionate about software architecture know this. It drives a lot of things that can make new features very hard to develop, but is very much worthwhile. For example, part of why people give singletons a hard time is that it's like, they don't have any structure, they just can be used anywhere in the app. I don't want to go into that too much, but basically, you know, it's better probably to have something like a type that holds a reference to a specific object that can only be ... You know, maybe no other part of the app needs to know about that object. Maybe it's just used in that type. So you pass it in in a constructor in the initializer and then no other class knows about it, no other class can get it, no other class can set it. This is very important.
Chaos can also mean View Controllers or any type where the name of the type doesn't really mean anything. You don't know how to use it, you don't know where in the app to find certain functionalities, and so we really want to create a system that makes sense, is calm, is testable, and the best scenario is that a new person starts and they can start to poke around the app, and things are expressive and make sense. This is something that I really encourage you to communicate to the designers you work with.
Developers are highly focused on how change happens. Will unexpected changes happen in this piece of part of the app. When I want to introduce this functionality into a different part of the app, how will that change happen? Will it be difficult for me to pull this type out of this part of the app? Will it be tightly coupled with this part of the app? Or can I just pull it out? Maybe I want to actually use the same instance in two places. Maybe I want to instantiate it high up in the app and use it throughout. So this framework is so fundamental to what we do, but I don't think other people know about it because we don't talk about it. So yeah, going back to my definitions of software development, software development is the reuse of code. When we protect our system from unwanted changes, we can actually reuse code in really expressive ways.
This is another interesting topic that's just cool, you know. But it's also practical for designers to know. Designing a test is a type of abstraction. The fact that we have different aesthetics and styles of testing, like unit testing, functional testing, integration testing, UI testing, snapshot testing, I can go onto any of those when we're in the Speaker Access room later, these are all both practical ways of testing the functionality of an app and ways of reasoning around what a test should do. Should a test test something really, really basic, like this function sets its value 10, test that this value is now 10? Or should it test something more broader, like the relationship between two types?
I talked about this earlier, but basically, it's the idea that you need some kind of state to validate in a test. Maybe it's on a public property, maybe it's passed through a function like in the example before, the photo library was created with a configuration type, which is iMessage, right? But passing that type in, in production we can also pass it in and test it, so we can force the style there and present correctly and then validate that state.
Let's talk more about the concept of change in software development and modularity in change. Understanding software development means understanding change. There are so many things that we do as software developers that take extra time, that it's important that we don't just shrug off questions like, "Why is this going to take longer?" It doesn't come from a place of antagonism all the time. Sometimes people really want to understand why, and that's an opportunity for you to be a teacher and actually give them an explanation that makes a lot of sense. It's important that people understand that what we're doing, even though now this takes longer, you know, making things very modular might take longer, in the future this will save time, this will reduce chaos and make this code base a great thing to add onto.
Does modularity make code easier or harder to change? Think about it for a second. I think this is a really fascinating question. The answer is, both of those things are right. It makes code harder to change and easier to change. Modular code is strict by design. By design, we don't want it to be changed any old way, we want it to be changed in very specific ways. That's why we name it a certain name, it has a very specific purpose, we try to limit the functions that are added to it.
So let's say you have a delicious type that has a very specific name, a function that serves a very specific purpose, and it has a reference to all these useful, delicious objects that you need for some purpose. In that situation you're not going to add another function to that type unless it makes sense. But you know, just as an example you might want to create a new type that has a very specific name and a specific purpose, even though it would be easier just to add another function on there. And I talked a lot about different reasons for this, but this is by design, this is intentional. We're making our lives harder for ourselves now so that our lives will be easier and better in the future.
But I would say that while modularity makes a type harder to change on purpose, it makes a type easier to reuse, for a few different reasons. Number one, we reuse a type, we know that it's not breaking everything. We add, for example, a new case to a switch case. We want to make sure that the correct logic is executing. But because we are unit testing and we are great developers who are developing very modular units of code, that don't have a lot of dependencies, we can easily pull this type out and use it to solve the same problem elsewhere in the app. So that definitely does make our lives easier.
Of course, testing fundamentally makes change easier. It makes change easier because it enables us to know what's going on, how our changes are impacting the entire system, and of course, encourages practices that make change easier as well. That's why we write all this extra code that is used to call this wimpy little assert that asserts something really obvious, because it really protects our code and it lets us make fundamental big changes in the future.
Prototyping isn't just about speed. I just wanted to include this gif because it's beautiful. It's about an app's foundations. If we invest time in creating great foundations for an app, prototyping something, pulling something out of one purpose, one area of the app, and using it in a different area, when it solves the exact same problem, is much easier. So this is sort of starting to get to why designers really benefit from understanding software architecture.
Why? Why am I so passionate about this? Why do I want to talk about software architecture with anyone who will listen, especially the brilliant designers I work with? Innovation. A very common way that designers come up with new things that they want to add to their apps is by doing competitive research. So let's break this down. They want to innovate, they want to add something new to their app. So they go to the app store, they download apps that already exist, they identify parts of those apps and they add them to their app. Hopefully you can see why this is fundamentally flawed.
You know, there's a space for competitive research, but you're building something that already exists. Although you can be innovating because of the way you combine concepts, how amazing would it be if you, as a designer were able to, for example, look at a new framework on iOS, look at the Messages framework that supports the iMessage App Store, and look at the models in that framework, and see what properties they have and start to think of amazing innovative ideas that you can introduce into your app. Then you get together with your awesome engineer and you prototype and you create genuinely innovative products.
Seeing the future is very good for software. There's such a thing as over-optimization. But if I know that I need to pull a type ... There's something I recently have been working on, where I have a class that has a reference to a lot of objects that are needed for a certain thing. I'm not going to go into too much detail. Basically, if I know that my software is going to be used in a certain way and adapted in a certain way, in a month I can architect it in a way that is easy, so that the next person who works on it can quickly pull my type out or instantiate it elsewhere and use it for a new purpose, rather than patching or having to make a lot of changes there.
Software architecture is design. That's another reason why designers should know software architecture. We're creating a system. We're coming up with metaphors and abstractions around things that our system does. We're trying to put it together in a way that other human beings can understand. We want them to understand what a type does, what a type should do, what a type shouldn't do. It's very similar to interface design, like I said, why should be we be keeping it to ourselves when it's so interesting and cool? It's something that our designers are already very familiar with, if we explain it in a certain way.
Collaboration. Like I said, I love working with the designers at Tumblr. My background before engineering was creating art, so that's part of why I love working with them, I'm an artist too. It's really important that when you collaborate with someone, you feel confident around them and they feel confident around you. You want to be able to ask them questions and not feel judged, and get a great answer. And you want them to be able to ask you questions. Of course, the more foundations you have around their work, the more you can have a high level conversation. For example, I know that designers develop a system. If I thought that they just developed screens, I would just teach them Auto Layout and some of UIKit, and call it a day. But I know that they're developing a system too. So my understanding of their work lets me be more helpful and a better teacher.
In conclusion, my vision for technology teams is people who feel confident around one another, inspire one another, teach one another, and build visually beautiful, very usable systems. So yeah, I'm Tamar, say hi, be my friend on Twitter. Yeah, that's it.
If you enjoyed this talk, you can find more info: