James Simone, who joins to talk about Test Driven Development, is working at Salesforce. He was also in the show previously on #69 talking about DML Mocking for Apex Testing.
Main Points
- Book: Test Driven Development By Example
- TDD Definition and its benefits
- How to learn and practice TDD
- Boundaries and Tests as documentation
- TDD for Salesforce developers
- Reading others’ code
- Mob programming
Links
- James’ LinkedIn
- James’ Twitter
- Book: Test-Driven Development By Example
- Mob programming
- James’ other episode: DML Mocking for Apex Testing
Video Teaser
Contact Me
If you enjoy this podcast, please reach me out to let me know your thoughts or simply say Hello. My (Xi’s) contact info:
Take care, until next time :)!
Transcript
AI-generated, unedited.
[00:13] Hello everybody. This is Xi Xiao. This is yet a new episode of SalesforceWay podcast. I'm sitting with the returning guest. His name is James Simone. Hello, James. Hey, Xi. Good to see you again. Yeah, good to see your face. It's been almost two years, I guess. Yeah, we had to backtrack there and decide how much time had actually passed since we last spoke. Yeah, would you still would like to introduce yourself? You know, it's been quite a while. Sure. Yeah, I'm James Simone. Currently working at Salesforce as a senior software engineer. Between the time since we last spoke, I was running my own consulting company, then moved to another consulting company, going back to full time work there, and then made the move to Salesforce. And now I'm developing on the Salesforce platform for Salesforce employees internally, which is pretty exciting. So you had the role as a consultant, both the solo developer, solo consultant, and also you joined the bigger company doing consultants as as a team, right? That's right, and now you are building a product inside Salesforce. Yeah, that's right. Yeah, the thing is that you have weared so many different roles, so that's perfect candidate for us to really talk about the topic of this episode. Yeah, one of my most favorite topics of all. Okay, so let's bring it up. It's TDD,
[01:43] the Test Driven Development. Do you remember when it was introduced at the very beginning? To me, I was introduced to test-driven development back in 2016 by an old colleague of mine who became a manager and mentor to me. I moved companies to work with him, and he gave me the one of the most classic TDD books at the time, Ken Beck's Test-Driven Development by Example, and that was really at the end of 2016, early 2017. A huge leap forward for me in terms of thinking about programming and how I thought about programming and how I thought about code in specific. That book is a really interesting example. Taking it sometimes literally line by line for a company that is moving from. Tracking payments in one currency to tracking payments in multiple currencies, and so the example that Kent Beck writes about goes from. Just imagining currency as always being in U.S. dollars to what does it mean to take a payment in Swiss franc or Australian dollars, and how do we represent this internally within our system so that we're accurately tracking the the price of things and the cost of things? And it's a it's a really mind blowing
[03:14] book. And again, I think one of the things that's so impressive about it is that he. Literally, in some cases, goes line by line. It's just very small changes that, through tests. And very clear articulation as to what he's thinking and and why he's doing certain things end up leading to a very big change in the system without that many lines of code and I think it's very impressive. Yeah, I'll put that book into our show notes. Could you articulate a bit more? What's the difference? I mean, how did you think before and to what really changed afterwards? So the the thing that I always come back to on this front is more traditional engineering, and take for example something like. Building a bridge. Before I read this book, I had never thought at this level of abstraction before. So I wasn't thinking about trying to find parallels in real life between the way that we do things in terms of creating features, fixing bugs, and the way that things are created in the real world, and in particular in the in the real world of engineering. But for the people who don't know much about TDD, how do you introduce it? Yeah, so I mean, I'll I'll go with Kent Beck's approach, which he refers to as the stoplight. And if you're not familiar with a stoplight, for
[04:44] whatever reason, we talk about. The red green refactor approach, where red means that you have succeeded in writing a test that fails, a test that says that this thing that you expect to happen is not happening. Then the green part is doing the smallest possible amount of work, which could even just be hard coding a value somewhere in order to make that test pass. And then refactor is the. Taking a step back, part of the approach where, at that point, you are either free to write another failing test, or you are free to do some cleanup on the code that you've produced. You might have introduced, for example, some duplication where you know that. Either other classes or other methods that you've written in the past might be possible to be reused, but again, because you're trying to do the simplest possible thing at the current moment. You just did that simple thing in order to get your test to pass, and now you have a chance to do cleanup. And then that cycle repeats. Where if you are done with refactoring, or you don't need to refactor at that given time, you write the next test that's going to fail, and then you proceed in working in this way until you're done. That's a good explanation. The first time I hear the TDD, the
[06:14] most mind-blowing part is that we start from writing the test. We don't have a product code at all. We always always start to writing the test and making it fails, which is the the first step that the red. That's right. I remember Robert C. Martin, Uncle Bob, when he heard about TDD, he was also. What is this absurd programming method? How do people already start writing useless tests? And then he tried it out multiple times with Cam together, and then he started to grasp the beauty of of using TDD and the the flexibility and you know all those beneficial points from using TDD. Yeah. I think it's not easy to just listen to people and say okay, TDD is good, and then you you really have to try it out. A lot of things are like embedded in the process, and then at some point you realize okay, this is such a a beautiful thing. We might want to use it in the future for a lot of projects. Yeah, and I think specifically in regards to Salesforce development, there are going to be cases where you can't use TDD, but those cases are fairly limited. At This point in the platform's maturity and lifecycle, and it does, I think, to some extent, come down to mentality about approaching
[07:45] this thing from the ground up. And again, I'm going to bring it back to the bridge analogy in terms of. Avoiding over engineering, I think, is the the biggest thing. Instead of building the bridge and then trying to prove that it works, if you are starting from the ground up and starting with the test itself, you frequently avoid. Something that might otherwise be very alluring, which is adding bells and whistles as you go, making methods that you may never use, making classes that are only going to be used once, whereas TDD sort of forces you to focus on the task at hand in a really direct way that produces results and and forces you to continue to produce results instead of thinking. Maybe I should use an interface. Maybe I should use an abstract class. It's much more concrete, and that refactoring step then becomes a chance for you to fold in other code, older code, fix up the code that you're writing without the allure or the. The temptation to give in to adding things that you can't use at that current moment. Let's say I am a developer. I'm just a solo developer. I have my open source project and doing it alone. I want to practice TDD. What kind of benefits
[09:15] would I get by practicing it? Yeah, I think the the clearest benefit to me, especially as somebody who has contributed quite a bit to open source over the past few years, is that you frequently find while writing tests. An intuition about the next test that you need to write. It's a lot easier to think about both situations that you need to be aware of, both in the terms of negative tests and positive tests, and different ways that the code that you've currently written. Because you're starting so small, you start to have an intuition about these things in a very brief period of time about what you need to test next. And that's one of the things that I love the most about employing TDD as a solo developer. When I'm doing my my open source work, although I don't always do that solo, I do pair programming and sometimes mob programming in my open source work as well with groups of people. But always we are doing it through the lens of TDD because frequently again it informs what you need to think of next in terms of either additions that you need to make to the production or the next test that you need to write in order to prove that things aren't quite. Right as they stand currently, so you mean even if I have a very clear business
[10:46] requirement, what the application is going to be, still TDD can force me to start from small. Yeah, and in fact, that's a famous Kent Beck quote as well. In that book, it's start small or not at all. Even if I don't do TDD, I can also start small. I can force myself have the thinking. I start small and build small features. That's the same thing, isn't it? Even if I don't use TDD, it can be the same thing. I think the part that Uncle Bob actually talks about, which I think is helpful in terms of clarifying why TDD is in many cases easier to reason about than. The alternative, which would be, if we're being generous, writing tests afterwards. Sometimes not writing tests at all. Right, Uncle Bob in Clean Code talks about the importance of boundaries. It's chapter eight in the book, I believe. And when you approach either developing features or updating code for whatever reason. From the perspective of TDD, the boundary that you are established between the tests and your production level code oftentimes allow you to think about how the code that you're updating, for example, is going to work
[12:16] in new ways that weren't anticipated the first time around. Whereas if you are constantly in the process of just developing and updating production code, Over time, you know these systems start to intermingle, and those boundaries become confused. In Salesforce, you know you're very. Typically, going to see this with DML between different objects, right? And as the automations start to interact with one another, frequently, what ends up happening is something unexpected. Either you are you have things that you want to do in terms of updating accounts and their contacts, but also from the contact side, you want to update accounts in in certain circumstances. And now you have this thing where the code is interacting in a way that. It may not be immediately obvious in terms of what you are immediately working on, but you could have done something like accidentally introduced recursive DML into the the mix. And in some cases, that's perfectly fine, and everything will "quote unquote" just work and continue to work. And maybe the only way that you would even know that you had done something like that would be. The system working a little bit slower, right? But from the perspective of everybody on the outside looking in, it may be that it again just worked. But you also may have introduced different and very
[13:46] hard to trace bugs. When you start to work on something where you're like, oh, I'm just going to make a small change, and then you find that actually another test has started failing, that's where things start to get really interesting, right? Because technically, you've done what you set out to achieve, but maybe you've done so at the expense of other functionality in the system. You've broken a contract or an expectation elsewhere in the system in an unintended way. Yep, and TDD helps to remove those unintended side effects by forcing you to think about upfront and confront upfront the reality of how code interacts with other parts of the system. So you mentioned a keyword boundary. So especially when I read the code written with the TDD, is that I see a lot of tests that really act as the boundaries. That's actually the crucial thing in our software project. When we touch something, we don't know whether it has side effect or not. Right, and so the tests then become your documentation as to what those possible side effects are. Hmm. And let's say for a developer, we join a team. That's a typical situation, right? The team could be already experienced with TDD, or maybe they are not, but you are.
[15:16] You want to introduce the TDD to to the team. Have you experienced these kind of situations? Absolutely. And again, this goes back to working for consulting companies. Goes back to working. Within larger teams in organizations, it's not always easy to. Be somebody practicing TDD by yourself. When you work on a team, you're never going to be able to convince everybody of the things that you believe in the the quality that I personally know that test driven development can be responsible for in an organization. I have had both successes and notable failures in terms of introducing it. Two places. I know that it it works for me. I know that at Salesforce it really works for the team that I'm currently on. I know that there are a lot of teams, both internally within Salesforce and basically everywhere else in the world, that don't use test driven development. I mean, what if I join a team and they don't agree? Not not all of them agree to use TDD. Can I still practice TDD with my code? Oh yeah, and I know many people in the Salesforce world who are the only people practicing TDD. So even if we're in the same team doing the same project, I can still use the TDD regardless. Absolutely,
[16:46] that's that's a good information. Is it practical if I don't know too much about TDD? I've only you know, I've only done projects as a hobby projects, and then now it's kind of commercial project I want join and then start to practice TDD. Is that feasible? Yeah, you can start again. Start small. You can start from any point. You can start practicing TDD at any point. It could be that, you know, a month from now, you want to say, "Hey, for this new feature or this this bug fix, I want to." Try it out, and I I am going to try it out by writing a test first, because that's the golden rule in test driven development that you are not allowed to touch the production level code until you have a failing test. And so you could start at any point, at any time, any season, right, any day. As long as you write that failing test first, you you are now practicing TDD, and you can decide for yourself. I was talking to somebody. Yesterday, who said I tried TDD several times before it really took for me. Before I got it. Before I became excited about it. And I loved that this person said that to me. It was such a a moment of humility, which I think is also a very important trait to have in a developer. Somebody
[18:17] who's willing to be humble to admit mistake. That's a really powerful personality trait when we're working with code because we all make mistakes, right? And either due to Changes in the system, additional complexity, additional requirements, or just things that we never imagined the first time around. We're not perfect. We make mistakes. And TDD is a very forgiving framework in that sense because it allows you to capture those mistakes in new tests. So it's a a mentality shift that you can start down that path at any point and allow for really powerful results. It can be the difference between saying, "I I made a terrible mistake." And transforming that sentence into "I found a new test I have to write." That's a good mindset. So, so okay, I I got it. So I can practice TDD from small whenever. But again, we are Salesforce developers. So, is there anything that doesn't fit into DDD that much? One of the things immediately coming into my mind is the running testing time. We are running it on the cloud; it's not local. And also, we have the DML that actually really lengthening the time it took to run. Especially if we have a large number of unit tests, then
[19:47] it gets really frustrating. First, I want to talk specifically about places where TDD is difficult or almost impossible to apply. And at this point, on the platform, I would say that that's. Almost uniformly limited to Aura components at this point. I think it's also a one of the big draws, in addition to massively increased performance and ease of development experience, in. Transforming Aura components into Lightning Web components, where you can actually practice TDD through the Jest unit testing framework that Salesforce has. But when we talk about network transfer time and and latency and. How long tests take to run, you know, this really brings me back to the first time that you had me on the show to talk about mocking frameworks and how. It's important again to establish boundaries in your code. I again constantly practicing test-driven development in my own life in my open source projects. It's very rare for me to have long-running tests, and I would classify long-running tests as tests that take longer than a hundred milliseconds to run. I frequently run hundreds of tests in under a minute. I wouldn't be able to do the work
[21:17] that I do without the ability for my tests to run quickly, and so practicing TDD has allowed me to shift and hone my mindset as far as the Salesforce platform is concerned into more of a boundary level consideration. And again, this is going back to the first time that I was on your show to talk to you about this stuff. But as far as boundaries are concerned, if the performance limitation is. DML and things that involve the database. Then that's my first order of business in terms of abstracting that away when it comes to running tests. Because I'm not looking to write unit tests that validate that the Salesforce platform correctly inserts records, for example. That's, I'm not trying to test the database. In other words, I'm trying to test my code. And so, while there are cases like when you're developing within triggers, trigger handler frameworks, where you for sure are going to want to test DML. That's the equivalent of an integration test. It's not a real unit test. You're trying to test that changes propagate correctly through the system, but the unit test is all about what's the smallest possible. Amount of code that I can test. Is it a single method? Is it a class being called? And in that context, TDD
[22:48] really shines. I think on the Salesforce platform because the test running framework is so easy to use and to run through. Whether you're running a single test, whether you're running the entirety of the the test methods in a given test class. I think that it's actually quite pleasant, and again, it should be extremely quick in terms of running those tests. It's interesting to listen back to again what you mentioned in the previous episode we talked, which is the sixty ninth episode, if I remember the DMM mocking. The point here is that if you are practicing TDD, then you would want the tests to be running fast. You want the test to be running, yeah. You want because you're hitting the testing button so many times, right? You are doing it in the Seracoin each time at least you hit them once, and this brings me to this kind of philosophical question. I mean, we find a new framework, the TDD, to do our development, and then we start to pay more attention to the test, the code quality. And which a lot of Salesforce developer didn't pay that much attention. Okay, the test can pass the sixty-five threshold. That's good, that's good enough. We we can push to production, and nobody really cares it anymore. But now you are telling me that I need to write a good test. The test should be fast.
[24:19] The code should be flexible, easy to maybe adjust in the future. And what if in the future the business requirement changes in the middle, all of a sudden? And I have all these existing code, the testing code. Should I change all of them somehow? You know all these things are not easy to solve. I know there are books talking about how to write good test codes, like a really thick book. Yeah, we we can summarize those thick books. I think, and again, this goes back to how I think that the best part about TDD is preventing over architecting, because what the situation that you're talking about happens frequently in real life, right? Where you are now given a new requirement, and it changes the system in a subtle way, and that subtlety involves. Dozens, if not hundreds, of changes to your existing code. But if your tests are small, if the code that you are writing is Produced through TDD frequently, it is small too, and even when you have to make large changes, then. It all starts with adding that next test, and when it all starts with adding that next test, and all of your other tests are still passing, you now have this incredible safety net as far as adding new
[25:49] functionality or changing the functionality of the system. Because now you know. As long as you are doing work, and only the the new tests that you've written or tests that involve changes to functionality that you've already produced start to fail, as long as those are the only tests that are failing, you're safe. And so it's very easy to. Go back and forth between that green and red state while refactoring existing code. When you have the safety net of good test coverage, now that's not always easy, right? We have. Lots of people work with legacy code. You inherit code, right? And I think something that you were alluding to before is that as long as you have the appropriate level of code coverage, you're free to deploy. But code coverage is a very, very tricky and deceiving metric when it comes to code quality. Really, it's not related to code quality at all. A situation that my team ran into recently is that we performed a very large refactor of a class with hundreds and hundreds of lines of duplicated code, and we removed quite a bit of that code because it was all just copied and pasted together into different methods. But essentially, it was doing
[27:19] the same thing. And because we had the tests, we felt confident about performing that refactoring. But what we missed is that there was actually a test that. Was not there a test that should have been there? Let's say, and it only became obvious when there started we started having issues. After performing this big refactoring, so we introduced a bug by doing this big refactoring, and the reason that we introduced it is because we had relied on the safety net of those tests, which if you are developing from the ground up, you should have a lot of confidence in. But in this case, because we had inherited this whole system. We made a mistake. We didn't realize that there was a missing test, and we thought, "Okay, well, we've done the refactoring. All the tests are still passing, so we're good." And now we know, and so you you build on that mental model too as time goes on, where you start to think, well, what am I missing? And that was a good reminder for me. You know, it's it's good to be at this seven years now and be reminded, hey, look again, I make mistakes every day. We all make mistakes every day. I'm not perfect, and I find that the framework helps me to produce better code faster and write more code over time. As a result, but that doesn't mean that it's going to prevent you from making mistakes. And
[28:50] I think it was a good reminder for me to be like, okay, well, yeah, next time that we do something like this, we have to be on the lookout for. Not only are all the tests passing at the end of this, but what's the quality of the of the tests that we have? Did we write these? Should we be looking for things that are missing from the existing tests that we have? So you mentioned the tests in Salesforce. Do you find that? Sometimes difficult to read the test code. Cause, cause for me, I do a lot of times, especially code written by somebody else who don't write the code as in the same style as I do. I just found it so hard to really understand some test test method. What does it really test? Well, that I think you say you said something very interesting there, which is that it's particularly hard to read code that you haven't written, and and I actually think that this is a skill, this is a huge skill set, and for a lot of people, it's an underdeveloped piece of their their. Tools for working together with other people, because a lot of us are working alone, right? Or we're used to working alone. We want to work alone, but as a result, if you're only ever reading the code that you've written. You're actually missing a crucial communicative
[30:21] piece in terms of how you think about code, and it's like a, it's like going to the gym. Basically, it's like exercise. The more that you read code that you haven't written, the Easier it becomes the more used to reading somebody else's code you get, and something that I find that's really fascinating, whether it's reading a test that somebody else has written or reading production level code that they've written, is that frequently you start to see a voice, the voice of this other person, the author of the code, and I find now again from having inherited quite a bit of code from people. I, I know, even without checking source control, in a lot of instances, who has written the code that I'm looking at, because there's a a voice that starts to come through. The more that you read somebody else's code, and you start to recognize the patterns that they employ, the thought processes that they had. Whether or not they leave comments in the code, for example, and test code frequently has comments in it as well. Whether they're of high quality or or still ring true today is always, you know, an interesting thing to discover. But for me, it's a process of discovering, and something that I I would highly recommend. To others, even if your only outlet for
[31:51] doing so is via GitHub or something like that, to read other people's test code, to try to. Have empathy and understanding for other people too, because it helps you as a developer to get inside somebody else's mind. Instead of thinking, you know, approaching it from a a point of condescension, like how could somebody have written this code? I can't believe. Obviously, it doesn't work. It's it's garbage. But that that you could just as easily say that about the code that you wrote yourself yesterday, you know. And so approaching. Reading code and practicing the reading of code from an empathetic mindset, I think, helps you to start to expand the possibilities. Understand why decisions were made before you you did something or before another team did something, and. Even if you already know ways that you're going to improve upon that, that empathy helps a lot in terms of reading something and understanding it. Okay, thanks a lot, James. Anything else you still want to add?
[33:01] Yeah, I would say I just want to mention in particular. I think I've spoken at length now about the work that my team does, but in particular, we also practice, in addition to test-driven development, mob programming, and mob programming is a much more recent development and. Framework, you might say, within the programming world, where, in addition to us writing the tests first, we're also only ever working on one thing at a time, and we work on it together as a group. So we have a number of engineers. We have a number of other people on our team. Our product owner, our Scrum lead, our engineering manager, and we're all on a video call for. Six plus hours a day together, and what we do is we practice working on features together, iteratively handing off to each other in very short increments of time. So we, every fifteen minutes, we swap out who's sharing their screen. And again, we're practicing this through test-driven development. But something that I think that's really interesting with mob programming, and it touches on something that we were just talking about, is a. It forces you to read other people's code constantly. Because you're on this call together and you're working collaboratively in that fashion, and B, it enables people to not only have a sense of community,
[34:32] even if you are working solo or would be working solo normally, gives you the sense of community that then. Allows people to share their strengths and weaknesses together in a way that, you know, I'm sure you've heard this phrase before: the sum is greater than the whole of its parts. That is true in teams as well as math, and I think that when we have a chance to work together collaboratively. Frequently, the output that is produced in that kind of framework is greater than what. You would see if everybody were just alone doing their own thing. Even if in the ideal world they were alone doing their own thing with test driven development. So I think that's an interesting thing to bring up. That there are, in addition to TDD, there are a lot of really interesting ways to share. That experience together, and I think that those can be really powerful. Yeah, yeah. Okay, thanks, James. Good to have you back, and thanks for coming to the show. Yeah, thanks so much for having me again. Talk to you next time. Bye bye. Bye.