I wanted to learn more about mock testing after working with it and other testing approaches in the recent activities. There are some parts of mock testing that I am think I could understand better after doing some more reading on it.
The process and purpose of mocks make sense to me, where mocks are used to simulate behaviors for tests and focus more on the code itself. Because of this, mock testing is based on the behavior of code while unit testing is state based. State based is for the values you get from the code while behavior based is for checking if the right method is called. Behavior based is important because you can get the right values, but you might not call the right method if there are methods with similar return types and parameters.
I think where I had some misunderstandings at first was when it came to dependencies. Mock testing is based around dependencies and it seem like mock testing will be more difficult if you have some trouble with dependencies. I haven’t worked as much with dependencies as other areas with code, and I had a little trouble with it in a past assignment.
Dependencies are other programs that the main code relies in order to work, with JUnit being one of the main examples that we work with. Where I have some trouble with this is less with understanding the concept and more with some parts of how it works in the IDEs. I sometimes get errors from not properly setting the dependencies and get stuck on them for a while. Mockito is another one we have started working with that was giving me some trouble in a similar way.
I have a better grasp on dependencies after reading more about them and mock testing, and I hope to overcome problems that I have working with mock testing as we continue in the activities.
After a piece of software, whether that be the project as a whole or simply one component as part of a system has been completed, it makes sense to give the final product a sort of “look-over” to make sure that everything is working as intended and meets any required specifications. This is something which generally seems to be good practice to follow anyway in my opinion, since it could be easy to overlook something during the development process. It can be easy to become hyper-focused on one thing or another when working on larger projects with multiple components, which could lead to the potential to forget or leave-out some minor (or major) requirements, introduce bugs or flaws, or otherwise implement things in a way which is inefficient or ineffective.
This is where the more formally defined process of “Code Review” is beneficial. While looking into the process, I found this helpful writeup from Atlassian: (https://www.atlassian.com/agile/software-development/code-reviews) which goes into great detail regarding the practice. Overall, the process involves looking for any clearly visible bugs or issues with the code, as well as considering logic errors and concerns. The finished code/components is compared to the requirements of the project, and any test-cases or testing methodologies used are evaluated (how complete is the test coverage, should any new tests be added?). Additionally the code is compared to preexisting code (referred to as “style guidelines” in the post by Atlassian) which exists within the same space or project to promote consistency and cohesiveness.
Compared to the process I described earlier, the formal definition of Code Review provided by Atlassian hits many of the same key areas. The finished piece or product is examined in relation to any requirements, testing/test cases are examined to determine coverage and whether any additional tests are necessary, and bugs, errors, or logical flaws are sought out and fixed where they have occurred. I would say that the last point regarding “style guidelines” is less important, especially in solo-development environments where you might not be working in a team. Regardless it still seems like a good thing to keep in mind when working in larger group applications. Code review is a helpful and largely effective way to take a second look after the main development process is complete and make sure that the final product is what it was intended to be.
Mr. Bugayenko talks about the structure of a simple Java project and return values of various methods. In the article, it shows how much work it would take to mock a program that has multiple levels of abstraction and if we did mock everything, it would obscure the meaning of our tests because our tests will would have multiple levels of abstraction and makes it that much harder to see what exactly we are testing. In the end, it would also make the test file much longer than our main file. In situations like this, it would be easier to use fakes.
According to the article linked above, integration testing is a type of software testing that focuses on how well different components of a system interact with each other. For example, when assembling a pen, one would ideally test if all the parts for a pen fit together like the cap, ink cartridge, tail, among other parts. There are two main types of integration testing called unit integrated testing and system integrated testing respectively. Unit integrated testing focuses on testing the interactions and interfaces between integrated components. System integrated testing focuses on testing the interactions and interfaces between systems. There are also four ways to approach integration testing called Big Bang, Top Down, Bottom Up, and Sandwich or Hybrid. Big Bang is when most if not all components are tested at once such as all the functionalities in a system. Top Down is when top-level components are tested first followed by lower-level components. Bottom Up is the opposite where low-level components are tested first followed by higher-level ones. Sandwich, also called Hybrid, is combination of both Top Down and Bottom Up approaches.
For my software capstone, me and three other team members are working on an inventory system. Each of us are working on different components; for example, I am working on the backend API and a messaging system. Right now, I’m in the middle of testing the API by itself, but I’ll have to test it in conjunction with other components like the messaging system, the rest of the backend, the frontend, and also the other systems that are being developed simultaneously. One could call this a form of integration testing, focusing both on the unit and system levels. I feel I’ll take a Hybrid approach since what’ll be tested first is whatever I finish first and what my teammates finish first.
Christian Shadis, CS-443 Self-Directed Blog Post #4
For students like me with limited experience writing JUnit tests, it becomes easy to get lost in the repetition of “Setup, Exercise, Verify” and not realize that a simple, understandable feature of JUnit testing, annotations, can be used to improve the functionality and efficiency of your JUnit Test Suites. In this post I will explain the uses five JUnit 5 annotations and demonstrate them. The following annotations will be explained:
We begin with an extremely simple Java class “Book” and its similarly simple counterpart “BookTest”. See the code for the two classes below.
public class Book {
private int pages;
private String author;
private int pubYear;
public Book(int pages, String author, int pubYear){
this.pages = pages;
this.author = author;
this.pubYear = pubYear;
}
public int getPages(){ return this.pages; }
public String getAuthor(){ return this.author; }
public int getPubYear(){ return this.pubYear; }
public void setPages(int pages){ this.pages = pages; }
public void setAuthor(String author){ this.author = author; }
public void setPubYear(int pubYear){ this.pubYear = pubYear; }
}
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BookTest {
@Test
void testConstructor(){
Book book1 = new Book(100, "John Doe", 1999);
boolean result = book1 instanceof Book;
assertTrue(result);
}
@Test
void testGetPages(){
Book book1 = new Book(100, "John Doe", 1999);
int result = book1.getPages();
assertEquals(result, 100);
}
@Test
void testGetAuthor(){
Book book1 = new Book(100, "John Doe", 1999);
String result = book1.getAuthor();
assertEquals(result, "John Doe");
}
@Test
void testGetPubYear(){
Book book1 = new Book(100, "John Doe", 1999);
int result = book1.getPubYear();
assertEquals(result, 1999);
}
@Test
void testSetPages(){
Book book1 = new Book(100, "John Doe", 1999);
book1.setPages(150);
assertEquals(book1.getPages(), 150);
}
@Test
void testSetAuthor(){
Book book1 = new Book(100, "John Doe", 1999);
book1.setAuthor("Jane Smith");
assertEquals(book1.getAuthor(), "Jane Smith");
}
@Test
void testSetPubYear(){
Book book1 = new Book(100, "John Doe", 1999);
book1.setPubYear(2001);
assertEquals(book1.getPubYear(), 2001);
}
}
@BeforeEach
The first annotation we will look at is @BeforeEach. This annotation is used for repetitive code that is executed during each test case. If you examined BookTest closely, you might have noticed that the first line of every test case is identical. This makes our suite the perfect candidate to use @BeforeEach. In order to implement, we must create a setUp test method, and put book1 as an instance variable – this way, scope does not become a nuisance. See the revised BookTest.java file below.
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BookTest {
Book book1;
@BeforeEach
void setUp(){
book1 = new Book(100, "John Doe", 1999);
}
@Test
void testConstructor(){
boolean result = book1 instanceof Book;
assertTrue(result);
}
@Test
void testGetPages(){
int result = book1.getPages();
assertEquals(result, 100);
}
@Test
void testGetAuthor(){
String result = book1.getAuthor();
assertEquals(result, "John Doe");
}
@Test
void testGetPubYear(){
int result = book1.getPubYear();
assertEquals(result, 1999);
}
@Test
void testSetPages(){
book1.setPages(150);
assertEquals(book1.getPages(), 150);
}
@Test
void testSetAuthor(){
book1.setAuthor("Jane Smith");
assertEquals(book1.getAuthor(), "Jane Smith");
}
@Test
void testSetPubYear(){
book1.setPubYear(2001);
assertEquals(book1.getPubYear(), 2001);
}
}
@AfterEach
Analogous to @BeforeEach, @AfterEach will execute following each test case. There is no obvious implementation in our code, but I will demonstrate its use nonetheless. We can use @AfterEach to update a count variable indicating how many tests are run. We add a print statement to our setup method to print the test number, and add count as an instance variable, initialized to 0. @AfterEach will allow us to increment the count after each test case is run. Create a method tearDown() with the @AfterEach annotation, and add “count++;” as the body. The count declaration must also be revised to a public static modifier. The new instance variables, setUp(), and tearDown() methods are shown below.
public class BookTest {
Book book1;
private static int count = 0;
@BeforeEach
void setUp(){
book1 = new Book(100, "John Doe", 1999);
System.out.println("Running test " + count);
}
@AfterEach
void tearDown(){
count++;
}
.
.
.
}
We now have methods that execute before and after every single test case.
@BeforeAll
@BeforeAll is used as an initialization method that executes before any other method. It is the first method in BookTest that will be run. We can use it in this example to instantiate our count variable to 0, and leave just the variable declaration with the instance variables. See the modified instance variables and new init() method below.
public class BookTest {
private static int count;
Book book1;
@BeforeAll //runs first
public static void init(){
System.out.println("BeforeAll running: count set to 0.");
count = 0;
}
.
.
.
}
@ParameterizedTest
This annotation is useful for running test cases with multiple inputs. For example, imagine the developer of BookTest is unsure whether testSetAuthor() will pass when multi-word strings are passed as arguments. This scenario is the perfect situation to use a parameterized test. When using @ParameterizedTest, however, one must be sure to specify the source of the arguments, which is accomplished using the annotation @ValueSource. See the example below.
This annotation is used for when tests need to be run multiple times. For example, if we wanted to run testGetAuthor three times, we would use this annotation. When using @RepeatingTest, we must pass a parameter value which determines the number of repetitions, and name which names each repetition. See the example below.
@RepeatedTest(value = 3, name = "Repetition {currentRepetition}")
void testGetAuthor(){
String result = book1.getAuthor();
assertEquals(result, "John Doe");
}
Summary
We have now taken an extremely simple JUnit 5 test suite and used five different annotations to make the suite a bit more complex and comprehensive. There are many more annotations, all of which can be found in the JUnit documentation here. We can now run tests multiple times, create setup and teardown methods, and parameterize tests to run them with multiple argument inputs.
public class Book {
private int pages;
private String author;
private int pubYear;
public Book(int pages, String author, int pubYear){
this.pages = pages;
this.author = author;
this.pubYear = pubYear;
}
public int getPages(){ return this.pages; }
public String getAuthor(){ return this.author; }
public int getPubYear(){ return this.pubYear; }
public void setPages(int pages){ this.pages = pages; }
public void setAuthor(String author){ this.author = author; }
public void setPubYear(int pubYear){ this.pubYear = pubYear; }
}
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class BookTest {
private static int count;
Book book1;
@BeforeAll
public static void init(){
System.out.println("BeforeAll running: count set to 0.");
count = 0;
}
@BeforeEach
void setUp(){
book1 = new Book(100, "John Doe", 1999);
System.out.println("Running test " + count);
}
@AfterEach
void tearDown(){
count = count + 1;
}
@Test
void testConstructor(){
boolean result = book1 instanceof Book;
assertTrue(result);
}
@Test
void testGetPages(){
int result = book1.getPages();
assertEquals(result, 100);
}
@RepeatedTest(value = 3, name = "Repetition {currentRepetition}")
void testGetAuthor(){
String result = book1.getAuthor();
assertEquals(result, "John Doe");
}
@Test
void testGetPubYear(){
int result = book1.getPubYear();
assertEquals(result, 1999);
}
@Test
void testSetPages(){
book1.setPages(150);
assertEquals(book1.getPages(), 150);
}
@ParameterizedTest
@ValueSource(strings = {"Jane", "Jane Smith"})
void testSetAuthor(String str){
book1.setAuthor(str);
assertEquals(book1.getAuthor(), str);
}
@Test
void testSetPubYear(){
book1.setPubYear(2001);
assertEquals(book1.getPubYear(), 2001);
}
}
Although I may not have stated it explicitly, I wrote my previous post regarding using boundary values during testing on programs that are, more or less, complete with all the necessary functions having been implemented. That can be the case for a programmer who is working on software that is relatively small and can be coded to completion by a single individual. Though, one would ask, what happens when the project’s scale is large enough that multiple people would need to be assigned to code certain chunks of code that would be combined into a complete piece of software? The programmers may have some guidelines on or a semblance of how the software should work as a whole, including some of the parts they aren’t actually assigned to implement, yet they may not have the time or resources to work on the other functionalities on top of the ones assigned to them. Yet, to ensure that their assigned functionalities work according to specifications, they will still need the missing code to be implemented for which they may not know how to implement, mostly because of certain specifications that are expected to be met yet are unknown to the programmer. And the cycle continues.
In such cases, one technique that programmers can utilize in order to test their Work-In-Progress code without having to manually implement the missing code is test doubles. Essentially, programmers use test doubles as a way to make up for the missing functionalities by implementing simplified objects or methods such that the can still be run with minimal amount of code that can be easily replaced by the actual final product. Test doubles include, but are not limited to, the following: 1) Dummies 2) Stubs 3) Fakes 4) Mocks
I will be discussing Mocks along with Mockito in more detail in a later post. Instead, in this post I will be focusing on the first three types of test doubles that are given above. What I will mention about mocks, however, is that they can utilize the Mockito testing framework to automate and simplify the process of testing to a more significant degree compared to the previous three types. It goes without saying, however, that each approach has its own merits depending on the needs.
Dummies or dummy objects are objects or procedures with minimal, if any, implementation, making it by far the simplest form of test double implementation. In other words, dummy objects can be considered a bare-bone implementation of code that is sufficient enough to help in testing. Below is a simple example of a dummy object:
public DummyObject(){ } Essentially, the above dummy object is more than enough code even though lacks the implementation. Dummy objects are created to be used when other methods may require parameters, though in this particular case we are not actually creating the objects ourselves.
Stubs are (or I at least believe they are) a more common method of testing methods. Essentially, stubs contain slightly more (yet still bare-bones) implementation than dummies, which is more often than not a predefined return value that corresponds to the return type. Below is an example of a stub method: public double average(double a, double b){ return 3.1; } In the above example, the method will not do any calculations. Instead, it will return an arbitrary value that is relevant to the return type, without any other significance whatsoever. Stubs are used to test if the method actually responds when being called during testing, rather than if the method produces an expected result after running a specific set of computations. I have found myself using stubs when programming in C earlier in my studies just to make sure if other parts of my code can be compiled properly, though now I know that stubs may also be used in unit testing as well.
The last type of test doubles I will be talking about is fakes. Fakes or fake objects have some implementation, though this implementation is often different from that in the end product. Fake objects do not actually use resources (such as data from a database) but may use some arbitrary data in order for the object to be used in testing. An example of a fake object is given below:
Compared to stubs and dummies, fakes contain more implementation, though they are much “lighter” to use than actual objects that contain data that may need to be retrieved in one way or another.
Overall, which of the above implementations may need to be used in testing depends solely on the programmer’s judgement and the state of their code. When combined with other testing techniques (such as boundary value testing), unit testing may provide enough insight to the developers in order to make the appropriate adjustments to their software, be it in regards to fixing faulty code or implementing better optimization. Like I mentioned earlier, each technique has its own merits depending on the circumstances. Moreover, in my next post I will be discussing Mock objects, the Mockito testing framework, and some of the advantages Mockito brings to software testing.
When working on a piece of software or project, often I will encounter a point where some particular piece of the program cannot be tested properly due to one or more components or features not being completed yet. Rather than waiting until all these things are completed, and potentially rushing them to completion in order to do testing, mocking allows me to bypass the issue and use a “mock” version of the missing dependency or component needed for testing.
This is a convenient way to get around having to develop the entire program all at once before being able to test anything, which could arguably lead to more errors or issues over time since you then aren’t able to test as you implement features, but rather only after they have all been implemented. This aspect alone makes mocking seem like an overtly beneficial concept. But there are some drawbacks to consider with mocking, some of which I will discuss below.
In regards to the first issue, repetition, I think that in some cases that this can be justified, and while mocking does represent some duplication in that it is taking a preexisting piece of the program and creating a placeholder for it, this seems unlikely to have the same negative effects that say, having three different train classes which only differ in the value of their color attribute.
The second and third points regarding refactoring and complication of the program seem like legitimate pitfalls to watch out for. When restructuring, the various mocks present could no longer be applicable based on changes in the location of various dependencies and test-cases in relation to them. Mocking does introduce an additional layer of complexity which would not otherwise be present, definitely a fair concern to consider.
Overall, I would say that most of the issues associated with mocking which are described by the author can be mitigated or completely avoided provided that they are considered during development. Mocking still seems in general to be a benefit in relation to testing during development rather than after it has been feature-completed.
As stated in my previous post, I did a group honors project where each member covered a different software testing tool. The primary reason for this was to determine which tools were useful for what purpose, one of which, being Jacoco, I covered in the previous post as well. But, after having heard each group member’s analysis of their tools it begged the question, which tool is best? To properly discuss this I will be examining the pros and cons to each of the tools my group member’s covered in addition to a brief summary of Jacoco.
First there is PIT, which is a mutation testing tool. I was not familiar with these different types of tests, so I will give a quick explanation of what they are. Mutation testing involves the tool essentially making multiple copies of the code which are then given faults, these being the titular mutations. Tests are then run against each mutant with the result being the mutant’s death, meaning the tests failed, or the mutant surviving, meaning the tests passed. The quality of these tests is then determined by the amount of mutants that could be killed. This method of testing is very robust and covers code coverage as well, but this results in these tests being slower than others. PIT is advantageous as it is the fastest of the mutation tests, and has a lot of support from build tools. The other type of testing covered was Programming Mistake Detector, or PMD for short. This tool focuses on finding more expected flaws, like unused variables, useless objects, empty catch blocks, and other such errors we are all familiar with. In addition to this it can also spot any duplicated code with a copy-paste-detector. This is all handled by rules used to test source code, which are grouped into rule sets. Unlike mutation tests, this is much more lightweight and easy to run from a command line, additionally you can run multiple rulesets at once. However this is a less robust testing method, not accounting for any runtime issues or a way to match a ruleset with its generated error easily when running rulesets on a file. So what is the verdict?
Ultimately, there is only really one type of testing that seems sufficient to be used in and of itself, this being mutation testing with PIT. If you are looking for the most condensed testing tool that can do the most in one package, this would be the answer. However you will want to ensure that using mutation testing is necessary, as if you are only really concerned about code coverage Jacoco would be easier and more efficient. Simultaneously, if you want to check the code itself to ensure there are not many errors and do not care about the coverage, you can implement PMD. The answer will ultimately vary from system to system, and if you want to read more about these tools I have provided some resources below. If you want to read about Jacoco be sure to check my previous post!
This week I wanted to look at integration testing. Most projects have multiple components coded by different programmers. The purpose of integration testing is to expose any defects in the way these components interact with each other. It is sometimes called ‘I & T’ (Integration and Testing), ‘String Testing’, or ‘Thread Testing’.
Integration testing is the next level of testing after unit testing. While each individual component of the software is unit tested, there may still be defects. These may be because:
A module written by one programmer may have different logic and understanding of requirements from another causing their two components to work together improperly.
New requirements may be added by the customer that need to be tested.
The software may not be interacting with the database correctly.
There are multiple approaches to integration testing. Big bang testing is integrating all the components at once and testing as a unit. This works well for small systems, but it may be hard to isolate issues. Incremental testing starts with only a few modules ad gradually adds more after testing for proper function. This can be done bottom-up by testing lower-level modules first, or it can be done top-down with higher level modules first. There is also the sandwich method which tests higher level and lower level simultaneously and then combines the two.
While we have not yet taken a look at integration testing in class, I wanted to have a rough idea of what it is at least. I am sure our POGIL activities will go more in depth and explore why integration testing is used as well as exactly how it is done.
Structural testing is based on the source code of the program under test, rather than the definition. This is known as white box testing, while functional testing is known as black-box testing.
Program diagram: For a program written in an imperative programming language, the program diagram is a directed graph with nodes representing statement fragments and edges representing control flow.
DD – path
DD path: Decision-to-decision path (Miller). Begin with the “exit” of the decision statement and end with the “path” of the next decision statement.
DD chain: A path of starting and ending nodes at different points in a directed graph.
Consisting of a node, the internality =0;
Consisting of a node, externality =0;
Consisting of a node, the inner degree > =2 or the outer degree > =2;
Consisting of a node, the degree inside =1 and the degree outside =1;
Length > =1 for maximum drill
DD – path graph
A DD-path graph is a labeled directed graph in which nodes represent the DD Path of a program graph and edges represent the control flow of the Path.
For a given program, many different program diagrams can be constructed, all of which can be reduced to a unique DD-path diagram.
It is possible to generate DD paths for programs up to 100 lines, and above that size, analysis tools are generally required.
The simplest control flow diagram is the DD path. DD can be thought of as belonging to the control flow diagram.
DD path definition:
Given a program written in an imperative language, its DD path graph is a directed graph, where nodes represent the DD paths of its program graph and edges represent the control flow between successive DD paths.
In fact, the DD path graph is a compressed graph in which 2-connected components are compressed into a single node corresponding to the 5DD path.