The Your First Language pattern, discussed in chapter 2 of Apprenticeship Patterns, concerns the idea of someone picking their first programming language, how they should choose it, what they should do with it once they become proficient, and how it might affect future efforts to learn other languages. I thought that this aspect in particular was very relevant to my own experiences. I first started learning to program in Java, and as a result I find that I tend to look at things in relation to how they might work in Java rather than from the perspective of whatever language I might be working in.
Regarding the text, Apprenticeship Patterns discusses the idea in terms of how it might play into someone’s career over time. If you learn one language first, and put a majority of your development time into that language, then it makes sense that this would impact your views and understanding of other languages. Various techniques are offered in terms of actually learning the language, including building of toy programs (similar to those discussed in the breakable toys pattern), learning through testing frameworks (implementing test cases and using them to understand how the language works), or finding mentors who are already experienced in the language to provide guidance and advice.
In terms of how this is relevant to myself, I certainly feel that starting with an object oriented language has made me more predisposed to prefer this style of programming over others. This can at times make me feel out-of-depth when working with other styles of programming language (functional, scripting, procedural) as object oriented programming is the most familiar to me. The authors of Apprenticeship Patterns recommend trying to learn as many different style of programming language as possible to avoid getting “stuck” in one language.
Going outside of the “comfort zone” so to speak, is a good way to broaden your range of experience relative to any topic in a general sense. In terms of using this approach myself, I would likely first start by taking a smaller step away from more familiar Java-similar languages (python, ruby etc) before moving on to something completely dissimilar to Java (F#, Lua, R etc). This would help broaden my area of knowledge and allow for a greater range of potential solutions when developing software.
Boundary value testing is often seen as an ineffective testing methodology in comparison to other techniques. And while other techniques such as equivalence case testing might be better at testing in some situations opposed to boundary value testing, I believe there are some situations where boundary value testing is a better solution.
For instance, in a situation where you want to test that one particular variable (ie: price of a product in a product catalog) which should exist within a certain definable range (suppose price should stay between 49.99 and 28.99 depending on sales or coupons) then you have both a minimum and a maximum value already specified, and a nominal value can be as simple as the value halfway between the minimum and maximum values. In situations similar to this, boundary value testing has the potential to be much easier than some other testing methods, such as creating partitions and going through equivalence case testing.
On the other hand, in a situation where you want to evaluate a boolean value, boundary value testing makes no sense whatsoever. Since the output will on ever be true or false, there is no obvious specified minimum or maximum. And without a minimum or maximum value it would be difficult to find a nominal value which represents typical output for the variable/function being tested.
Additionally, when working with variables, sometimes the output may have an obvious boundary in one direction (ie: maximum age of a bond is 10 years) but no specified boundary in the other direction (no specified minimum age of a bond). In these cases, a limit will often have to be chosen as a minimum or maximum based off of the functionality of the software or what it’s purpose is (ie: bond cannot be less than 0 years old, can say minimum is 0).
This can be confusing at times when the upper limit is not bounded, forcing you to choose an arbitrary upper limit (ie: price is the amount someone pays for a product, specified must be greater than 0, but no specified maximum price) such as 10,000, 50, etc. This can be somewhat subjective and might vary significantly depending on the context of the program being tested, and can often introduce complications.
Regarding disadvantages, boundary value testing is a poor choice for boolean values as previously discussed, and is also ineffective when working with dependent variables (if you try to use BVT with a dependent variable, you would likely also have to test for the independent variable associated which could be better accomplished through other testing procedures).
Conversely, the advantages of boundary value testing largely involve how simple it is to implement and execute, relative ease of finding lower and upper boundaries (often specified within the program being evaluated), and ability to use more or less test cases as needed (robust boundary value testing could provide a more comprehensive analysis than normal, and worst-case robust BVT could provide even more detailed information).
Because it is often dependent on the inclusion of obvious upper and lower limits, and can’t be used effectively with boolean values, the applications of BVT are not universal, but when it can be applied effectively, boundary value testing is easy to use and can provide representation of the majority of expected inputs.
Software testing often involves the process of writing tests in order to ascertain the proper functioning and capabilities of software, within expected operating conditions. When coming up with test cases for a calculator program for instance, you might find it important to test that the addition of two numbers results in a correct sum following the operation.
Testing against the expected value of two arbitrarily chosen numbers (ie: 5 + 4 should equal 9) would be able to provide insight into whether the calculator is functioning as intended, but you could also choose to test for addition of negative numbers, negative and positive numbers, addition resulting in 0 and so on. Because there could be a large number of potential cases to check for, it might seem unclear what exactly would make for an optimal or objectively “good” test in this situation.
The process described in this article follows a series of steps, first identifying and considering risks involved with the program, things which could go wrong and which could affect the program as well as people or entities dependent on the program (users, other programs which access the one being tested).
Following the identification of risks, forming test ideas is described in terms of asking questions. So good tests would need to answer questions about the software and it’s functionality, maybe entering in a negative number, or something which is not a number in terms of the calculator example. This allows for unexpected behaviors to be considered even when they might not seem readily apparent.
Next, the test should be conducted or executed, and depending on whether or not the test reveals any relevant information (do you learn anything from it?) the information gained can be applied to improve the program, or further testing may be required if the test did not reveal anything.
I would say that this process makes sense as a basic framework to follow when writing test cases. In some cases, such as with very simple applications or non-commercial development, the risk can be practically nonexistent; the risk aspect may not always apply to every situation. But in general, this article brings up some good points and provides a helpful sequence of processes which can be followed and repeated as needed.
The “Reflect As You Work” pattern in Apprenticeship Patterns has much to do with developing a methodical and concrete approach to introspection within one’s software development career on both a macro and micro level. On the micro level, Hoover and Oshineye make the recommendation to consider your day-to-day practices when programming and to take notes on the practices of more senior team members to see what makes them so effective. On a macro level they describe what amounts to a hotwash or after-action review of the operation (to borrow government idioms) but caveat that a certain level of trust by management is necessary for the approach detailed and that may not always be the case.
One of the exercises in this Apprenticeship Pattern I finally found to have immediate utility in my life is accounting for the things that I do or do not do adequately in regards to programming. The exercise as described by the authors:
“If there is something particularly painful or pleasant about your current work, ask yourself how it got that way, and if things are negative, how can they be improved? The goal is to extract the maximum amount of educational value from every experience by taking it apart and putting it back together in new and interesting ways.”
Like many of the Apprenticeship Patterns and the exercises contained within them, much of the immediate implementation is lost due to the apprenticeship patterns pre-supposing that the reader is currently writing a meaningful amount of code to have established a personal pattern. Despite that fact, I’m able to use this exercise to dissect my personal habits prior to entering college which were things such as poor commenting, not adopting to the bracketing style of the language, not using tests, not using git, and CI/CD. What was pleasant about my past work was my consistent use of the Microsoft Visual Studio IDE to debug and step through my programs. Unfortunately, I’ve been able to write substantially less code since I made the choice to return to college but look forward to using the exercises that I learned about in this apprenticeship pattern to maximize the value in learning from my mistakes.
Decision Tables have been one of the tricker concepts for me to understand and work with, and was one of the tougher sections on the recent exam for me. I think part of it is me mixing some of the terms up, such as at the conditions and rules, which leads to weird looking tables or I would just get stumped. I wanted to read more articles about decision tables to better understand what I else I might be missing and how to improve.
I understand the main concept of using the tables as another way to show different combinations of values for the variables, with the conditions acting similar to the previous boundary and equivalence classes that we have been working with, and actions simply being the output. So I think it’s the idea of the rules and what goes in those columns that is what I am having trouble with.
At first I though that rules were the classes and was confused on what the conditions were, but after understanding the difference between them, I had a better grasp on making the tables. I was able to set up the conditions and actions after this, and now I wanted to focus on understand rules and what is put in the columns.
The rules themselves are similar to case numbers from boundary and class testing, where they identify each combination of input and output values. Because of these, we are usually able to group rules together that have several input values matching a condition and have a shared output , such as multiple values that fall in the range between 0 and 10 that match an action.
I have a better understanding of decision table testing now after doing more reading on the subject from other articles and activity 8 and seeing my mistakes with my understanding of rules, possible input and output values, and the conditions and actions.
During my introductory programming classes, I tended to think that simply running my code and using random values as input to see if any errors popped up was more than enough to ensure that my code was (at least at the time) sound. Perhaps, in ideal circumstances this could be an ideal testing method. After all, if I had an inkling of an idea of what is considered a valid or invalid input, I could simply use some somewhat random input values, see what happens, and call it a day. It works, right?
As tempting as this may be, I am still working on understanding how functional software testing works, what techniques exist and can be used, and how to implement the tools that are created specifically for functional testing. In fact, I did talk about JUnit testing in a previous post, which can automate the process of testing for valid and invalid inputs that we provide to the software. If we were to just use random values without having a specific pattern in mind while testing software, what conclusions we would end up with by the end of testing would not mean much in the long run.
One efficient technique of testing for valid and invalid inputs is Boundary Value Testing or Boundary Value Analysis (two terms that I may be using interchangeably), a software testing technique for which test cases are being written based on what the programs define an acceptable range of inputs to be. To be more specific, when using boundary value testing, a tester may use the values at the boundaries of a range as inputs. In essence, we use the range or set of acceptable values for any variables in the code as the test cases, rather than simply coming up with random valid or invalid values at our own discretion. Moreover, to conduct boundary value testing we take values near and at the boundaries of a defined range [min, max], with some such values being: 1) max and min: the values at the extremes of a range 2) max-: the value just below the upper limit of a range 3) min+: the value just above the lower limit of a range Boundary value analysis can include more values near the extremes of a range based on the needs of specific test cases, thus increasing the testing complexity, though the above mentioned are actually very common among all variations of boundary value testing, which may even include values that fall outside of the valid range. The above values fall within the acceptable (or valid) range of values, thus if they are provided as inputs during testing, we can expect to get reasonable outputs and behaviors from the software in return.
Here is an example of boundary value analysis using mathematical sets: Suppose we have the variable x of type int that has acceptable values within the range [a,b]. This essentially means that we can define the valid range of x as {x: x ≥ a and x ≤ b}, meaning that any value within and including a and b will result in the program behaving in a reasonable manner. Any value that fits the invalid range of {x: x < a and x > b}, on the other hand, may result in the program throwing exceptions.
A simple example of boundary value testing being used in JAVA is the following:
Though boundary value analysis may not absolutely prevent errors and bugs from occurring while testing software, it is an extremely good point of reference that a tester can use when they are testing code. If one expects the software to behave in certain ways depending on specific value intervals, it can be fairly easy for the tester to adjust their approach around such specific intervals and have a better understanding of the software they are testing and how it should work.
In this second assignment for the software quality Assur & test we had to practice writing more Junit5 test cases. We had given three java classes, product, customer, and order. Our job was to write Junit5 test classes. Different from the week before this time we had more test cases to write. I liked and disliked this assignment because I enjoyed writing the test cases but the part that was not my favorite was running gradle in the project. It gave me a lot of trouble until I figure out what was wrong with it.
I believe that like me, many students or developers like to write Junit5 test cases. In our assignments we just scratch the surface of the features offered by JUnit 5. To find out more, go to the JUnit 5 documentation, it covers a huge host of topics, including the features we’ve have used until now and many more in detail. What I personally like about writing in Junit is that it follows a modular approach, which makes extending the API easier. It provides a separation of concern, where writing tests and discovering/running them is served from different APIs. In essence, three main modules exist within JUnit 5: JUnit Platform + JUnit Jupiter + JUnit Vintage.
Another obstacle that I had for this assignment was running gradle in program. Gradle is an open-source build automation tool that is designed to be flexible enough to build almost any type of software. What I like about it is that gradle avoids unnecessary work by only running the tasks that need to run because their inputs or outputs have changed. When I run gradle in my computer some of the test cases failed even though they passed in my environment. The main problem was in one test case. My solution to this problem is think another way you can write your test case, and that’s what I did. I changed the test cases and added some import statement that were missing and no issue after that.
Overall, I enjoyed this assignment. I like writing Junit5, but I don’t enjoy gradle. I had used it before and always causing trouble for me. Hope in the future that things will run smoothly.
The second assignment I worked on in my Software Quality Assurance and Testing class combined what we learned about JUnit 5 testing and using Gradle to automatically run all the tests fast and easy. This assignment proved to be more difficult than I was expecting, but in the end, I got very good at it and learned what I needed to. Downloading Gradle in the first place was my initial issue, since it seemed much easier to get it with a Windows machine than my Mac. Some classmates were kind enough to assist me and let me know about a software called Homebrew that basically is able to download Gradle for me. This was so helpful, and it was installed in no time! The second thing that make this assignment more difficult was that the actual tests I had to write were much harder than my first assignment. It took a lot of debugging and testing over and over again to finally get all of the tests to pass correctly. For some reason, many of the errors I ran into were primarily because I have made silly mistakes like grammar or syntax faults. All the repeated testing and debugging made me so much better at writing the methods correctly. The last thing that I would like to reflect on from this assignment is something I should have covered in my previous blog post. It is that pushing my projects to GitLab has changed. Of course, it has not changed that much, but just slightly. Rather than using the command “git push origin master,” I now have to write “git push origin main.” This is not too big of a deal for me, but at first I did not understand what I was doing wrong because of my pushes not working. I believe all of these things together will be a big part of my first exam in this class, and because of that, I am happy I was able to briefly discuss them in this blog to get a little extra review on them before that date!
In my previous blog-post, I talked about the importance of testing any product in order to ensure that it meets certain qualities and specifications, as well as work as intended. Software is among such products, as software goes through extensive testing before it becomes available to a wider range of users. Software testing includes conducting functional and non-functional testing, with the first focusing on testing if software runs as intended (by producing expect outputs and whatnot) and the latter focusing on non-functional aspects of software like performance. Today, I want to focus specifically on functional testing, more specifically on unit testing.
Although I wish to spend the rest of this post talking specifically about unit testing related to the JAVA programming language, I want to mention that unit testing is not exclusive to one programming language. During my earlier academic years, I learned to write unit test cases on C++ using CxxTest, an experience that I was able to carry over to JUnit5 as I started learning it in 2021.
Unit testing is a method of software testing that focuses on individual units of code. When testing for code written in object-oriented languages, a unit can be a function, a class, or an interface. While a novice programmer would simply run the code and provide their own input in order to determine whether the software behaves as expected, a programmer that is more familiar with using unit testing can automate the testing process by creating test cases for the units of code they wish to test, which would provide better coverage and much more insight to the tester regarding the behavior of the code compared to the feedback that simply running and inputting values to the code would provide. Though this method of testing is called “unit” testing, that is not to say that each function has to be tested in isolation. On the contrary, unit testing can be a very useful tool that can help a coder understand how each function interacts in tandem with other functions, which can make it much easier to troubleshoot and implement fixes.
As I mentioned earlier in this post, I began learning how to write JUnit5 unit test cases sometime earlier this year. Fundamentally, writing JUnit5 test cases is exactly the same thing as writing any other class in JAVA: it involves library imports, class definitions, instance variable and method implementations. Not too dissimilar to what a newer programmer would do when learning JAVA.
Depending on the kind and the complexity of test cases they could be writing, one could utilize the same functionalities that are used in the source code. The only difference is that JUnit5 introduces more functionalities that are used to run test cases. Such functionality is is the Assertions library. This method does the very thing a tester would do if they chose to run the code directly for testing; it checks if the value of output provided after execution is equal to the value a tester would expect the program to produce. Because it is possible to utilize multiple assertions in multiple test cases or for even multiple expected outputs, which can help save a tremendous amount of time of testing.
Though there are multiple other functionalities (such as annotations and whatnot), JUnit5 testing essentially boils down to its assertions; after all, it is the assertions that will show whether a test case has passed successfully and produced expected outputs, or has failed and has shown what has potentially gone awry.
The below image contains an example of a JUnit5 class with a test case:
Source: JUnit5 User Guide
One potential disadvantage that JUnit5 testing might present to programmers without much experience, such as myself, is that the order multiple test cases are run is not sequential. Rather, it is up to the compiler to run the test cases in any order it wishes to, which can be somewhat frustrating when the success or failure of a test case depends upon the success or failure of a test case(s) that exist(s) before it. Luckily, JUnit5 does have a way to run test cases in a specific order depending on the needs of the programmer who is writing the test cases.
Overall, learning how to write test cases can be a blessing for a programmer. Though this might not be necessary on programs of a smaller scale, it is extremely important to ensure that more complex software is tested thoroughly enough so that it functions at the best of its capabilities. After all, this blog is involved with Quality Assurance and Testing, thus making unit testing a very vital concept to be well-versed in.
With the semester nearing its end, I do not have many more apprenticeship patterns to review on here. Because of this, I am trying to choose patterns that have the most relatability to me. For that reason, I chose to review the pattern in the book entitled Sustainable Motivations. It is all about developing your technical skills in order to maintain your ability to work well given different project goals and situations. The problem that I am trying to solve by learning this skill/pattern is the fact that this line of job is often rigorous, difficult, and stressful. I need to learn to love what I do and be good at what I do when I am not loving it. The book explains how there are many days, weeks, and months where my job will be extremely fun and enjoyable, but that there are also many times when it will be quite the opposite. The important thing is for me to keep moving and working through the not-so-good times and continue to stay motivated as much as I can throughout the process. The book has some great tips and examples of how to stay motivated during the harder times. First, I should be working hard to move up in the ladder of my jobs rather than just fulfilling my personal duties. This is especially apparent when I am not enjoying what I am working on and mainly working for the money aspect. The second example is when I could possibly want to quit and I decide to pull through and finish what I am working on. It proves valuable and exciting to finish things whether you liked the process or not. Lastly, the book shows that pulling through the tough and hard times will help your reputation as a worker and a programmer immensely and that in doing so, more opportunities with open up. This whole pattern resonates with me so much because of how ominous real life jobs feel to me at the moment. I know that if I follow those three examples/tips, I will not regret it! Bad times will happen, and pulling through will only help me grow.