This document explains the procedures for preparing and turning in programming assignments in this course. For information on non-programming assignments, see here.
Most of the assignments in this course will involve making small changes to a set of code for a nearly complete program, or altering a completely working program to take advantage of some new data structure or algorithm we have studied. This feels “wrong” to some students, but, when I give an assignment, it’s designed to exercise a specific lesson you have just read. I want you spending your time and effort on that lesson’s material, not on writing yet another main()
function, lots of basic Java I/O that you should have learned in earlier courses, or any of the other myriad details that suck up your time when writing a program from scratch.
As it happens, this modification of existing code is probably more representative of what real programmers do. Most real-world programming projects will not involve creating an entire program from scratch. Far more often, you will be asked to modify, fix, or enhance existing code. Even when the program itself is new, you are likely to be building upon substantial libraries of existing code.
So you may not be writing that much code in any given assignment, But part of the challenge in working with programs at this scale is that you have to develop a knack for reading existing code and seeing where you need to make changes and what you need to leave alone. Again, this is realistic. By some estimates, practicing programmers spend four times as much time reading code than they do writing it.
Read the assignment page.
Get the starting code and set up the project in your IDE.
Write your solution, compiling, testing, & debugging as you go.
Commit & push your changes.
After the due date, watch for your final grade report.
Review the solution and the instructor’s tests.
Assignment pages describe the purpose of the assignment, describe a problem and related program, and provide a limited number of examples program inputs and outputs.
As noted above, in a typical assignment you will be altering a few components of a larger program.
The starting code includes the rest of that program and, often, preliminary, incomplete, or broken components that you are supposed to finish.
Each assignment page will have an Assignment Management section. That section will tell you
Be wary of changing other files. When the instructor grades your code, those other files will be restored back to their original content. Your code must compile and work with the original content of those other files.
The starting code will be in the form of a git repository on GitHub.
All starting code will include a makefile. When setting up your project in your IDE, you must set it up to compile via that makefile.
Compiling will generally be a matter of running gradlew
or, more likely, letting your IDE compile your code based on settings it gets from build.gradle
.
You may want to try compiling the code “as is”, before making any changes. On some assignments, this will work without errors. In many other cases, missing or incorrect components will result in error messages. But those messages may help you identify what you need to add or fix in the program.
The instructor will supply some tests.
However, the supplied tests might not be complete. It is your responsibility to conduct proper system testing of your code. This is discussed in more detail in Testing Your Code, below.
It is your responsibility to be sure that the last-pushed version of the code before the due date is the one that you want graded.
If you push changes to your code that actually decrease your score compared to earlier versions, you can fix that.
One of the important features of git
is that it allows you to “go back in time” to older versions of your code.
In most circumstances, the instructor will not agree to requests to regrade your code using an older version. Instead, it is your responsibility to find that older version and re-commit and push it.
Preliminary grade reports should be generated automatically any time you push your code. These can take anywhere from a few minutes to an hour to appear, depending on how many tests are being run on your code and on how many other students may be queued up in front of you.
Preliminary grade reports are preliminary.
- The instructor may change test data if it becomes clear that the original set is incorrect or inadequate.
- The instructor may use additional tests for the final evaluation besides those that have been provided to you.
In the final judgment, it is your job to make sure that your code is properly tested and debugged before the due date.
Beginning this semester, you can also get preliminary grade reports immediately in your own directory.
After the due date, the instructor will clone your repository and grade the code that you last pushed there.
You can view this final report from the same link where you obtain the preliminary grade reports.
You will know the final report is available when you receive notice that the assignment grades have been posted to Canvas.
After reading your final grade report, you can return to the “Assignment Manager” section of the assignment page and request access to the instructor’s solution.
This will include the instructor’s source code and, in a .zip
file, the instructor’s tests.
The format of these tests is explained in the section on Testing Your Code, below.
Instructors are fallible too, and sometimes we will have a bad test or other problem. If you discover a bad test when you review the assignment solution, you can ask to have it re-scored.
For any programming assignment, you must
Satisfy the stipulations and limitations of the assignment.
Each assignment will have certain stipulations. You may be limited in what files you can change, what data structures you can use, etc. The point of any assignment is to practice and demonstrate your ability with certain skills and techniques that we have covered.
You might be able to get that program running in some completely different manner, maybe rewriting the whole thing from scratch. It might even be easier for you to do it that way. That doesn’t matter. If you have not employed the stipulated techniques, your submission will be disqualified.
Produce the correct output.
The primary scoring criteria for programming projects is the percentage of the test cases on which your code produces correct output.
While it’s common in beginning programming classes to write interactive code that prompts for inputs and accepts them from the keyboard, few programs these days work that way. Most truly interactive programs will work through a graphic user interface.
But there are still many programs that work through simple text input/output interfaces. These programs typically read and write to files or possibly directly to other programs. Consequently, the I/O formats of these programs are crucial and must be followed very precisely.
The good news is that, in most assignments for this course, I will provide the I/O code so you should not run into issues with formatting.
These are things that you will not get points for in the grading of programming assignments:
The code is “almost correct” or “looks similar to” a correct solution.
Sorry, but almost everyone who submits buggy code thinks that their code is “almost correct” or that it “looks like” code that “should work”. It’s a well-known truism in our field that the last 10% of most projects takes 90% of the time. This is sometimes known as the 90/10 rule or the 90% syndrome.
It’s very easy to get code to a point where it looks like something that should be correct and yet fails every test case submitted to it. The real effort in programming, and what you are rewarded for in the grading, is for getting past that “almost correct” point to something that actually passes one or more tests.
Time spent on the assignment
“I spent so many hours on this assignment.” That’s gratifying, but do you think that your classmates did not? Or, if someone managed to produce correct output while spending only half the time that you did, do you think that I should take points away from them for having worked more efficiently than you?
Telling me how much time you spent on an assignment may earn you sympathy. And in some cases I may want to talk with you about whether you are using your time in a smart way. I may be able to suggest more efficient ways for you to approach your coding, testing, and debugging. But it does not demonstrate your mastery of whatever techniques the assignment is trying to showcase.
Testing is an important and non-trivial part of any programming project, and I regard it as part of your duties in any programming assignment.
There are two kinds of testing that we practice in this course, system testing and unit testing.
The assignment makefile will be set up to run both kinds of test automatically, including checking the output to see if your code passes or fails the test.
You will need to know, however, how to launch the tests “manually” fom the command line so that, if you are failing a particular test, you can run it in a debugger.
System testing is the testing of the entire program. This is the type of testing that you are most used to from your earlier programming courses. You run the program, feed it appropriate input, and observe the outputs to see if they are correct.
Don’t be satisfied with the one or two examples given on the assignment web page. Make up your own test data. Proper choice of test data was discussed in the prerequisite course CS250, and I expect you to use that knowledge.
The command
make tests
will compile the code if necessary and then will run the system and unit tests.
System tests are typically packaged in the Tests/
directory.
Each subdirectory of Tests/
represents a single test case. Inside that subdirectory, you will find one or more of the following file types:
Before the tests have been run:
.in
.param
.yaml
file)..expected
After the system tests have been run, additional files may appear in that directory:
.out
.diff
.out
file do not match the expected output in the .expected
file, this file will describe the difference between the actual and expected output.
If this file is empty, it means that no differences could be found.
The assignment page will describe how to run the main program. You can do this with system test data of your own devising or with, the appropriate path, data files from the instructor-provided Tests/
directory.
You are strongly encouraged to do additional system testing beyond what is provided by the instructor.
You can simply run the program, as described in the assignment page, with your own inputs.
A unit test is a test of a module (typically a single class, sometimes a single function) in isolation from the rest of the program.
Because the module being tested cannot be executed by itself, unit testing requires programmers to write scaffolding code to wrap the module into something that can be executed, providing a way to feed inputs to the module and collect the outputs from it.
Although this may seem like more work, writing code that you won’t actually be turning in for grading, it can make it easier to test your class and much, much easier to debug it.
Real programmers do a lot of unit testing, and most use a unit test framework to do it. You may already be familiar with frameworks such as JUnit or GoogleTest from CS350 or from your own reading. And if you have not taken CS350 yet, you can look forward to learning about them there.
Unit test frameworks simplify the process of writing tests by providing
It is typical of these frameworks that they are quiet so long as your code is passing the tests, but announce failures immediately, ending the test case as soon as a failure is detected.
For example, a test of a simple Counter
class:
public class Counter {
private int theCount;
public Counter (int initialValue) {theCount = initialValue;};
public void increment() { ... }
public int getValue() { ... }
public boolean isZero() { ... }
};
might look like
@Test
void testConstructor()
{
Counter x = new Counter(23);
assertThat (x.getValue(), is(23));
assertThat (x.isZero(), is(false));
Counter c = new Counter(0);
assertThat (c.isZero(), is(true));
}
@Test
public void testIncrement()
{
Counter x = new Counter(23);
x.increment();
assertThat (x.getValue(), is(24));
assertThat (x.isZero(), is(false));
x.increment();
assertThat (x.getValue(), is(25));
}
If these tests were run on a correctly implemented version of Counter
, you may see nothing at all.
But if these tests were run on a version of Counter
with a buggy implementation of increment
, you might see something more like:
Assertion x.getValue() is(24) failed in TestCounter.java, line 14.
In many assignments, if you are assigned to implement a class that is a small portion of the overall program, I may use unit tests like these to exercise your code.
Unit tests are not a replacement for system testing. You are still responsible for doing your own system tests.
You are not responsible for testing your program on illegal inputs.
An illegal input is one that is specifically prohibited by the problem statement or one for which no behavior has been specified for the program to follow.
It’s actually not possible to test programs on illegal inputs because you cannot pass or fail such a test. A program can do anything it wants on an illegal input (including crash) because, by definition, no behavior has been specified.
If your testing reveals a problem, debug your code (also here (250/333), here (252) and here (252).
This may save you a lot of time and effort in debugging by giving you an easy way to relaunch a failing test as a starting point for using a debugger.
Once you have fixed the current problem, go back to the previous step and re-test.
What you will find in an assignment directory:
src/
All source code. This is divided between hte main program code in src/main/java
and unit test ode in src/test/java
.
java/
directory. For example, a Java class cs361.asst4.Data
would be stored in src/main/java/cs361/asst4/Data.java
or, if it were unit test code, in src/test/java/cs361/asst4/Data.java
.src/test/data/
.Tests/
Test cases. Inside here ae directories for individual test cases. Within each test case, the most common contents will be
*.in
, an input data file*.expected
, the expected output from the program.After you have run system tests, you may find additional files in these directories:
*.in
: Input data supplied to standard in (cin
) for this test.*.out
: The actual output of your program from the last time this test case was run (via either make tests
or make systemTests
).*.err
: Any error/debugging output written to the standard error stream. (via either make tests
or make systemTests
).*.message
: A description of the test results.*.time
: How much time (in seconds) it took to run the test.gradle/
, gradlew
, gradlew.bat
, the Gradle build manager that is used to compile the code and run the tests
build.gradle
, settings.gradle
, the build control files that customize the build for this project.