Skip to content
Snippets Groups Projects
Commit 51c3da83 authored by Christopher Bohn's avatar Christopher Bohn :thinking:
Browse files

Replaced testing of CipherFactory with testing of communicateOneMessage

parent e1eb9caf
No related branches found
No related tags found
No related merge requests found
......@@ -3,25 +3,84 @@
# Mac file finder metadata
.DS_Store
# Windows file metadata
._*
# Thumbnail image caches
Thumbs.db
ethumbs.db
# MS Office temporary file
~*
# Emacs backup file
*~
# Common
[Bb]in/
[Bb]uild/
[Oo]bj/
[Oo]ut/
[Tt]mp/
[Xx]86/
[Ii][Aa]32/
[Xx]64/
[Xx]86_64/
[Xx]86-64/
[Aa]rm
[Aa]32
[Tt]32
[Aa]64
*.tmp
*.bak
*.bk
*.swp
# Miscellaneous
*.gcno
# LaTeX files
*.aux
*.log
*.tex.gz
*.synctex.gz
*.texmk
*.dvi
*.fls
*.fdb_latexmk
# C files
*.o
*.out
# Java files
*.class
javadoc/
# Emacs backup file
*~
# Maven
target/
# Python files
*.pyc
*.pyo
# Swift files
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
# IntelliJ files
# JetBrains (IntelliJ IDEA, PyCharm, etc) files
.idea/
out/
cmake-build-*/
*.iml
*.iws
*.ipr
# Eclipse files
bin/
.settings/
.classpath
.project
.classpath
.buildpath
.loadpath
local.properties
# Visual Studio / VS Code files
.vs*/
......@@ -47,7 +106,6 @@ bin/
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
......@@ -57,6 +115,15 @@ bin/
*.pidb
*.svclog
*.scc
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
*.psess
*.vsp
*.vspx
# Netbeans files
nbproject/private/
......@@ -67,12 +134,8 @@ nbdist/
nbactions.xml
nb-configuration.xml
# Maven
target/
# Miscellaneous
tmp/
*.bak
*.bk
*.swp
# Xcode files
*.xcodeproj/
xcuserdata/
.build/
# Socket Chat Program
- Assignment Due: February 10, 2020 at 9:30am
- Peer Assessment Due: February 10, 2020 at 11:59pm
- Assignment Due: June 23, 2020 at 11:00am (CDT, UTC-5)
- Peer Assessment Due: June 23, 2020 at 11:59pm
In this assignment you will modify a simple 2-way network chat program to
prompt the user and get responses in a language other than English, and to
......@@ -25,8 +25,8 @@ Students will:
- Simple Factory Pattern
- Make use of resource "property" files to support internationalization
- Develop whitebox test cases to attain statement coverage
- Practice using test doubles
- Create JUnit tests for those test cases
<!-- - Practice using test doubles -- MIGHT HAVE TO PUT DOUBLES IN A FUTURE ASSIGNMENT-->
- Practice overriding the default author on a git commit
## Instructions
......@@ -60,7 +60,7 @@ git commit --author="Herbie Husker <herbie@huskers.unl.edu>"
prepared for you.
1. Navigate to your shared directory
(<https://git.unl.edu/csce_361/spring2020/09pairNN/>, where *NN* is your
(<https://git.unl.edu/csce_361/summer2020/09pairNN/>, where *NN* is your
team number).
1. Verify that the repository is private, and that you and your partner
......@@ -71,7 +71,7 @@ git commit --author="Herbie Husker <herbie@huskers.unl.edu>"
1. Clone the project: `git clone <URL>` (here the angle brackets should
not be included).
- **Do *NOT* place your calculator repository inside your
- **Do *NOT* place your socket_chat repository inside your
csce361-homework repository!**
1. Import the project into your IDE. The project is set up as a Maven
......@@ -105,28 +105,43 @@ authoritative source of requirements.**
## Assignment
Look over the starter code, view
[this short demonstration](https://use.vg/qB6zFZ), and run the program to get a
<!--[this short demonstration](https://use.vg/qB6zFZ),-->
[this short demonstration](TODO) <!-- this one includes relay server -->
and run the program to get a
feel for how the program works.
- One host, one client, but the same code
- Normal usage (requires host and client to be on the same network):
- To avoid having to do any network discovery, the host displays its IP
address, which must be shared with whoever is running the client so they
can type in the IP address
- The host sets the port number to be used, and this must be shared with
whoever is running the client so they can type in the IP address
- If the port number were hard-coded then we'd run the risk of that port
number already being in use
- If the port number were hard-coded then we'd run the risk of that
port number already being in use
- The host sets up a `ServerSocket` to accept the client's connection, and
the client sets up a `Socket` to connect to the host
- The host and client alternate turns sending Strings to each other over the
- Remote instruction usage:
- We have provided a Relay Server on csce.unl.edu, whose IP address is
publicly visible, so it doesn't have to be on the same network as the
chatters
- When launching SocketChat, both chatters should select the option to be
the client
- IP address: `-127.93.-91.26` <!--`129.93.165.26`-->, Port: `361NN`,
where *NN* is your team number
- The two chatters alternate turns sending Strings to each other over the
socket
- When there's message from either the host or client consisting solely of
the keyword `EXIT` in all capital letters, the program terminates.
- The host (or the first chatter to connect to the Relay Sever) sends the
first message
- When there's message from either chatter consisting solely of the keyword
`EXIT` in all capital letters, the program terminates.
<!--
NOTE: in the demonstration, I used jar files. This was convenient for the
purposes of the demonstration. You do not have to create jar files, but if you
want to, you can use Maven's "package" target. (Separate instructions for this
will be posted on Piazza.)
-->
### Internationalization
......@@ -141,7 +156,7 @@ Notice that anyplace that the program outputs something, the argument to
that corresponds to the specified key String. We also use this to compare the
user input to a string.
The key-value pairs are in a properties file in the `.../resources/` directory.
The key-value pairs are in a properties file in the `resources/` directory.
The files are of the form `basename_XX.properties`, where XX is the 2- or 3-
character language code. Example language codes are `en` for English, `fr` for
French, `de` for German, `zh` for Chinese, and `tlh` for Klingon. If you decide
......@@ -198,12 +213,46 @@ parameterized strings similar to C's `printf()`.
- Otherwise, create a `Locale` using [`Locale.Builder`](https://docs.oracle.com/javase/8/docs/api/java/util/Locale.Builder.html).
<!-- (Yes, this is using the Builder Pattern.) -->
### Whitebox Testing
This is the specification for `Chat.communicateOneMessage()` (not including
exception handling):
- The method will return `true` unless otherwise noted below
- If the message to be processed originates locally then the contents of
`localInput` shall be enciphered and be placed on `remoteOutput`
- If the message to be processed originates remotely then the contents of
`remoteInput` shall be deciphered and be placed on `localOutput`
- If a remotely-originated message is `null`, then an error message shall be
placed on `localOutput` indicating that a null message was received, and
the method will return `false`
- If the message, regardless of origin, consists solely of a keyword, then
the message shall be passed to the `handleKeyword()` method for processing;
the `communicateOneMessage()` method will return the boolean value returned
by `handleKeyword()`
4. Write sufficient JUnit tests to attain *statement coverage* of
`communicateOneMessage()`
- You will need to prepare test doubles to be able to run automated tests
of `communicateOneMessage()`
- The exception-handling behavior is not part of the specification, but
the developers wisely attempted to gracefully handle exceptional
conditions; you will need to write tests to exercise the catch block
for a `SocketException` (we have provided a test for the `IOException`
catch block)
- You can confirm that you have statement coverage by generating a
coverage report
- Eclipse: <https://www.eclemma.org/userdoc/importexport.html>
- Instructions at the bottom of that page. Export as HTML
- IntelliJ IDEA: <https://www.jetbrains.com/help/idea/generating-code-coverage-report.html>
### Strategy Pattern
You will use the *Strategy Pattern* to attach cipher algorithms to the chat
program.[^1]
- Figure 16.9 on Kung p397
- HFDP, [Chapter 1](https://learning.oreilly.com/library/view/head-first-design/0596007124/ch01.html)
[^1]: For this application, you are going to write cipher algorithms and
......@@ -213,18 +262,15 @@ program.[^1]
`javax.crypto.Cipher` and, if you're streaming text back and forth as
in this program, attach the algorithms using the Decorator pattern via
`javax.crypto.CipherInputStream` and `javax.crypto.CipherOutputStream`.
Decorator Pattern:
- Figure 16.34 on Kung p421
- HFDP, [Chapter 3](https://learning.oreilly.com/library/view/head-first-design/0596007124/ch03.html)
(for the Decorator Pattern, see HFDP,
[Chapter 3](https://learning.oreilly.com/library/view/head-first-design/0596007124/ch03.html))
The original version of this program sends plaintext messages over the network.
The current version passes an outgoing message through `Chat.encipher()` and
incoming messages through `Chat.decipher()`. Right now, all these methods do is
return the original message without enciphering (or deciphering) it.
4. Create `Cipher.java`, an interface with two methods: `String
5. Create `Cipher.java`, an interface with two methods: `String
encipher(String plaintext)` whose specification is that it that passes the
plaintext through a cipher to create ciphertext, and `String
decipher(String ciphertext)` whose specification is that it passes the
......@@ -266,7 +312,9 @@ internally, you've delegated the behavior for `Chat.encipher()` and
This would be a good time to verify that your partial implementation of the
Strategy Pattern hasn't broken anything.
8. Implement two classical ciphers as Java classes that implement the `Cipher`
9. Re-run your test suite to make sure no changes caused your tests to fail.
1. Implement two classical ciphers as Java classes that implement the `Cipher`
interface. You can include any other methods you feel are necessary;
however, only `encipher()` and `decipher()` will be exposed to `Chat.java`.
- You may use ciphers you implemented in a previous course (but they must
......@@ -285,6 +333,21 @@ Strategy Pattern hasn't broken anything.
1. You can replace `NullCipher` in the `Chat` constructor with either of the
other ciphers you wrote, and your program will still work.
1. If `NullCipher` is no longer the initial cipher then make any changes to
your tests that are necessary to reflect this new behavior
- The option that requires the least effort in the short term would be to
change the input and/or expected output for some tests to take into
account the new cipher
- A better option would be to make any changes needed to your code to
allow your tests to set the cipher being used, then set the cipher
either in your tests or in your `setUp()`, and finally change the input
and/or expected output for some tests to take into account the assigned
cipher
- While this will require a little more effort in the short term, it
means you won't have to change your tests again if you (or somebody
else) later decides to change the initial cipher
- Run your test suite to make sure everything passes
What we need now to complete the Strategy Pattern is a way to replace the
cipher algorithm at runtime. Many options are possible; we'll use the...
......@@ -310,7 +373,7 @@ the client code cannot assume a concrete type.
We will use a parameterized factory.
11. Create `CipherFactory.java`.
14. Create `CipherFactory.java`.
1. Write the method
`public static Cipher createCipher(String name, String[] keys)`. This
......@@ -325,23 +388,11 @@ We will use a parameterized factory.
arguments. This method shall return a default cipher algorithm. (You decide
what that default is).
1. Write JUnit tests to verify that `createCipher(String, String[])` returns
the correct type of `Cipher`. Hint: Java's `instanceof` operator evaluates
to `true` when the first argument is an instance of the class specified by
the second argument. Write enough tests to attain *statement coverage* of
`createCipher(String, String[])`.
- You can confirm that you have statement coverage by generating a
coverage report.
- Eclipse: <https://www.eclemma.org/userdoc/importexport.html>
- Instructions at the bottom of that page. Export as HTML.
- IntelliJ IDEA: <https://www.jetbrains.com/help/idea/generating-code-coverage-report.html>
That's it. That's your Simple Factory.
Now it's time to use it.
15. Find line line in the `Chat.Chat()` constructor where you wrote
17. Find line line in the `Chat.Chat()` constructor where you wrote
`cipherBehavior = new NullCipher()` (or something like that). Replace
`new NullCipher()` with `CipherFactory.createCipher()`. Now your cipher
algorithm is whatever the default happens to be.
......@@ -373,6 +424,8 @@ Now it's time to use it.
`CipherFactory.java`. If you have to add or modify `Chat.java` then
your code still depends on knowledge of `Cipher` implementations.
1. Re-run your test suite to make sure no changes caused your tests to fail.
You can now chat away without worrying about a "l337 h4x0r" being snoopy. (If
you're worried about someone with NSA-level snooping capabilities, don't use a
"classical era" cipher! They're relatively easy to break in general, and the
......@@ -409,6 +462,7 @@ is due, and we will look in the Maven-conventional directories for:
- `Cipher.java` and `CipherFactory.java`
- Three `Cipher` implementations (`NullCipher.java` plus two others of your
choosing)
- Updated `ChatTest.java`
*It is your responsibility to ensure that your work is in the master branch of
the **correct repository** and that we have the correct level of access to the
......@@ -418,7 +472,7 @@ the correct repository, or that we cannot access, will not be graded.*
## Rubric
The assignment is worth **24 points**:
The assignment is worth **25 points**:
- **4 points** for internationalization
- 1 points for creating the properties file for the other language
......@@ -430,16 +484,16 @@ The assignment is worth **24 points**:
- 1 point for creating `Cipher.java` and `NullCipher.java`
- 2 points each for the 2 other classical cipher algorithms
- 1 point for delegating `encipher` and `decipher`
- 3 points for writing the code to change cipher algorithms
- 3 points for writing the code to change cipher algorithms that is fully
independent of `Cipher` implementations
- **5 points** for tests
- **6 points** for tests
- 2 point for testing `encipher` and `decipher` for your two classical
cipher algorithms
- 3 points for writing sufficient tests to attain statement coverage of
`CipherFactory.createCipher(String, String[])`
- 4 points for writing sufficient tests to attain statement coverage of
`Chat.communicateOneMessage()`
- **1 point** for making regular commits; *i.e.*, not waiting until the end
of the project to make a massive commit.
- **1 point** for making regular commits throughout the project
- **2 points** for meaningful commit messages
......@@ -456,8 +510,9 @@ complicit in their academic dishonesty.*
The contribution is worth **10 points**:
- **1 point** for completing peer assessment
- **5 points** for equitable contribution based on peer assessments
- **1 point** for completing peer assessment on time
- **1 point** for contacting your partner promptly
- **4 points** for equitable contribution based on peer assessments
- **4 points** for equitable contribution based on git history
## Footnote
......@@ -60,7 +60,7 @@ public class Chat {
} catch (IOException ioException) {
// "Connection failed: ..."
System.err.println(bundle.getString("connection.error.generalConnectionFailure") + ": " + ioException);
System.exit(1);
exit(1);
}
this.socket = socket;
return socket;
......@@ -145,7 +145,7 @@ public class Chat {
} else {
// "Exceeded maximum number of connection attempts. Terminating."
System.err.println(bundle.getString("connection.error.tooManyAttempts"));
System.exit(1);
exit(1);
}
}
} while (socket == null);
......@@ -255,7 +255,7 @@ public class Chat {
// "Terminating."
System.err.println(bundle.getString("communicate.error.cannotSetUpStreams") + ": " + ioException);
System.err.println(bundle.getString("communicate.info.terminating"));
System.exit(1);
exit(1);
}
}
......@@ -322,8 +322,8 @@ public class Chat {
keepTalking = handleKeyword(message, localMessage, localInput, localOutput);
}
} catch (IOException ioException) {
System.err.println("Connection dropped: " + ioException);
System.exit(1);
localOutput.println("Connection dropped: " + ioException);
exit(1);
}
return keepTalking;
}
......@@ -363,6 +363,10 @@ public class Chat {
return true;
}
protected void exit(int exitCode) {
System.exit(exitCode);
}
private String encipher(String plaintext) {
String ciphertext = plaintext; // Replace this with a call to cipherBehavior.encipher(plaintext)
return ciphertext;
......
package edu.unl.cse.csce361.socket_chat;
import org.junit.Before;
import org.junit.Test;
import java.io.*;
import java.net.SocketException;
import java.util.ResourceBundle;
import static org.junit.Assert.*;
public class ChatTest {
static class MockChat extends Chat {
boolean exitCalled = false;
int actualExitCode = 0;
@Override
protected void exit(int exitCode) {
exitCalled = true;
actualExitCode = exitCode;
}
}
private MockChat chatter;
private String newLine;
private BufferedReader remoteInput;
private PrintStream localOutput;
private ByteArrayOutputStream localOutputSpy;
private ResourceBundle bundle;
@Before
public void setUp() {
chatter = new MockChat();
newLine = System.getProperty("line.separator");
bundle = ResourceBundle.getBundle("socketchat");
localOutputSpy = new ByteArrayOutputStream();
localOutput = new PrintStream(localOutputSpy);
}
@Test
public void testCommunicateOneMessageCatchesIOException() {
// arrange
Reader stubReader = new Reader() {
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
throw new IOException("IOException from stubReader");
}
@Override
public void close() throws IOException {
}
};
remoteInput = new BufferedReader(stubReader);
int expectedExitCode = 1;
String expectedOutput = "Connection dropped: java.io.IOException: IOException from stubReader" + newLine;
// act
boolean actualReturn = chatter.communicateOneMessage(null, remoteInput, localOutput, null, false);
// assert
assertTrue(chatter.exitCalled);
assertEquals(expectedExitCode, chatter.actualExitCode);
assertEquals(expectedOutput, localOutputSpy.toString());
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment