 Okay, welcome back to the second part of this testing lecture. As you might hear, I'm still a bit sick, so I might have to pause the videos in between to cuff and don't get too loud on the microphone, so I hope that's fine. Now we go into debugging here and then we do a bit of unit testing in this module. And you have, you should have worked with the debugger in the programming course already a bit, but most of you don't have much experience and debuggers are a bit like unit testing, something that you need to keep using ever and ever again so that you get used to them. They are initially a bit of a burden. You need to get used to them, but once you know how to use them, they're extremely powerful and they save you lots of time. So that's what we'll do here. To be somewhat successful, you need to know what you're looking for. And there are a couple of basic things that are very common that essentially have to do with finding errors. In JavaScript, the first step is always looking at the browser console. So if you ever come to me with a problem in your JavaScript application, usually the first thing I ask you is, what does the browser console say? Are there any errors there? Because quite often that's where you get an indication of where to start. So I've written a small application. That's it. It has an input field with an ID and it has a button which should call a function add element that is in my debug script here. And I have a div with an ID and this is where I want to add paragraphs. So the idea is whenever I click on my button, I read the text from the text field and I add a paragraph here with the text. The debug.js is also quite short. It's a bit longer than it needs to be, but I made at least two functions. So I have the function add element. This is called by our button in which I create a paragraph. I change the text of the paragraph depending on what get text input returns and get text input just gets the text from the text field and returns it. And then I get my div and I append the paragraph. So that's it. Now I've done a mistake in here, which means that it doesn't work. Since this is so small, you could probably find this quite easily without a debugger. But it's good enough to illustrate how it works. So let's have a look. If I press add paragraph, nothing happens. But as I said, first step, look at the console. You'll see that there is a type error down here. It says, div dot append child is not a function. And if this does not help us a lot, what definitely should help us is this information back here, which says that this error happens in the debug.js file at line five. So here we get the type error and say this append child does not exist. We don't know this function, which is great because now we have a starting point where to search. If you look at the JavaScript slides, this is perfectly fine. So there's nothing wrong here, which means the error is somewhere before that. And now we'll go and have a look how to debug that. Now, the naive approach to debugging is always to just do printouts. So you just print stuff to the console and try to understand what is wrong. That is fine. It's just a lot of work. And usually when you first have to locate the error and then you have to go into details, it can take a long time and you have to change your printouts until you get where you want to go. It's much easier to use a debugger, but in the beginning, this is very much helpful. So what you might want to do is print the values of variables because you know that, for example, this should create an object, a paragraph tag. So if this variable is not an object, it's not a paragraph tag, then something is wrong. And the same goes for the other things. If the paragraph ID, so I create an attribute here, if that's not, if that's, for example, undefined, something happened, if something is wrong here, then again, something went wrong. So it can help us to tell exactly which line causes the problem. And we have similar strategies when using a debugger. So looking at variable values over time to localize the problem. And typical things are, as I discussed, looking for whether variables are undefined or null, even if they should not be. Looking for if you are in the wrong branch of your program. For example, your if condition is wrong and you ended up in the else instead of the if branch. Or everything is fine, but the outcome is not. So then you can ask yourself, are you using all the libraries correctly? Or are you using, for example, the wrong function here? So those are things we might want to look for. Let's start by doing the naive console output. And in this example, that will be perfectly fine. So we can do console log. And I just log the values of all our things that I'm changing. So here, I'm just logging the paragraph, the text of the paragraph, and the div. And we know that here we already have an error, so we don't need to continue. And we can also go in here and just in case check is our text field correct. Is this actually working? Now if I reload and if I press, now I get a lot more output that I can look at. And you see that the first print is my P tag that's up here. So we know that the paragraph that I've created looks good. It looks like a P tag. It has lots of attributes. So on this level, it looks okay. This is my text field. So that's the print down here because I've called the function here. So we're down here, we print the text field again. This looks reasonable. Then we get this ASD whatever, what I put in here. That's the print up here. So that's the text content. Again, this looks correct. And then finally, and this is where it gets interesting, our div. We would expect something to look like this, like a div tag. But instead we get something that says HTML collection length zero. So it's some kind of collection of HTML elements that has length zero. There's no element inside. So it seems like we did not get our div. We did not get it. Something went wrong. And now we have effectively located the problem here in line seven, where we do the print. So here, something is wrong. And now we can look in detail and we actually see that. I'm trying to get elements by tag name. That returns me a collection. So it returns me an array of all the elements that have this tag name. What I want to use is get elements by ID. I want to get a specific element with the ID main container. So I've simply used the wrong method here. And because there is no element that has a so-called main container tag, which does not exist, but it would be something like this. Since we do not have any of that, we also don't get anything back. We get a collection of length zero back. So instead we want to have get element by ID. And now I believe there are no more errors, so this should actually work. So you see that now this looks much better down here. The print line and indeed the paragraph appears. So you can see if I change the text, then I'll get a different paragraph down there. So this was okay. But as I said, this can take quite some time. Because the problem is, if I have, for example, a lot of functions, I might not know exactly in which function my error is, so first I have to locate it. And then even if I have located the right function, I don't know exactly how my error looks like. So I might have to change my print lines all the time. And this just takes lots of time. So it's much better to use a debugger. And that's what we'll do. Because essentially you can adapt very quickly. You don't need to change your printouts. You can execute your code step by step and see what happens. And I'll show you two ways here. I'll show you in Firefox and in VS Code how to do that. But the principles are always very similar. So for example, in Chrome, it looks essentially the same. Now, in Chrome, you'll find the debugger on a developer tool source. In Firefox, you find it under debugger in the web developer tools. And what you always do in the debugger is you set breakpoints. So you go into the code and you click on specific lines. And if you have such a breakpoint, it means that whenever the code, the execution is getting there, it's paused. There is a break. And when you have a break, you can look at all the variables. So you can check out everything. You don't need to print. You can just click on it and you can look at all the details. And in particular, you can look at details that you did not know you would need before. So that's quite useful. And then you can either you can just resume. You have a play button. You just say, OK, please just continue. Or you can say, I am now doing stepwise execution. So please go to the next line. Please go into a function. Please go out of this function or so on. I'll just demonstrate that a bit. An important thing is just in case you run into this problem, the debugger, at least in Firefox, only works on external JavaScript files. So if you put any JavaScript code into your HTML, the debugger will not quite work, just so you have been warned. So let's first maybe put our arrow back in place. So this was the problem before. And now it again does not work. Now let's go to the debugger. We need some more space to actually see it. We still have the console down here because it can be useful. And now we have all the different things we can look at here. Down here is our debug file. And you see that happens to be already a break point that I put in. But essentially, if I click on any line here, I can set a break point. You will see that some lines don't work like here because there is nothing. And then the function headings don't work either because those are just declarations. So if you want to stop whenever the function is executed, you just click on two, the first line within that function. And that's what we'll do here. So every time now add element is executed, which means every time the button is pressed, the execution stops. And then we'll have a closer look. So let's try. We click and you see it says paused on break point and the thing here is frozen, nothing happens. And now we can look into the details. Now we can look at the interesting stuff. And that's over here. So what you see down here in the scopes are all the different variables. So you see, for example, our diff and para values variables, they are both already defined or they already exist because of hoisting. So they have been moved to the top of the scope. That's why we have both of them. They are both undefined because I haven't yet assigned anything. So this line here just to clarify this line has not executed yet. We just reached it. So this document dot create element is not processed yet. And then we look at, for example, all the different things that exist in the window variable, which is like a global variable in the browser. There is a lot there. And there's a lot of other stuff we can be doing. So that's not interesting. But the maybe interesting thing here is the call stack that shows us how the function has been called. So here it says that the add element function, which is in line two of the debug script has been called from an on click event in the source file in the HTML file. So we know that this was the event that actually triggered this function. Now, here are the things I mentioned in the slides. So we can now jump over this line. We can just say, okay, please go to line three next. Let's do that. And now we'll see that para actually has a value. It's no longer undefined, but it actually is a paragraph now and it has all the defined attributes. And we also see, for example, now the text content should still be empty. You see it's an empty string. Nothing is in the paragraph. If we again jump to the next function, then the text content should have changed. So now it's whatever I had in my input field. Now we're now interesting line. So if we again step over that one, then we'll see that the diff has this HTML length zero. And now we can start maybe thinking about what went wrong here. So we see that something did not quite work and we can look at the code and say, okay, this is actually the wrong function. And once we have figured out what the problem is, we just resume the operation. The other thing, maybe just to demonstrate it, if I click on it again, are the other two options here. Now I'm at this line. So I'm about to execute this get text input. And instead of just jumping to line four here, I can say, well, please jump into that function. So suddenly I'm now down here. So this allows you to vary how detailed you look at different things. Maybe you know that this function works very well. It has been tested a lot. So you don't need to look at it. You just continue. But if you're not sure, then maybe it's good to actually go into the details. But that's what we'll do here. So I'll actually look at these things. I can jump to the next. If I now say, step over, then we'll actually go out here and go to line four, which is exactly the same as if I use this one, which says, well, I have seen enough in this function. Please go one level higher. So go back here. If I do that, then I'm back here and I go to line four instead. So those things allow you to jump into functions. You can also do that with the system functions like these get elements by tag name, but it's usually not very helpful. Okay, it doesn't actually work in this case. I think usually it allows you to look at them as well, but it's not very interesting. Okay, so that's a very short introduction on the debugger. It looks exactly the same more or less in VS code. So I'll show you that as well. Let's remove my line here and I'll just go back to VS code. You note here that you have this bug symbol. And if you click on it, you're in the debugger and you'll see it's a bit similar. It has this call stack. It has variables. We have our code. And actually when you hover over here, you see this red light, which is the indicator for a break point. So I can click on here and I'll get a break point. Same as before, the only thing is you have to somehow launch it. And for that, you need to add a launch configuration. So you actually need, you can look up in the documentation how to do that exactly, but you just need to define how to launch your debug script and which tool and so on. So now I'm actually launching debug.html with Firefox again. So it's not that different. But once I'm done with that, I can say, okay, please launch debug.html. Let's go. And you see down here a new Firefox windows opening. Everything is here. And you see here, I've set the break point. So now whenever I click on this, it goes back here and I get something that looks extremely similar to what you have seen before. So here's our call stack on click and element. Up here are all the variables, same thing. And here is the visualization where we are. And we can again, we can jump over to the next line. We can jump into functions. We can jump out of functions. So there is nothing different here. And okay, we get the exception visualized. The only advantage is of course you're working in your code editor and it's much more integrated than always switching to Firefox. So that can be useful, but otherwise it's basically the same thing everywhere. They all look very similar. Okay, so that's a short excursion to doing debugging. And I really encourage you to do that, to try it out because you'll get so much more effective in your programming that if you do all of that by just doing printouts or trying to read the code. Okay, the next thing we're doing is unit testing. And we'll be in this course using Mocha, which is a unit testing framework, which means it's some kind of library that allows you to write and run tests in an easy way. It has a fairly easy intuitive syntax and it requires to use some research library. That's always the case in unit testing. You have in every test, you typically do a search and so you basically do checks. You check whether, for example, a return value is what you expected. And one of those assertion libraries is CHI. So we look at that. CHI allows different styles according to, for example, the test-driven or behavior-driven development paradigm. So the syntax is a bit similar to writing a sentence. You'll see that. Again, these frameworks are not anyhow perfect or the best, but it's a selection. And whenever you use a different unit testing framework, you'll recognize a lot of similarities. They are all very similar. You can look at the documentation on these websites, but we'll just jump right in. What we have is, in this case, function as basic as it gets. It should square value and return it. So it just returns x squared. Of course, you don't need a function for that. You could just write it like that, but this will help me to explain unit testing much better. What you see below is actual test code. So it is cryptic, but it basically describes a test suite. So a collection of tests, basically. And this is just a string. So it says, we are testing the square function. And then it has a callback function. And that just has to do with how the framework works. So it expects a callback here. And then in there, you have single unit tests, which are, again, similar structure as before. It just says it. And then it has, again, a string, which typically is written like it should do something. So the function that we're testing should return the square of a positive input. Add again a callback and in there, we're doing something. What we're doing in there is we're calling the function. So you see square five here. That's our actual function call. If we want to test it, we have to somewhat execute it. And then we check the return value. And we do this with the Chai assertion library. And as I said, this looks a bit like a sentence. So we say, expect the square of five to equal 25. So what the test does now, the test framework, is it looks at all of these collections. For all of these collections, it runs all the tests and it checks that the assertions are correct. And if they are, the test passes, otherwise it fails, more or less. So on a rough level, this is how it should look like. You have these it statements that contain your tests. If we look at that in code, I have an application here. So here's our square function. We'll do something else later, but that's good enough for now. And I have my test script here. It's a get a JavaScript file, but it's in a different folder. And it's in a different file. So that's of course useful because you don't want to mix your test code with your regular code. They should be separate. And you see that I have done one of these describes for each of my two functions. So basically one test collection per function per unit. And in the first one, I just said, okay, the first test should return the square of five, which is 25 and the second one is testing for negative squares. So it should return the square of minus five, which is also 25. And now I want to run this. Now, how do I run this? That's a bit complicated because JavaScript or so far a client side JavaScript is executed always in the browser. So we also have to execute our tests in the browser. And for that, we have to write an HTML file that contains both our actual program. So we import the my app and our test code. And then there's a whole of other stuff here. We have to include the libraries because our browser has to know what to do with these weird statements with this describe and it and so on. And for that it needs the Chai and Mocha frameworks. Mocha also needs to somehow be initialized. It has come somehow different styles. It's not that important. They are all equivalent. It's just what you prefer basically. And as I said, to include the application code, we include the tests and the stuff up here is just styling. So Mocha has some CSS, has some CSS code so that the test results are displayed in a nice way. And to display them, it requires that you have a diff with this ID. So it actually means your application is running in this HTML file, but at the same time the tests are running and are being displayed. This code, of course, you will just copy in the beginning because this is nothing you should be memorizing anyhow. Okay, so that's how it looks like. This is all we need so far. So let's execute that. So we have our tests in our test file. We have our app here and we have the Mocha HTML which now includes the whole thing. So it includes the style sheet for displaying the results. It includes the diff for displaying the results. It also has a button that executes square but this doesn't really matter. Then we download the two scripts. So the frameworks. We initialize, we set up a Mocha. We include our application code and we include our test code and then we just say mocha.run. So please run all the tests. And it means all the tests you can find in any of the included JavaScript files. And if you look back just to confirm we have two test suites here and we have two or three tests in total. So this is what you should be seeing if I now open this here. So I see squared, it's my first collection with two tests and you see async squares. That's the second collection with the third test. So that's exactly the test results you see. If we look at the inspector you'll see that all of this is within the Mocha diff. So these are our test results. That looks basic but you can actually look at the details. So you can click on this and see what it returns. We can also, if we break any of that, so let's say for example our square function happens to be really stupid. It always returns 25. So now actually the first test should be fine because we happen to have hit exactly the right spot. But this one, now this one will also be fine so that's not very good. Let's just do something that doesn't make sense. Let's return x again. So now obviously will not work. And if we reload you'll see that the tests fail and actually we get interesting information that can help us to debug. So it says expect that five to equal 25. So we already know that five was returned. Asserts an error, blah, blah, blah. And this happened at line three. So we can look into the test of what exactly happened. And if we go into the tests line three, we'll see that okay here there is a problem. Maybe we need to go into that function and debug it. And then of course we go in there and we see that we have forgotten something. So that's how this works in theory. You can just write a lot of tests. There are some additional things that I want to mention because they are fairly common. One of them is what is very common. For example, let's say you test something that has a database connection. You need to initialize something. You need to do something and you do that before every single test. For that, there is like every single unit test framework I know has these different functions that allow you to run code before the actual tests or before the actual test collection. So Mocha has the same. It has a before function, it has an after function. It has a before each and an after each function. So the before is run when the test collection starts. The after is obviously when all the tests in there have been run. And the before each and after each are the same but for every single test. So before each would be executed once before every test. And this can be helpful if you, for example, have to have some kind of global variables or anything to keep track. Or if your program happens to have a state and you somehow need to reset it or something like that. That's what you have these functions for. The other important thing is, I mentioned that Mocha has different styles and in the example here, I've initialized it with the BDD style. And the BDD style is the one that has this describe and it syntax. That's just how it looks like. There's also a TDD style which just uses slightly different naming. It's really exactly the same thing. It's just that some people prefer certain keywords and that's why they prefer to write sweet instead of describe or they prefer sweet setup instead of writing before. So unless you really want to use this, you can just go with what I've done on the previous slides and there's nothing wrong with it. But it basically means that Mocha is a bit flexible with how you write the tests. And you also find that in the documentation that there are slightly different ways of writing the assertions. It still works. Now that's one interesting thing and that is asynchronicity. How do we test asynchronous functions? And the best way of showing that is just to show what happens if I do it in a regular way. So I'll just remove my correct function down here and I'll say, okay, what I have here is an asynchronous square. So it's the same function as above. It returns the square, but it does so after three seconds. So we just kind of pretend it takes long. This might sound stupid, but it's not uncommon to have certain mathematical functions that take a long time. For example, if you want to calculate prime numbers or so on factorization, that those things take time that they're done asynchronously. So basically you call the function. You do not wait for a return value, but instead you send in a callback. It's very common. Here, of course, the artificial time out is just to pretend that something that takes a long time is happening. So what happens is just this function returns the square after three seconds. Let's try to test that in exactly the same way that I did before. So I say try expect square async, now my other function, minus five to equal 25. So this should just return 25. Let's test, and it says expected undefined to equal 25. And that's already interesting. Now, apparently this is undefined. And this is simply because there's no return statement here. It doesn't say return anywhere. And that means that, well, what this function does is it actually implicitly returns undefined. So this is what happens. Which means obviously that our check we compare the return value undefined to 25, does not pass. So this is already something that's not good. This is not how we test an asynchronous function. Now, we could do this a bit different. We could say that, well, square async actually expects a callback, right? So we could just say, well, the callback is a function. And now we change this a bit. And we say, well, whenever the function returns, so we just wait for it, then we can do our, we just expect that the parameter that is return equals 25. Okay, so now we've just said, okay, well, if undefined doesn't work, then maybe we just execute the function as an asynchronous function with a callback. And whenever the callback returns, we just check that the parameter equals 25. That's what the function does, right? It sends the square back as a parameter of our callback function. Now, if I execute that, it takes some time. And it says, error timeout of 200, 2000 milliseconds exceeded. So what's the problem here? The problem is that Mocha waits for two seconds. And then it says, if I don't have a result, it means the test is broken. We don't wait any longer. To change that, I can manually change the timeout. So I say, well, let's wait longer. This is milliseconds, so that should be more than enough. In this case, we know that, well, actually if it takes longer than three seconds, then maybe we just let the time at the pass, the test pass fail. So if you do this again, la, la, la, it waits, nothing happens, timeout exceeded. So again, this somehow hasn't worked. It actually complains, ensure done is called. And that's something I have here already. That's why it didn't work. So now it looks exactly like it texted before and you will actually see that it passes, which is great. But it's great for the wrong reasons. So if I fixed, if I break my test, if I say it should return 25,000, now it should obviously fail. But you see that it still passes. So I can actually do whatever I wanted here. Should return a string, no problem, it returns a string. Why is that? The reason is, this is a asynchronous function and as soon as it executes, the test just continues. So it goes here to line 17 and it says there is no, there is no assertion. There's nothing I need to check. So basically the test just passes and it passes irrespective of what's going on in here. So I could do a fail, I think. Let's see whether it exists here. No, it doesn't, maybe it does. No, it does not. Anyway, I could write in something that's obviously wrong. Far expect false to equal true, for example. And this would work. So something here is wrong. And to avoid that, we need exactly the keyword that I had before, we need this done parameter in our callback and that's a specific function that was passed in that we can use to test and to tell the testing framework that we're done. So what happens now is that the test actually waits until either this time has elapsed. So if it takes more than five seconds, it just says fail or until we call done. So in this case, it will wait until we get here. It will check our assertion and then it will end the test. And if no assertion is wrong, it will pass it. So now it waits and you get expected false to equal true. So this one failed, which is good. It should fail. But now we can go back to our actual behavior and say what the function gives us back. The parameter should be 25. It should be the square. Now the test passes. It has this red flag that says this took 3,001 milliseconds. This took a long time, which is exactly the 3,000 milliseconds our function takes. So now we have successfully tested an asynchronous function. So basically you need to make sure you call it correctly. You actually wait for the callback and you check within the callback your assertions. And you need to use this done parameter, the function to tell Mocha that, okay, now we have done executing this test, please finish it. So those things together make sure that you test asynchronous functions correctly. What you can always do to a sanity check that you're actually doing correctly is to mess with these parameters. So obviously this test should not pass anymore now. And if I've done it correctly, it will fail. If it still passes, it means my test is wrong. And that's something you should always check. If all your tests are passing, check whether you can break them or the other way around. So to somehow assure that your testing works. Okay, so that's also summarized here that you can use the done callback to test asynchronous functions. And that's it for unit testing. Now in the last part, I'll just give examples of system and graphical acceptance testing as the last part of this lecture.