6.031 — Software Construction
Fall 2017

Problem Set 1: Around the World

Iteration 1 due
Monday, September 25, 2017, 10:00 pm
Code reviews due
Friday, September 29, 2017, 11:00 am
Iteration 2 due
Monday, October 2, 2017, 10:00 pm

The purpose of this problem set is to give you practice with test-first programming. Given a set of specifications, you will write unit tests that check for compliance with the specifications, and then implement code that meets the specifications.

Get the code

To get started,

  1. Ask Didit to create a remote psets/ps1 repository for you on Athena.
  2. Pull the repo from Athena using Git:
git clone ssh://[username]@athena.dialup.mit.edu/mit/6.031/git/fa17/psets/ps1/[username].git ps1

If you need a refresher on how to create, clone, and import your repository, see Problem Set 0.

Overview

The theme of this problem set is to build a toolbox of methods for working with geographic points of interest located with latitude and longitude. You can read the Javadoc documentation for all classes in the problem set, generated from the .java source files.

Since we are doing test-first programming, your workflow for each method should be (in this order)…

  1. Study the specification of the method carefully.
  2. Write JUnit tests for the method according to the spec.
  3. Implement the method according to the spec.
  4. Revise your implementation and improve your test cases until your implementation passes all your tests.

On Problem Set 0, we graded only your method implementations. On this problem set, we will also grade the tests you write. In particular:

  • Your test cases should be chosen using the input/output-space partitioning approach. This approach is explained in the reading about testing.
  • Include a comment at the top of each test suite class describing your testing strategy – how you partitioned the input/output space of each method, and then how you decided which test cases to choose for each partition.
  • Your test cases should be small and well-chosen. Don’t use a large set of data for each test. Instead, create inputs carefully chosen to test the partition you’re trying to test.
  • Your tests should find bugs. We will grade your test cases in part by running them against buggy implementations and seeing if your tests catch the bugs. So consider ways an implementation might inadvertently fail to meet the spec, and choose tests that will expose those bugs.
  • Your tests must be legal clients of the spec. We will also run your test cases against legal, variant implementations that still strictly satisfy the specs, and your test cases should not complain for these good implementations. That means that your test cases can’t make extra assumptions that are only true for your own implementation.
  • Put each test case in its own JUnit method. This will be far more useful than a single large test method, since it pinpoints where the problem areas lie in the implementation.
  • Again, keep your tests small. Don’t use unreasonable amounts of resources (such as MAX_INT size lists). We won’t expect your test suite to catch bugs related to running out of resources; every program fails when it runs out of resources.

You should also keep in mind these facts about specifications:

  • Preconditions. Some of the specs have preconditions. Recall from the specs reading that when preconditions are violated by the client, the behavior of the method is completely unspecified.
  • Underdetermined postconditions. Some of the specs have underdetermined postconditions, allowing a range of behavior. When you’re implementing such a method, the exact behavior of your method within that range is up to you to decide. When you’re writing a test case for the method, you must allow the implementation you’re testing to have the full range of variation, because otherwise your test case is not a legal client of the spec as required above.

Finally, in order for your overall program to meet the specification of this problem set, you are required to keep some things unchanged:

  • Don’t change these classes at all: the classes Angle, CardinalDirection, and PointOfInterest should not be modified at all.
  • Don’t change these class names: the classes Angular, Bounds, Search, AngularTest, BoundsTest, and SearchTest must use those names and remain in the geo package.
  • Don’t change the method signatures and specifications: The public methods provided for you to implement in Angular, Bounds, Search must use the method signatures and the specifications that we provided.
  • Don’t include illegal test cases: the tests you implement in AngularTest, BoundsTest, and SearchTest must respect the specifications that we provided for the methods you are testing.

Aside from these requirements, however, you are free to add new public and private methods and new public or private classes if you wish. In particular, if you wish to write test cases that test a stronger spec than we provide, you should put those tests in a separate JUnit test class, so that we don’t try to run them on staff implementations that only satisfy the weaker spec. We suggest naming those test classes MyAngularTest, MyBoundsTest, MySearchTest, and we suggest putting them in the geo package in the test folder alongside the other JUnit test classes.


Problem 1: Computing with angles

In this problem, you will test and implement the methods in Angular.java.

You’ll find Angular.java in the src folder, and a JUnit test class AngularTest.java in the test folder. Separating implementation code from test code is a common practice in development projects. It makes the implementation code easier to understand, uncluttered by tests, and easier to package up for release.

Read the specs. Before you start, read the specs in Angle.java and Angular.java. Since the spec for displacement() includes images, you may find it easier to view the generated Javadoc documentation. You can also play around with latitudes, longitudes, and displacements to understand these specs better.

Erratum

The Angle constructor has a bug where passing in a very large value for minutes can result in integer overflow, violating the spec. The constructor can still create all possible Angle values by putting large values in the degrees parameter instead. Use that workaround if you need to make such angles.

We are not applying or releasing a patch for this bug, to ensure that everybody, including Didit, is using the same implementation of Angle.

reading exercises

Angle

What does new Angle(90, 0, 0, CardinalDirection.SOUTH) represent?

(missing explanation)

What does new Angle(0, 0, 0, CardinalDirection.EAST) represent?

(missing explanation)

Which of the following represent the equator?

(missing explanation)

What is the result of new Angle(0, 1200, 0, CardinalDirection.NORTH).degrees()?

If this code has an error, write “error” instead.

(missing explanation)

toDegrees()

What should Angular.toDegrees(new Angle(0, 15, 0, CardinalDirection.EAST)) return?

Write the shortest legal return value. If this code has an error, write “error” instead.

(missing explanation)

What should Angular.toDegrees(new Angle(90, 0, 0, CardinalDirection.SOUTH)) return?

Write the shortest legal return value. If this code has an error, write “error” instead.

(missing explanation)

What should Angular.toDegrees(new Angle(90, 0, 0, CardinalDirection.UP)) return?

Write the shortest legal return value. If this code has an error, write “error” instead.

(missing explanation)

The postcondition of toDegrees is underdetermined. Which of the following values are legal return values of Angular.toDegrees(new Angle(0, 0, 0, CardinalDirection.NORTH))?

(missing explanation)

Which of the following are legal values of the expression

Angular.toDegrees(new Angle(0, 0, 0, CardinalDirection.NORTH))
 == Angular.toDegrees(new Angle(0, 0, 0, CardinalDirection.NORTH))

(missing explanation)

displacement()

Which of the following are legal values of Angular.displacement(new Angle(20, 0, 0, CardinalDirection.NORTH), new Angle(30, 0, 0, CardinalDirection.SOUTH))?

(missing explanation)

Which of the following are legal values of Angular.displacement(new Angle(0, 0, 0, CardinalDirection.EAST), new Angle(0, 0, 0, CardinalDirection.SOUTH))?

(missing explanation)

Which of the following are legal values of Angular.displacement(new Angle(175, 0, 0, CardinalDirection.EAST), new Angle(175, 0, 0, CardinalDirection.WEST))?

(missing explanation)

Which of the following are legal values of Angular.displacement(new Angle(0, 0, 0, CardinalDirection.EAST), new Angle(0, 0, 0, CardinalDirection.WEST))?

(missing explanation)

Test. Create tests for toDegrees() and displacement(), and put them in AngularTest.java. You should partition both functions, write down your partitions in a testing strategy comment, and choose test cases to cover the partitions.

Be careful that your test cases for both methods respect their underdetermined postconditions.

For this problem and all other problems on this problem set, you are free to rename, rewrite, replace, or remove the provided example tests and their assertions.

Commit. Commit your tests for this problem to git, with a good commit message. Committing frequently – whenever you’ve written some tests, fixed a bug, or added a new feature – is a good way to use version control, and will be a good habit to have for your team projects.

reading exercises

Commit

Use git lol to find the ID of the commit you just made, and put it here.

Implement. Implement toDegrees() and displacement() in Angular.java. Revise your implementation and your tests until all your tests pass.

Commit and push. Commit again, and this time push your commits to Athena so that Didit can compile them.

reading exercises

Commit and push

Go to Didit to look at its report about the commit you just pushed. Put the ID of that commit here.

Run. You can run Main.java to apply these methods to some example points of interest provided by the staff. Main.java will not be used in grading, and you are free to edit it as you wish.


Problem 2: Latitude-longitude bounds

In this problem, you will test and implement the methods in Bounds.java. The latitudeRange() and longitudeRange() methods find a range of latitudes or longitudes that bound a set of points.

Read the specs. Before you start, read the specs in Bounds.java. You can also play around with latitudes, longitudes, and displacements to understand these specs better.

reading exercises

latitudeRange()

Suppose you call Bounds.latitudeRange() on a list of two points of interest, one at 42° N 25° W and the other at 17° N 25° E. Which of the following are legal values for the returned list of angles?

(missing explanation)

longitudeRange()

Suppose you call Bounds.longitudeRange() on a list of two points of interest, one at 90° N 30° W and the other at 0° N 40° E. Which of the following are legal values for the returned list of angles?

(missing explanation)

Suppose you call Bounds.longitudeRange() on a list of two points of interest, one at 20° N 100° W and the other at 0° N 90° E. Which of the following are legal values for the returned list of angles?

(missing explanation)

Test. Create tests for latitudeRange() and longitudeRange(), and put them in BoundsTest.java. Partition both functions, write down your partitions in a testing strategy comment, and choose test cases to cover the partitions.

Writing small helper methods in your test classes can help DRY up the tests, make them easier to read, and prevent copy-and-paste bugs. For helper methods that you add to Bounds, be sure to write specs for them, and remember to test those methods in your own MyBoundsTest class, not in BoundsTest.

For this problem and all other problems on this problem set, you are free to rename, rewrite, replace, or remove the provided example tests and their assertions.

reading exercises

Commit

Commit your tests for this problem. Use git lol to find the ID of the commit you just made, and put it here.

Implement. Implement latitudeRange() and longitudeRange() in Bounds.java. Revise your implementation and your tests until all your tests pass.

reading exercises

Commit and push

Commit and push your changes. Go to Didit to look at its report about the commit you just pushed. Put the ID of that commit here.

Run. Run Main.java to apply these methods to some example points of interest.


Problem 3: Searching points of interest

In this problem, you will test and implement the methods in Search.java, which we could use to build a search engine from raw point-of-interest data.

Read the specs. Before you start, read the specs in Search.java.

reading exercises

collectDuplicates()
PointOfInterest northPole1 = new PointOfInterest(
                               new Angle(90, 0, 0, CardinalDirection.NORTH),
                               new Angle(0, 0, 0, CardinalDirection.EAST),
                               "North Pole",
                               "admire the aurora borealis");
PointOfInterest northPole2 = new PointOfInterest(
                               new Angle(90, 0, 0, CardinalDirection.NORTH),
                               new Angle(35, 0, 0, CardinalDirection.WEST),
                               "North Pole",
                               "beware the polar bear");

Suppose you call Search.collectDuplicates() on a three-element list [northPole1, northPole1, northPole2]. Which of the following are legal values for the returned map?

(missing explanation)

search()

Test. Create tests for collectDuplicates() and search(), and put them in SearchTest.java. Partition both functions, write down your partitions in a testing strategy comment, and choose test cases to cover the partitions.

As in previous problems, be careful that your test cases for collectDuplicates() respect its underdetermined postcondition.

reading exercises

Commit

Commit your tests for this problem. Use git lol to find the ID of the commit you just made, and put it here.

Implement. Implement collectDuplicates() and search() in Search.java. For now, implement only the minimum required behavior for collectDuplicates(), which infers that two points of interest are duplicates if they have exactly the same latitude, longitude, and name.

reading exercises

Commit and push

Commit and push your changes. Go to Didit to look at its report about the commit you just pushed. Put the ID of that commit here.

Run. Run Main.java to apply these methods to some example points of interest. Edit the file example-points.csv to add, remove, or change the examples.


Problem 4: Better maps

In this problem, you will implement one additional kind of evidence in collectDuplicates(). Many companies that provide mapping services do so by aggregating information from multiple sources, and de-duplicating the data is an important problem.

Here are some ideas for evidence of duplication. Feel free to experiment with your own.

  • Similar location. Points of interest are not really zero-dimensional points, and different data sources might place them at slightly different locations. So you could implement a tolerance for two locations to be considered close enough. Relating latitude-longitude and physical location is complicated, since the earth is not a sphere, continents drift, etc. As a start, note that one second of longitude equals between 0 and about 100 feet, depending on latitude.

  • Similar names. Names with small string edit distance might simply contain typos. Names with larger differences might still point to the same place, for example The Grind Café vs. Cafe Grind. You could be smarter about tolerating differences in names.

  • Missing data. Many real-word systems use magic numbers for location when the exact position of a place is unknown. For example, the geographic center of a country or state if that’s all the data source knows; or 0°N 0°E if there’s no information at all. If one point of interest seems to be missing its position in this way, you could consider it as having no information about its position.

Keep in mind that whatever additional evidence you implement, your collectDuplicates() must still obey the spec. To test your specific implementation, make sure you put test cases in your own MySearchTest class rather than the SearchTest class that we will run against staff implementations. Your work on this problem will be judged by the clarity of the code you wrote to implement it and the test cases you wrote to test it.

reading exercises

Commit and push

Commit and push your changes. Go to Didit to look at its report about the commit you just pushed. Put the ID of that commit here.


Submitting

Make sure you commit AND push your work to your repository on Athena. We will use the state of your repository on Athena as of 10:00pm on the deadline date. When you git push, the continuous build system attempts to compile your code and run a subset of the autograder tests. You can always review your build results at didit.csail.mit.edu.

Didit feedback is provided on a best-effort basis:

  • There is no guarantee that Didit tests will run within any particular timeframe, or at all. If you push code close to the deadline, the large number of submissions will slow the turnaround time before your code is examined.
  • If you commit and push right before the deadline, the Didit build does not have to complete in order for that commit to be graded.
  • Passing some or all of the public tests on Didit is no guarantee that you will pass the full battery of autograding tests — but failing them is almost sure to mean lost points on the problem set.

Grading

Your overall ps1 grade will be computed as approximately:
35% iter1 autograde + 5% iter1 manual grade + 45% iter2 autograde + 15% iter2 manual grade

The autograder test cases will not change from iter1 to iter2, but their point values will. In order to encourage test-first programming, iter1 autograding will put more weight on your tests and less weight on your implementations. On iter2, autograding will look for both good testing and good implementations.

Manual grading of iter1 will only examine testing. Manual grading of iter2 may examine any part, including re-examining testing, and how you addressed code review feedback.