Preparing and Submitting Programming Assignments
Steven J. Zeil
This document explains the procedures for preparing and turning in programming assignments in this course. For information on non-programming assignments, see here.
1 Lots to Read, Little to Write
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 C++ 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.
2 Walking Through A Typical Assignment
-
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.
2.1 Read the Assignment Page
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.
2.2 Get the starting code and set up the project in your IDE.
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
- Where to get the starting code.
- Which files you are supposed to change.
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.
- You will need to clone that repository into your own working directory.
-
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.
2.3 Write your solution, compiling, testing, & debugging as you go.
-
Compiling will generally be a matter of running
make
or, more likely, telling your IDE to runmake
for you. -
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 will 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.
2.4 Commit & push your changes.
2.4.1 Submitting
- You will “submit” your work by pushing your changes to your git repository.
- You may do this any number of times up till the due date.
- In fact, it is probably a good idea to commit and push your changes at the end of every work session.
- Each time you push changes to the GitHub repository, your code will be compiled and tested and a preliminary grade report will be generated.
- After the due date, the instructor will clone your repository and grade the code that you last pushed there.
- If you have a nearly working solution, make some changes and discover that they actually make your code perform worse, remember that git allows you to retrieve previously committed versions.
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 have a nearly working solution, make some changes and discover that they actually make your code perform worse, remember that git allows you to retrieve previously committed versions.
-
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.
2.4.2 Preliminary grade reports.
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.
2.5 After the due date, watch for your final grade report.
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.
2.6 Review the solution and the instructor’s tests.
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.
2.6.1 Appeals
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.
3 Policies
3.1 The Grading Criteria for Programming Assignments
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.
3.2 The Anti-Grading Criteria for Programming Assignments
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.
4 Testing Your Code
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.
4.1 System Testing
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.
4.1.1 Running System Tests Automatically
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
- This file contains input text that should be fed to the program’s standard in, as if typed form the keyboard.
.param
- this file contains the text oc command line parameters that should be supplied to the program when it is launched. (This information can also be supplied inside the
.yaml
file). .expected
- This file contains the text that is expected to be seen as the output of the program.
-
After the system tests have been run, additional files may appear in that directory:
.out
- The actual output obtained when running the program.
.diff
- If the contents of the actual output in the
.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.
4.1.2 Running System Tests Manually
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. For example, if the assignment page said to run your program like this
./yourProgram < yourData.txt
and you have a systems test Tests/test01/
, you could run
./yourProgram < Tests/test01/*.in
4.1.3 Writing Your Own System Tests
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.
4.2 Unit Testing
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
- a simple structure within which one can set up ADTs and apply operations to the ADT instance that are supposed to change it in some fashion,
- a set of assertions that can be used to examine the ADT after such a change to see if it is behaving as it should. These assertions allow the test code to automatically check the ADT to see if it passes or fails, without printing ADT outputs and relying on a human to scan the output for errors.
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:
class Counter {
int theCount;
public:
Counter (int initialValue = 0);
void increment();
int getValue() const;
bool isZero() const;
};
might look like
UnitTest (testConstructor)
{
Counter x (23);
assertThat (x.getValue(), is(23));
assertThat (x.isZero(), is(false));
Counter c;
assertThat (c.isZero(), is(true));
}
UnitTest (testIncrement)
{
Counter x (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
, all that likely would be seen is a final summary:
Passed 2 of 2 tests.
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 testIncrement, line 14.
Passed 1 of 2 tests. Failed 1 tests.
In some 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. Sometimes, I will provide those unit tests as part of the assignment code, written using this test framework. If you see files named unittest.h
and unittest.cpp
included in an assignment’s code, then you can expect to find unit tests in files named test*.cpp
. The makefile in such assignments will probably produce an executable program called unittests
or unittests.exe
, which you can run to apply those unit tests.
Unit tests are not a replacement for system testing. You are still responsible for doing your own system tests.
4.3 Illegal Inputs
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, for example, a problem statement says “This program reads a positive integer N…” then don’t bother feeding it zero or negative numbers during testing.
- By way of contrast, if the problem statement says “This program … [behaves normally] … for positive N and prints an error message when N is negative or zero”, then those inputs are no longer illegal – the behavior of the program on those inputs has been specified and can be (and should be) tested.
5 Debugging Your Code
If your testing reveals a problem, debug your code (also here (250/333), here (252) and here (252).
- If I have provided my own unit tests for part of your code, and you receive a message indicating that you are failing one of those tests, you can re-run that test case only by putting its name in the command line:
./unittests nameOfFailingTestcase
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.
6 Files Provided in Assignment Directories
Typically, each assignment directory will include a number of .cpp and .h files. Some of these are for you to modify, some should be left alone. You can tell which should be modified by consulting the list of files to be submitted. Anything that you aren’t going to turn in must be left unchanged.
Other files that you may find in the assignment directories include:
-
makefile
:This is a standard Unix tool for project management. If an assignment comes with a
makefile
, then you can compile the program by giving the commandmake
and you can clean up your directory when the project is done by giving the commandmake clean
-
make.dep
,*.d
:These files support the
makefile
. You can ignore them. If you get a warning message about a “missing make.dep”, ignore it. Themake.dep
file will be created automatically. -
Doxyfile
Configuration file for the
doxygen
documentation generator. In assignments where this is provided, you can generate class API documentation with the commandmake docs
or
doxygen
on systems where the
doxygen
program is installed (e.g. the CS Linux servers). -
unittest.h, unittest.cpp:
On assignments where your task focuses on a specific class (or small group of classes), this permits more focused testing of that class than can be accomplished by running entire larger programs in which that class is merely one component.
These are normally accompanied by one or more files named
test*.cpp
that contain the actual tests. -
Tests/
: A directory containing system tests.Within this directory you may find subdirectories describing individual test cases, and these may contain:
*.in
: Input data supplied to standard in (cin
) for this test.*.params
: Command line parameters used to run the program for this test.*.dat
: Files mentioned in the command line parameters.*.out
: The actual output of your program from the last time this test case was run (via eithermake tests
ormake systemTests
).*.expected
: The expected output that your program is supposed to produce.*.diff
: A description of the difference between the actual and expected outputs. If this file is present but empty, the actual and expected outputs match.