Lesson 2 of 21
Introduction to JUnit
In this talk we start learning about JUnit (external link, opens in new tab) and some of its APIs. We also learn when NOT to use some of them.
In this lesson
Roy Osherove contrasts manual test rigs with xUnit frameworks (external link, opens in new tab) and introduces JUnit 4 with a test of a string calculator (external link, opens in new tab)'s add method laid out as arrange, act, assert (external link, opens in new tab). He explains his three-part test names (external link, opens in new tab) (unit of work (external link, opens in new tab), scenario, expected behavior) and why the before, after, before class and after class attributes usually point to integration tests (external link, opens in new tab). He defines unit tests (external link, opens in new tab) as running in memory under full control and keeps them in a separate package from integration tests, giving a safe green zone (external link, opens in new tab) of tests that should always pass.
Transcript
[00:04] (external link, opens in new tab) Now when I when I talk to people who don't have unit testing (external link, opens in new tab) experience, I explain to them that all developers have already done unit testing because I don't know a single developer that doesn't check their code before handing it off to someone else. They do it, but they do it manually. They maybe create a form or web or or a page or something with buttons and text boxes like text one and command button one and all that stuff. And if you click them in just the right order, it will invoke your code and you'll know that the code works.
[00:36] (external link, opens in new tab) So I've I've done that. Most developers have done these test rigs where they know how to run and know that the test is working. But the problem is that that's not structured. You cannot give that kind of test to everyone in your team and they will just instantly know how to run that. And the the big question is can I run my test that I just created three months from now when I forget how to use that form? Can anyone on my team run the things that I just wrote, the test rigs that I just did three months from now when I'm not here and I'm in a different department?
[01:12] (external link, opens in new tab) And if the answer is no, then it's not repeatable. It's not structured. And that's one of the reasons we also do it only on parts of our code because some parts of our code are really hard to test. So we only test the things that seem easy to us or we test only the things that are the most complicated because even the easy things take a lot of time to test manually. So we only save it for the really difficult stuff. And that's where frameworks come in.
[01:42] (external link, opens in new tab) Unit test frameworks. Now the the the collective name for the unit test frameworks is called XUnit frameworks (external link, opens in new tab) and the idea is that the X stands for the first letter of the language. So the first one was for Smalltalk was called SUnit. JUnit (external link, opens in new tab) was one of the first to to arrive as well. We're going to look at JUnit 4 and I'm assuming that you all have access to JUnit 4.
[02:13] (external link, opens in new tab) And what's nice about it is once you know how to work with one XUnit framework, it's very easy to translate your knowledge into other frameworks. So if you wanted to switch to something like TestNG later, you shouldn't have any problem switching because a lot of the constructs are very much the same. But I believe that JUnit is is definitely good enough and it's supported well enough in most of the tooling that it shouldn't be a problem to use it.
[02:48] (external link, opens in new tab) This is the structure of a very very simple test with JUnit. Now the structure is very simple. We have a class and the class has methods. The methods are public void. They don't take parameters. They could take parameters but we'll talk about that maybe later. I usually don't use that in Java. There is a special attribute on the method called test which is from JUnit
[03:18] (external link, opens in new tab) And the test itself is just a simple method that does three things and those are called colloquially arrange act assert and I've even separated them with an empty line between each of them. Now the thing that we're testing is string calculator (external link, opens in new tab) but the unit of work (external link, opens in new tab) in string calculator is called add or add is the entry point (external link, opens in new tab) that's the public entry point that starts the unit of work. The situation I'm testing is that when I'm calling it with an empty string and I'm expecting a return value that is the default value in the system.
[03:56] (external link, opens in new tab) The default value happens to be zero but I'm not documenting it in the name. Now I want you to notice the name that I'm using because the naming convention (external link, opens in new tab) is very important. You can adapt adopt your own naming convention. I find that this naming convention works for me really really well. And the naming convention main works this way. It has three parts in the name. The part is the start of the unit of work, the scenario under which you you are testing and the expected behavior or the return value for the state change (external link, opens in new tab) or the call to a third party (external link, opens in new tab).
[04:30] (external link, opens in new tab) And I separated them with underscores because it makes it a bit more readable. And the underscores are especially helpful for people who are new to unit testing. When they remember there has to be three parts. And when you have a good name with all these three parts built in, it's much easier to understand what the test is supposed to do. And a lot of people who start out with unit testing, they start out with only providing two or even just one of those pieces of information. For example, they would say, "Test add."
[05:00] (external link, opens in new tab) And then, of course, the serial killer comes in and says, "Okay, test add, but what are you testing?" I shall go to their house and ask them. Or they might say, "Test add empty string." Serial killer comes in, says, "Okay, you're calling add with an empty string. That's great, but what's supposed to happen?" And then they have to start reading the code. And the point of the name is that you shouldn't need to read the code of the test. So the third part is also very important. What's supposed to happen?
[05:31] (external link, opens in new tab) It's also important because the name of the test is one of the only things you will ever see in the list of errors when things start failing. And that means that even if you have good comments on top or at the bottom, you're not going to see those comments. What you're going to see is the name of the test. Out of the thousands of tests, you're going to have 50 failing. And the name of the test is the first thing you're ever going to see. And if the name is good enough, you won't have to drill down. You can just go and start fixing things and that's going to save you a lot of time.
[06:03] (external link, opens in new tab) It also feels to read a bit like like a specification. When I call add with an empty string, it returns the default value. I can make a sentence from that. Okay, that's how it should be. It almost reads like documentation. That's kind of the point. And then the arrange act part we have the arrange is where I arrange the objects under test. I might arrange mock objects (external link, opens in new tab) stubs (external link, opens in new tab) the state in the system. I might create a string calculator that does something.
[06:36] (external link, opens in new tab) The act part is the act part is me calling the unit of work the beginning and the assert part is the assert. Obviously assert equals is part of JUnit and there are a lot of other different asserts. The first part of the assert is the expected value. So I'm I'm reading that bottom sentence like this. Assert equals zero from result. Okay. Or assert that you get zero from result.
[07:13] (external link, opens in new tab) And I find it once you get used to it it's very tur and very easy to read. There are longer syntaxes out there, but I find that this is perfectly okay once you get used to it. so let me show you some features of JUnit. For example, JUnit has has a couple of other attributes that we can use that I would not actually recommend using, but you should know that they exist.
[07:51] (external link, opens in new tab) And those attributes are after class, before class, after, and before. And what they all have in common is that you can use them as hooks to do things before the tests run. after and before execute before each and every test. So if you have three tests in the class before will execute three times. Before class will execute only once. Even if you have a thousand tests, it will only execute once before all of them are run.
[08:23] (external link, opens in new tab) And why would you use these this these these attributes? Well, you can see here that people are calling collection clear on something etc. But that's not a good reason. Usually I I would usually see people do that when they initialize database connections, when they put things in something that's either in database, when they clean up static stuff. And that's very important because you would use these attributes when you clean up shared resources.
[08:54] (external link, opens in new tab) For example, you initialize database connection and the after class you kill the database or you roll it back to a known state. These only make sense when you have either legacy code (external link, opens in new tab) with static methods that you have to clear up static state or when you have integration tests (external link, opens in new tab) when you have to clean up shared resources file systems databases. Now when what we're talking about right now is doing unit testing not integration testing. So let me explain the difference. A unit testing is when you are able to run
[09:28] (external link, opens in new tab) The unit of work in memory. No file systems hopefully changed, nothing database, nothing network related. It's all happening in memory and you have control over everything. Your code has control. So if it's using threads, your code doesn't have control over threads. That's part of the system, the OS. And so you have to start killing threads and processes sometimes. And so then now we start having to wait and fork and join things and all that stuff. That makes it much much messier.
[09:58] (external link, opens in new tab) And it's my definition of an integration test. Everything that's not a unit test is an integration test. If it's not in memory, it's not fully automated, fully repeatable, if you don't have control over all the objects in your test, it's not a unit test anymore. And when I see someone uses these attributes, it usually means that they're not doing unit test 95% of the time. So it's a tell. The only other time when when you use it and everything is still in memory is when you have like singletons that you have to clear up between tests.
[10:37] (external link, opens in new tab) That's the only time where you it would make sense to use them. And I would usually not use the top two before class and after class. Those are specifically for things that are really hard to create, take a long time. I don't So usually these are the most giving away features of initializing a system that's in the integration test. The bottom two could be used for initializing and resetting static objects, but most of the time they're used for cleaning up some something that's integration test related.
[11:08] (external link, opens in new tab) And why do I care about the idea that in unit 10 and integration test should be we should know the difference? Because I separate them into two different packages. I put a unit test package and I create also an integration test package and I separate them. And unit tests always are in a package that's separate from integration tests. And the reason is is because I want to have what I call a safe green zone (external link, opens in new tab). A safe green zone
[11:38] (external link, opens in new tab) Is the ability for me to check out the code from source control, right click and run all the the unit tests and they should all be green. Make sense? They should all be passing. There is no reason for a unit test which you have fully control over to not be green when you check in something in the code. Integration tests are a different situation. Integration tests might require configuration on the local machine. They might require that something is set up something. And so some of them might actually fail even if you check out the code and run them for the first time.
[12:12] (external link, opens in new tab) And what's the problem with that? If you mix them up, if you have unit test and integration test in the same package and then you run them and three of them fail, what's the worst sentence that you can hear from someone say, "Oh, that's okay." Someone you see tests fail and someone says, "Oh, don't worry. That's okay." you're in trouble because you no longer trust (external link, opens in new tab) your tests. If you had unit test package that you know is supposed to be green and some of them fail, you should literally be worried.
[12:44] (external link, opens in new tab) There is no reason for those tests to fail and so you know you broke something. When you have integration test, you don't trust the results because it could be an integration test. Oh, maybe I'm just missing a database connection. Oh, maybe I'm just I didn't set up the Z drive, whatever. And that's kind of the thing is that when we separate them, we know that integration tests might be lying to us because they need configuration. But unit tests cannot lie because they run in memory. They should either be passing or failing. But we have control over everything.
[13:16] (external link, opens in new tab) And if they didn't change from the time they passed, then we definitely have a problem in the production code. So the safe green zone gives us the ability to trust the tests again by separating it to unit test and integration test. we have more trust in the tests because you know we have a bunch of tests that's always supposed to be passing and if they don't we are actually worried we don't say ah it's okay okay