What is going my good people of open source dev?
Have you ever dreaded those damn tests in school? Have you always put your heart and soul on the line to get that 'Pass'? Yeah, me neither...
This week's lab in my open source class we were tasked with creating unit tests for....
**queue drum-roll**
Our link checker program... Yes that again. Those of you who have no idea what is going on, good... you don't need to know. DeadLinkage (just incase you are interested)
Anyways,
As I mentioned, we had to integrate unit testing in our link checking programs. Let me start off by saying #$%! unit testing on java... OH MY GOD! What a pain in the ass this lab was.
I mean I loved the way java handles everything... But for real though. Unit testing on my code was a nightmare. I spent roughly 3-4 days trying to get JUnit (the testing framework for java) to cooperate with my IDE.
BIG MISTAKE
It was a mistake primarily because everything had to work on a CLI.
Anyways, at first I tried to get stuff working with JUnit5 everything was fine and dandy up to a point where I had to mock objects and classes such as the HttpUrlConnection and URL.
This posed a problem because the mocking API that I had settled on using was mockito. There was not a whole lot of documentation and examples of Mockito cooperating with JUnit5 so I decided to backtrack a version of JUnit to 4.12.
A good chunk of my time was spent walking back and forth while reading documentation on my phone about how mockito works. On my off time I would try code only to have it fail miserably.
I decided to take my problems to stack overflow and see if anyone else has run into this issue. I spent about a day reading through stackoverflow pages/questions and came to the conclusion that mockito, while a powerful mocking tool. Could not mock Final classes, the exact classes I was trying to mock (of course...)
While reading similar problems from other people trying to use mockito to mock final classes I came up on a forum that described an API that was used in parallel with mockito, Powermock. Powermock had an API of its own that works with the mockito API and served as sort of an extension to it.
So began my readings of the novel called "The Documentation". The way powermock is used was very similar to the way mockito is used to mock. Actually they were almost identical, slight variations from version to version in syntax, but that didn't pose much of a "threat".
OK, COOL.
Theoretically, it should work right? WROOONG!
I was so close to making it work until I ran into another problem. Version compatibility.....
AGGGGGGGGGGHHHHH
The first thing I did was ask this cool girl (who unrealistically also likes Java like me)... and a lot of other things but anyways. Nesa had no idea what the issue was and proved to be of no help. She actually said she hadn't started the lab herself so she had no idea what to do. Here is a little proof.
Thanks for the compliments Nesa, but nice habits don't write the unit tests. (or at least not yet)
So, powermock 2.x is not really compatible with most versions of mockito. The same goes for the opposite. A full compatibility list can be found here. (note: I did not find the compatibility list right away) Learn from my mistakes and get the right versions.Awesome! I found the right versions, downloaded the correct JAR files to stash in my external library list and now it was time to write some tests.
For my first unit test I picked an area of my program that would be easy to work with and wouldn't be dependent on many mocked objects that needed to be passed in. That area was my function to load an ignore-urls.txt file. The only parameter that this function needs is the filename which is given through the system arguments (which are stored as Strings) when the program is ran. I had to mock the file name being passed in which was as simple as making a string hold a "filename.txt" and running the function with that argument. The function returns an array list of strings which I then had to iterate through a loop and assert that they were returning the correct thing. Easy peazy.
Hazaah! and so I had written my first unit test. I ran the test (remember in my IDE) and everything was working correctly. I decided to try and run the test on the CLI. aaaaand more problems started occurring. I was not able to compile my test java file due to the terminal that I was using not being able to see environment variables. I took my problems to the very helpful and knowledgeable professor HumphD.
What do you know, the professor was right. Everything compiled fine in command prompt and another version of PowerShell. Thank you for the help! I was pulling my hair out and almost ended up looking like my buddy Chris.
It compiled, sweet! I only hoped that was my biggest issue at the time. I tried to run my compiled Test Runner only to have it crash and burn for missing dependencies. There was way too many for me to add and I did what every sensible person would do.
PROFESSOR! I NEED HELP... again.
I explained what the problem was and David (yeah first name basis, we are cool like that) told me something I dreaded of hearing ever since I started this damn link checking program at the beginning of the semester.
"Make a build project!" That is all I was hearing and my ears were ringing like I had been standing next to a speaker at a rave/club (ah what simpler times we lived in before all this pandemic stuff started happening).
Well I guess it was time to do the right thing. It was time to use a build tool like maven to "manage all this crap" as HumphD put it.
Integration into a maven project really was not that hard. The IDE (IntelliJ) I use actually makes it really easy for you to setup a maven project structure on a Java project. I will link it here, in case others are trying to do the same.Once maven support was added into my project I began to add the dependencies from the maven general repository, where you can find almost anything and everything. It is extremely easy to add a dependency into a Java project with maven... Literal click of a button.
Anyways once everything was setup, I ran the test I had written from before (The maven way) on cli and everything compiled and ran PERFECT! I was so happy I got over this hurdle and could finally move on after 3 days of pain and torture on my way back from the depths of hell.Oh man, maven opened my eyes to a whole new world with Java. Maven is now my new best friend. Everything is so easy with this build tool.
Anyways, I moved on and started to write my tests for the core of my program. Testing my CheckLink logic. These functions basically take in a URL (string) to be checked and a HttpUrlConnection to get the status code of said URL. Remember 10 minutes ago when I was explaining about mockito and powermock being used together to mock final classes such as HttpUrlConnection? Well, this is what I needed this mocked connection for. I had to mock the status codes for a specific url, I chose google.ca because why not. I set the status code of the mocked huc to 200 and 404 so that they would pass my goodLink and badLink functions and basically checked the output the function returned against an expected output I had preset. This was not that bad once I already had the knowledge of how to do this. Once I finished writing the tests I ran all tests and to my amazement everything works flawlessly.
I am so glad I spent a couple of hours rebuilding my project as a maven build. PHEW. In the long run it probably saved me so much time.
Next on the task list was to incorporate a code coverage tool. I chose to work with JaCoCo because it was the first one that popped up on google it was in the maven repository and was easy to work with. Guess how long it took me to get it up and running. Roughly 15 seconds!
MAVEN BAYBEEHH! but for real I just copy and pasted the JaCoCo dependency code into my pom file and specified the directory I wanted my new reports to go in and ran my tests. Boom I now had code coverage (although not a lot of code was covered with 3 tests LOL)!
Next up on the task list was to add integration testing via github actions. This was not hard at all. Github actually has already made templates for executing maven tests. I just had to change the platform that the project was being built/ran on from ubuntu to windows although I doubt that makes a difference anymore since I added maven support.
If you are interested in my workflow file it can be found here.
All I do in this workflow is the following:
- Setup Java
- Cache dependencies
- Compile the project
- Run tests
Running tests is dependent on whether the project compiles or not. If the project does not compile there is no point in running the tests. That is it. Pretty simple. Github documentation on maven CI was probably the clearest documentation I read throughout this whole lab. Anyways this is how executing the tests look like on each commit to master.
The next item on the agenda was to write some tests for a fellow class mate. I did not even give it a thought I messaged my bald headed friend Chris right away to ask if he wanted to work together on this lab as well. To my surprise he said sure. I really did think he hated me.
Anyways, as I have expressed my feelings about Python before (utter hatred). I asked Chris to give me a little crash course on using pytest to see how everything works. He did a great job of explaining everything. We then discussed what exactly he wanted me to write tests for. He mentioned that he would like tests for the functions that print out each status code...(like why was this necessary for you to make Chris) uhm yeah sure let me do that.
So I decided to add the following tests to ensure they were returning proper status codes and that nothing was broken. If these functions stopped reporting the correct code the entire program will break. so anyways this is what I came up with.
For:
def test_status_check_unknown(capsys):
- this function is used when a link is unknown
- I had to figure out how to capture stdout and read what was being returned from a test
- I also had to find the ansi codes to verify the texts match from stdout since the text color was white
The next test I just made a mockresponse() class and assigned a status_code of 300 and checked that the object had a status_code member which was set appropriately. I did this because the 'requests' library in python returns an object containing these details so I mocked that.
The rest of the tests are more or less the same, I just had to ensure the ANSI code was correct.
I tested this on my end using the Pycharm IDE, I also changed the function I was testing in question and the tests did in fact fail because the underlying functionality of the base function failed.
This reminds me of the matrix checker for our submissions in IPC/OOP/345.
FINITO!
Overall this entire process was a nightmare, I would never want to repeat it again.
HOWEVER, with that being said I learned so much about java, build tools, unit testing and integration testing. It was a really big learning curve to say the least but it is now done and I can finally do some Game Dev work for my final project. Thanks for the learning experience David. It really was a lot of involvement and reading on this one cheers.
Working with Chris was always a sad reality that I had to settle to work with this bald little man 😐 LOL I'm joking Chris, it was a pleasure as always.
Till next time,
XOXO,
Gossip Plamen