This section does not contain any design requirements (i.e. you could complete the whole project without reading this section). However, we’ve compiled our general guidelines for design, development, and testing.
Design Workflow
This project has a lot of moving parts, and it’s normal to feel overwhelmed by the amount of requirements you need to satisfy in your design. Here is one suggestion for how you can break down this project into more manageable steps:
- Read through the entire spec. It’s easy to miss a design requirement, which might cause you trouble later when you have to redo your design to meet the requirement you missed. We suggest reading through the entire spec front-to-back at least twice, just to make sure that you have internalized all the design requirements.
- Design each section in order. Start with user authentication: How do you ensure that users can log in? Focus on getting
InitUser
andGetUser
properly designed, and don’t worry about file storage or sharing yet. Then, after you’re satisfied with your login functionality, move on to the file storage functions. Don’t worry about sharing yet, and just make sure that a single user is able toLoadFile
andStoreFile
properly. Then, you can move on toAppendToFile
. Finally, once you’re satisfied with your single-user storage design, you can move on to sharing and revoking. - Don’t be afraid to redesign. It’s normal to change your design as you go. In particular, if you follow the order of functions in the spec, then
AppendToFile
might result in changes toLoadFile
andStoreFile
. Also,RevokeAccess
might result in changes toCreateInvitation
andAcceptInvitation
. It’s easier to change your design while you’re in the design phase; by contrast, it’s harder to change your design after you’ve already implemented it in code.
Coding Workflow
- Stay organized with helper functions. If you fit all your code in 8 functions, it’s easy for the functions to get bloated and hard to debug. By contrast, if you organize your code into helper functions, you can reuse code without needing to copy-paste code blocks, and you can also write unit tests to check that each helper function is working as intended.
- Test as you go. Don’t write a huge chunk of code and then test it at the end. This usually results in a failed test, and now you have no idea which part of the giant code block is broken. Instead, write small pieces of code incrementally, and write tests as you go to check that your code is doing what it’s supposed to.
- Don’t split the coding between partners. Sometimes, a 2-person project group will try to have each group member independently write half of the functions. As a design-oriented project, the code in different functions will often be connected in subtle ways, and it is difficult (if not impossible) to write code without understanding all the code that has been written so far. A better approach is to work together to figure out the high-level organization of your code. Ideally, you’d use a technique like pair programming to ensure that both partners understand the code being written. The only scenario where writing code individually might be useful is for isolated helper functions, where the behavior is clearly documented and the function can be tested and debugged in isolation. Staff are not responsible for helping you understand code that your partner wrote.
Development Environment
Visual Studio Code (VSCode)
VSCode is a very commonly used IDE, and provides a powerful set of code and debugging environments that can be exploited for Golang Projects. To setup VSCode for this project, follow these steps:
- Install Golang. Make sure you have Golang installed before starting this guide.
- Install the GoLang extension. In the “Extensions” tab (Use Ctrl+Shift+X to navigate you can’t find it), search up the Go extension that is created by Google.
- Install the debugging environment Once the extension is installed, the lower right corner might and most likely will pop up a warning stating that analysis tools might be missing. If so, click on the install, and wait for the analysis tools to install. If you missed this the first time, press (Ctrl+Shift+P) and search up “Extensions: Install Missing Dependencies,” and follow the instructions.
Getting Started Coding
After your design review, you’re ready to start implementing your design in code. Follow these steps to get started:
- Install Golang. Go v1.20 is recommended.
- Complete the online Golang Tutorial.
- The tutorial can take quite a bit of time to complete, so plan accordingly.
- The tutorial is a helpful tool that you may end up referencing frequently, especially while learning Go for the first time.
- Accept the Project 2 GitHub Classroom Invite Link.
- At this step, you may receive an email asking you to join the
cs161-students
organization.
- At this step, you may receive an email asking you to join the
- Enter a team name. If you’re working with a partner, only one partner should create a team - the other partner should join the team through the list of teams.
- Clone your repository using the
git clone
command. - Feel free to review 61B’s
git
resources (commands, commands continued, guide, common issues) for a refresher!
- Clone your repository using the
- In
README.md
, make sure to include the student IDs and emails of both you and your partner, as well as your team GitHub repository link. - In the
client_test
directory of the checked out repository, rungo test
.- Go will automatically retrieve the dependencies defined in
go.mod
and run the tests defined inclient_test.go
andclient_unittest.go
. - It is expected that some tests will fail because you have not yet implemented all of the required functions.
- Go will automatically retrieve the dependencies defined in
- Optionally, we’ve provided a unit test framework that you can access in the
client
directory, where you can create unit tests for your implementation-specific functions insideclient_unittest.go
.- If you would like to only run unit tests, please rename the unit test file to
client_unit_test.go
(and an_
betweenunit
andtest
) and rungo test
in theclient
directory. Reminder: If you later want to run unit tests with client tests together, make sure to change the name of the file back toclient_unittest.go
and rungo test
from theclient_test
directory.
- If you would like to only run unit tests, please rename the unit test file to
If the starter code is buggy and you need to pull updates from the starter code repo, you can do so with these steps:
- Run only once:
- If you use HTTP:
git remote add starter https://github.com/cs161-staff/project2-starter-code.git
- If you use SSH:
git remote add starter git@github.com:cs161-staff/project2-starter-code
- If you use HTTP:
- Run each time you need to pull updates:
git pull starter main
Please refer to our Troubleshooting Tips for an in-depth debugging guide!
Testing with Ginkgo
This section provides some basic documentation for Ginkgo, which is the framework you’ll be using to write your own test cases.
First, we recommend reading through the basic tests in client_test.go, especially the first few ones, since those are well-documented in-line. Then, come back to this documentation.
Basic Usage
You should be able to write most of your tests using some combination of calls to –
- Initialization methods (e.g.
client.InitUser
,client.GetUser
) - User-specific methods (e.g.
alice.StoreFile
,bob.LoadFile
) - Declarations of expected behavior (e.g.
Expect(err).To(BeNil())
)
Asserting Expected Behavior
To assert expected behavior, you may want to check (a) that an error did or didn’t occur, and/or (b) that some data was what you expected it to be. For example:
// Check that an error didn't occur
alice, err := client.InitUser("alice", "password")
Expect(err).To(BeNil())
// Check that an error didn't occur
err = alice.StoreFile("alice.txt", []byte("hello world"))
Expect(err).To(BeNil())
// Check that an error didn't occur AND that the data is what we expect
data, err := alice.LoadFile("alice.txt")
Expect(err).To(BeNil())
Expect(data).To(Equal([]byte("hello world")))
// Check that an error DID occur
data, err := alice.LoadFile("rubbish.txt")
Expect(err).ToNot(BeNil())
Organizing Tests
You can organize tests using some combination of Describe(...)
containers, with tests contained within Specify(...)
blocks. The more organization you have, the better! Read more about how to organize your tests here.
Running a Single Test Case
If you would like to run only one single test (and not both the client_test
and client_unittest
test suites), you can change the Specify
of that test case to a FSpecify
. For instance, if you have a test
Specify("Basic Test: Load and Store", func() {...})
you can rename it to
FSpecify("Basic Test: Load and Store", func() {...})
Then, if you click the Run/Debug
button, you will be focusing on that specific test. If you have multiple FSpecify
test cases, then all those will be run.
Warning: Please remove all FSpecify
statements when you submit to the autograder (otherwise, the autograder will only run tests labelled FSpecify
) or potentially break the autograder. We cannot promise we will re-run the autograder for you if you forgot to remove them.
Optional: Measure Local Test Coverage
To measure and visualize local test coverage (e.g. how many lines of your implementation your test file hits), you can run these commands in the root folder of your repository:
go test -v -coverpkg ./... ./... -coverprofile cover.out
go tool cover -html=cover.out
Coverage over your own implementation may serve as an indicator for how well your code will perform (with regards to coverage flags) when compared to the staff implementation! It should also help you write better unit testing to catch edge cases.