CS312 - Spring 2012 - Class 5
administrative
- assignment 1 grades back soon
- assignment 2: how'd it go?
- assignment 3
- writing unit tests for assignment 2
- you will be graded on how well your unit tests catch bad code
- you will get brownie points if you're unit test catch problems in other people's code :)
- will need to use git to track changes for your assignment
- you will submit the commit log
- assignment 2 grading will rely on assignment 3 unit tests
- immediate feedback
- some of these exercises push back assignment feedback
- if you would like feedback sooner (or have particular questions about your assignment submission) come talk to me and we can look at your code together
- reminder about computer use
Git recap
- how did the git lab go?
- git is a distributed version control system
- contrast this with a centralized version control system
- in this case there is a centralize version control server
- all commits, etc. interact with the one server
- if it's down, you can't use version control
- for distributed version control, many local copies exist
- often there is a single remote repository that acts as the production version, but that doesn't have to be the case
- look at the diagram at:
http://gitready.com/beginner/2009/01/21/pushing-and-pulling.html
- most of what we looked at in the Git lab was working with a single local repository
- you created a new repository
- you added things to the index
- you committed things from the index to your local repository
- you looked at using diff, log and other tools to compare differences and look at the history
- you looked at creating different branches
- when you work in a team environment, there is often another layer, which is a remote repository
- this could be a remote server that everyone is uploading to
- or this could just be another local copy of someone else on the team
- push and pull
- Git can interact between repositories using the push and pull (or fetch) commands
- to get started interacting with a remote repository, you first "clone" it using git clone
- this will create a local repository with the same contents as the remote repository
- if at any point you'd like to synch your local version with the remote version you can use fetch or pull to synch up
- fetch updates your local repository with the contents of the remote repository
- pull updates your workspace with the contents of the remote repository
- note that in any of these cases, you could get conflicts which you would need to resolve
- just like you can get conflicts when you try and merge branches
- you can work away on your local repository until you're ready to move your changes to the remote repository
- you can commit as many times as you want to your local repository and the changes will stay local
- when you institute a "git push" it copies the contents from your local repository to the remote repository
- if you run into a conflict, it will not let you commit unless your are up to date
- for example, let's say you've pulled some version of the data, but since then, someone else has pushed some data
- Git will NOT allow you to commit your changes because you're not up to date with the most recent version
- to resolve this
- you'll need to do a fetch/pull to get the latest version
- you'll then need to resolve any conflicts
- then you can push your changes to the remote server (assuming someone else hasn't pushed again in front of you :)
Evaluating code
-
http://www.cs.middlebury.edu/~dkauchak/classes/cs312/handouts/code_eval.html
- follow the instructions
- this is both an educational exercise and will be partially used for grading
- be clear, concise and constructive
- when you're done, e-mail me your evaluation
coding style
- how was it to read other people's code?
- any challenges?
- when you're writing code:
- half of it is about getting it correct
- the other half is about communicating to someone else reading your code
- that may be you
- or it may be other people
- many of the conventions that we follow allow for better communication:
- comments
- variable, function, class naming
- writing code in small functional units
testing code
- Would you be confident telling someone that you have found all of the issues with the code?
- put another way, if you didn't find any problems, would you be willing to tell someone that the code is correct?
- if you found an error, did you know where the problem was?
- Is this how you test your code? How do you test your code?
- there are many ways of testing code and functionality (some better than others)
unit testings
- there are a number of downsides to how you tested the code
- may not have tested all of the functionality of the code
- when you find a bug, it can be very hard to track down where the bug came from
- unit tests are functions/programs/tests that check the functionality of your code
- a "unit" is a single (generally small) functional chunk of the program
- often it is a function or a few lines of code
- unit tests are programs and can be run repetitively
- they provide a concrete way of continuously testing your code
- tests are written and confirmed to be working before moving on to other coding
- all tests are run when new code is added, assuring that new code doesn't break old code
- unit testing frameworks allow a lot of this to happen
- organize tests
- run them regularly
- etc.
unit testing in Ruby
- unit tests in Ruby are written as a separate class
- the class MUST inherit from Test::Unit::TestCase
- any method in this class that starts with "test" will be automatically run as a unit test (more on running unit tests soon)
- inside the unit test you can assert that statements are true (or false)
- if they are true, then the test passes
- if the are not true, then the test fails
- look at test_linked_list.rb in
UnitTesting code
- let's say we wanted to write some unit tests for the LinkedList class we've been using
- we create a new class (commonly we'll also start the unit testing class name with Test)
- the class should inherit from Test::Unit::testCase
- you'll need to require "test/unit" at the top of your unit testing class
- you'll also need to require the class/code you're trying to test
- often we'll do require rather than require_relative since when it actually gets deployed, the relative paths may move around
- looking at the LinkedList class
- what are some of the functional units?
- how can we test to make sure that they are working correctly?
- add/remove
- add some elements
- remove them and make sure that we get back what we added
- ideally, it would be nice to test these individually, but sometimes that can be hard, particularly when defining a class of objects
- the test_add_and_remove method is a unit test
- it must start with "test"
- we use "assert_equal" to check if we obtain the expected output
- the first value is the value that we expect to get
- the second is the value of the thing we're testing
- test_add_and_remove_better is another unit test
- what does it do?
- same as test_add_and_remove but does it using iteration
- we can use the range 1..10 to add the number 1 through 10
- 10.downto(1) allows us to iterate over the number 10 down to 1
- for each number, we have an assert to make sure that it has the right value
- code organization
- if we're testing right, we'll have one test class for each class/file
- if we put these all in the same directory, things would tend to get cluttered
- the most common convention is the following:
- put all of the class files in a directory called "lib"
- put all of the test files in a directory called "test"
- running the unit tests
- unit tests can be run just like any other program
ruby test_linked_list.rb
- because we inherited from Test::Unit::testCase the tests are automatically run with the class is defined
- the only catch is that we used require rather than require_relative
- we can add a directory where ruby looks for files (similar to the classpath in Java) using the -I command
- so to run this test:
ruby -I "lib" test/test_linked_list.rb
(assuming we're at the base directory)
- if we do that we see the following output:
Run options:
# Running tests:
..
Finished tests in 0.000552s, 3623.1884 tests/s, 23550.7246 assertions/s.
2 tests, 13 assertions, 0 failures, 0 errors, 0 skips
- the last line tells us that:
- 2 tests were run
- 13 assertions were checked
- there weren't any errors or failures
- none of the tests were skipped
- when tests don't work
- let's pretend we made a mistake in the LinkedList::remove method
- comment the linke that sets @head = @head.next, i.e. we forgot to actually remove the node
- if we rerun the test
Run options:
# Running tests:
FF
Finished tests in 0.000782s, 2557.5448 tests/s, 5115.0895 assertions/s.
1) Failure:
test_add_and_remove(TestLinkedList) [test/test_linked_list.rb:20]:
<2> expected but was
<3>.
2) Failure:
test_add_and_remove_better(TestLinkedList) [test/test_linked_list.rb:30]:
<9> expected but was
<10>.
2 tests, 4 assertions, 2 failures, 0 errors, 0 skips
- for each assertion that failed, we get a printout
- which test failed
- which line in the test failed
- the answer we expected to get
- the answer we actually got
- the summary shows 2 failed tests
- notice that the first assertion that fails causes the whole test to fail and stop executing
- 4 assertions vs. 13 above