generated from CMU-17-214/f23-lab10
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f79cb7a
Showing
9 changed files
with
360 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Compiled class file | ||
*.class | ||
|
||
.vscode/ | ||
# Log file | ||
*.log | ||
|
||
# BlueJ files | ||
*.ctxt | ||
|
||
# Mobile Tools for Java (J2ME) | ||
.mtj.tmp/ | ||
|
||
# Package Files # | ||
*.jar | ||
*.war | ||
*.nar | ||
*.ear | ||
*.zip | ||
*.tar.gz | ||
*.rar | ||
|
||
target/ | ||
|
||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml | ||
hs_err_pid* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# Lab 10 - Testing in the Real World | ||
|
||
This recitation is an introduction to test doubles. | ||
|
||
## Deliverables | ||
- [ ] Use a fake to test `logIn` | ||
- [ ] Use stubs to test `getRecommendation` | ||
- [ ] Use mocks to test `sendPromoEmail` | ||
|
||
## Introduction | ||
|
||
In testing, it may sometimes be necessary to use objects or procedures that look and behave like their release-intended counterparts but are actually simplified versions that reduce the complexity and facilitate testing. Objects or procedures meant for production can be too slow, unavailable, expensive, opaque, or non-deterministic. Instead, test doubles are often used. There are multiple types of test doubles, but the most well-known/popular are Fakes, Stubs, and Mocks. | ||
|
||
### Fakes | ||
|
||
Fakes are fully functional classes with a simplified implementation. Usually, they take some shortcuts and are a simplified version of the real object. We use fakes to avoid interacting directly with objects that are too costly to access during testing, like databases. So, instead of querying our actual database, we use a fully functional in-memory database to simulate the same operations. | ||
|
||
<!--  --> | ||
|
||
### Stubs | ||
|
||
A stub is an artificial class that returns pre-configured data. We use it to answer calls during tests. Stubs are used when we can't or don’t want to involve objects that would answer with real data or would have undesirable side effects. For example, instead of querying our real database, we may use a stub with predefined data to simulate only the functionality we need. | ||
|
||
<!--  --> | ||
|
||
### Mocks | ||
|
||
A mock is an instrumented variant of a real class with fine-grained control. We use mocks when we don’t want to invoke expensive production code or when there is no easy way to verify that an intended action was executed. For example, we don't want to send a new email every time we want to test an email system. | ||
|
||
<!--  --> | ||
|
||
These three terms are usually used interchangeably in practice, but there are some subtle differences. You can find plenty of resources online that go into more detail on the differences if you're still unsure what they are (don't worry, even experienced software developers get it [wrong](https://martinfowler.com/articles/mocksArentStubs.html)). | ||
|
||
## Instructions | ||
|
||
Clone the AndrewWS repository from https://github.com/CMU-17-214/f23-lab10. Run the following commands to get started: | ||
``` | ||
mvn install | ||
mvn test | ||
``` | ||
You might notice the tests are taking a very long time to run. Let's increase their performance using test doubles! Look through the provided files to see which methods you will need to test with which types of test doubles. You will find hints on how to proceed there. | ||
|
||
All of your tests should be written in `AndrewWebServicesTest.java`. You will also need to implement a fake database in `InMemoryDatabase.java`. For mocks, we will use the [Mockito](https://site.mockito.org/) framework. | ||
|
||
## Mockito | ||
|
||
We will be using the mocking framework [Mockito](https://site.mockito.org/) in this lab. Here is a simple example to get you familiar with the important parts of Mockito. | ||
|
||
We'll use the `Comics` class for this example. The `Comics` class represents a mapping from characters to the Comics they belong to. | ||
``` | ||
public class Comics { | ||
private Map<String, String> charactersToComics; | ||
public String get(String character) { | ||
return charactersToComics.get(character); | ||
} | ||
} | ||
``` | ||
We use the `mock` method to create a mock of `Comics`: | ||
``` | ||
Comics ourMock = mock(Comics.class); | ||
``` | ||
|
||
Now we can use the `when` and `thenReturn` methods to add behavior to our mocked class (aka stub a method call): | ||
|
||
``` | ||
when(ourMock.get("Snoopy")).thenReturn("Peanuts"); | ||
``` | ||
|
||
So, we've specified that whenever we call `get("Snoopy")`, our mocked class should return "Peanuts". | ||
|
||
Next, we execute a method call on our mock: | ||
``` | ||
String snoopyComic = ourMock.get("Snoopy"); | ||
``` | ||
|
||
Now we use the `verify` method to check that our method was called with the given arguments. The following lines confirm that we invoked the `get` method on the mock and that the method returned a value that matches the expectation we set before: | ||
``` | ||
verify(ourMock).get("Snoopy"); | ||
assertEquals(snoopyComic, "Peanuts") | ||
``` | ||
|
||
So, now we've successfully mocked the `Comics` class and used a stub method call to write a test for the `get` method in `Comics`. | ||
|
||
This example covered everything you need to know for mocks in this lab. Feel free to checkout the [Mockito website](https://site.mockito.org/) for more information and documentation on the methods we used above, or look online for other examples using Mockito if the one above wasn't clear. Also, ask your TAs or ask on Piazza if you need any further help. Good luck! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>org.example</groupId> | ||
<artifactId>lab10</artifactId> | ||
<version>1.0-SNAPSHOT</version> | ||
|
||
<properties> | ||
<maven.compiler.source>17</maven.compiler.source> | ||
<maven.compiler.target>17</maven.compiler.target> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<version>4.12</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mockito</groupId> | ||
<artifactId>mockito-core</artifactId> | ||
<version>3.2.4</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.sun.mail</groupId> | ||
<artifactId>javax.mail</artifactId> | ||
<version>1.6.2</version> | ||
</dependency> | ||
</dependencies> | ||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package AndrewWebServices; | ||
|
||
/** | ||
* AndrewWebServices contains the main functionality in our AndrewWS. | ||
* | ||
* DO NOT MODIFY this class. | ||
*/ | ||
public class AndrewWebServices { | ||
Database database; | ||
RecSys recommender; | ||
PromoService promoService; | ||
|
||
public AndrewWebServices(Database database, RecSys recommender, PromoService promoService) { | ||
this.database = database; | ||
this.recommender = recommender; | ||
this.promoService = promoService; | ||
} | ||
|
||
/* | ||
* Accesses the database to verify the passed account name and password. | ||
* | ||
* Note that the database has a large latency which we don't want to interact with during testing. | ||
* | ||
* TODO: use a fake to test this method | ||
*/ | ||
public boolean logIn(String accountName, int password) { | ||
long startTime = System.currentTimeMillis(); | ||
if (this.database.getPassword(accountName) == password) { | ||
long endTime = System.currentTimeMillis(); | ||
System.out.println("Login success! Took " + (endTime - startTime) / 1000 + " seconds"); | ||
return true; | ||
} | ||
System.out.println("Login failed."); | ||
return false; | ||
} | ||
|
||
/* | ||
* Returns a movie as recommendation to the passed user. | ||
* | ||
* Note that the recommender uses an ultra fancy machine learning algorithm | ||
* and may take very long to return results. | ||
* | ||
* TODO: use stubs to test this method. | ||
*/ | ||
public String getRecommendation(String accountName) { | ||
long startTime = System.currentTimeMillis(); | ||
String movie = recommender.getRecommendation(accountName); | ||
long endTime = System.currentTimeMillis(); | ||
System.out.println("Recommend " + movie + ". Took " + (endTime - startTime) / 1000 + " seconds."); | ||
return movie; | ||
} | ||
|
||
/* | ||
* Sends a promotional email to the given email address using the promoService. | ||
* | ||
* Note that the function has no return value. We want to avoid | ||
* actually sending emails during testing. | ||
* | ||
* TODO: use mocks to test this method | ||
*/ | ||
public void sendPromoEmail(String email) { | ||
promoService.mailTo(email); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package AndrewWebServices; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
/* | ||
* Database is an implementation of AndrewWS database. | ||
* | ||
* DO NOT MODIFY this class. | ||
*/ | ||
public class Database { | ||
public int getPassword(String accountName) { | ||
try { | ||
TimeUnit.SECONDS.sleep(10); | ||
if (accountName == "Scotty") { | ||
return 17214; | ||
} else { | ||
return 0; | ||
} | ||
} catch (InterruptedException e) { | ||
e.printStackTrace(); | ||
} | ||
return -1; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package AndrewWebServices; | ||
|
||
/* | ||
* InMemoryDatabase is a fake for the AndrewWS database which is used to improve test efficiency. | ||
* Remember, fakes are fully functional classes with simplified implementation. | ||
* What is the simplest core functionality that we need for a functional database? | ||
* | ||
* Hint: there are two methods you need to implement | ||
*/ | ||
public class InMemoryDatabase /* should there be something here? */ { | ||
// Implement your fake database here | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package AndrewWebServices; | ||
|
||
import java.util.Properties; | ||
import javax.mail.Message; | ||
import javax.mail.MessagingException; | ||
import javax.mail.PasswordAuthentication; | ||
import javax.mail.Session; | ||
import javax.mail.Transport; | ||
import javax.mail.internet.InternetAddress; | ||
import javax.mail.internet.MimeMessage; | ||
|
||
/* | ||
* PromoService uses an external API for AndrewWS promotional services. | ||
* | ||
* DO NOT MODIFY this class. | ||
*/ | ||
public class PromoService { | ||
public void mailTo(String email) { | ||
String to = email; | ||
String from = "[email protected]"; | ||
String host = "smtp.gmail.com"; | ||
|
||
Properties properties = System.getProperties(); | ||
properties.put("mail.smtp.host", host); | ||
properties.put("mail.smtp.port", "465"); | ||
properties.put("mail.smtp.ssl.enable", "true"); | ||
properties.put("mail.smtp.auth", "true"); | ||
|
||
Session session = Session.getInstance(properties, new javax.mail.Authenticator() { | ||
protected PasswordAuthentication getPasswordAuthentication() { | ||
return new PasswordAuthentication("[email protected]", "*******"); | ||
} | ||
}); | ||
|
||
try { | ||
MimeMessage message = new MimeMessage(session); | ||
message.setFrom(new InternetAddress(from)); | ||
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); | ||
message.setSubject("This is the email subject"); | ||
message.setText("This is the email body"); | ||
|
||
Transport.send(message); | ||
} catch (MessagingException mex) { | ||
mex.printStackTrace(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package AndrewWebServices; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
/* | ||
* RecSys is an implementation of the AndrewWS recommendation system which uses super advanced machine learning to given movie recommendations. | ||
* | ||
* DO NOT MODIFY this class. | ||
*/ | ||
public class RecSys { | ||
public String getRecommendation(String accountName) { | ||
try { | ||
TimeUnit.SECONDS.sleep(10); | ||
} catch (InterruptedException e) { | ||
e.printStackTrace(); | ||
} | ||
return "Animal House"; | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
src/test/java/AndrewWebServices/AndrewWebServicesTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package AndrewWebServices; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
import org.junit.Before; | ||
import org.junit.Test; | ||
|
||
public class AndrewWebServicesTest { | ||
Database database; | ||
RecSys recommender; | ||
PromoService promoService; | ||
AndrewWebServices andrewWebService; | ||
|
||
@Before | ||
public void setUp() { | ||
// You need to use some mock objects here | ||
database = new Database(); // We probably don't want to access our real database... | ||
recommender = new RecSys(); | ||
promoService = new PromoService(); | ||
|
||
andrewWebService = new AndrewWebServices(database, recommender, promoService); | ||
} | ||
|
||
@Test | ||
public void testLogIn() { | ||
// This is taking way too long to test | ||
assertTrue(andrewWebService.logIn("Scotty", 17214)); | ||
} | ||
|
||
@Test | ||
public void testGetRecommendation() { | ||
// This is taking way too long to test | ||
assertEquals("Animal House", andrewWebService.getRecommendation("Scotty")); | ||
} | ||
|
||
@Test | ||
public void testSendEmail() { | ||
// How should we test sendEmail() when it doesn't have a return value? | ||
// Hint: is there something from Mockito that seems useful here? | ||
} | ||
|
||
@Test | ||
public void testNoSendEmail() { | ||
// How should we test that no email has been sent in certain situations (like right after logging in)? | ||
// Hint: is there something from Mockito that seems useful here? | ||
} | ||
} |