We tested our new code manually. There's a tool built in to Squeak called SUnit which allows for repeatable test execution. If we add test code to SUnit to check our new filename method we could assure that any time someone makes changes to the FileList class in the future, if they run SUnit, they will know if they did something causing our code to break.
In the world of eXtreme Programming (XP) the rule is that you write the SUnit test case code before you implement your model or solution code. We didn't do that in this case however.
The first check we should make is to actually exercise SUnit. The idea is that the running of all known test cases should not produce any test failures. Once we are certain we can introduce our new test. In general, we would actually run SUnit tests before we introduce a single line of code in FileList, and verify that all tests pass. Then, if there are sufficient tests for FileList, when we run SUnit again after we add code we might expect something to break in a test. That's assuming the changes we make would be caught by an existing test case. Regardless, let's take a moment and review how to run SUnit tests in Squeak.
The process of execution of SUnit tests is to perform the following line of Squeak code:
TestRunner runTests
For now, open up a Workspace (using the World-->open.. menus), and paste this code in. When you perform a "do-it" on the TestRunner runTests code you will see the Test Runner window open up and begin to run. When it has completed running all the SUnit tests you will see a summary.
The next check we should make is to see if there already is a FileList test class in SUnit. It would be a simple matter to add a unique test just for our situation. However, adoption of SUnit into Squeak is a relatively new event and more often than not, there are no SUnit tests pre-existing. Let's look at the class hierarchy for TestCase.
Using a standard class Browser we perform a "find class..." on TestCase.
Let's create a new test case subclass. Select the class TestCase in the hierarchy list pane. I created the following class definition in the code pane.
I'd like to exercise FileList and validate that the original "copy name to clipboard" menu operation and the new "copy just file name to clipboard" menu operation work as expected. Since we cannot expect the environment SUnit is running in to have any specific file we can use for testing, we'll have to create one.
After we create the temporary file we will use just for the tests, we'll perform some operations on it, verify expected results, and then delete the file.
I wrote a few methods to support our intent to test this way. Both these two methods shown below are included in the method cateogry "Private" on our test class. Click on the method categories menu and choose "new category". You will see a list of method categories already defined on the superclasses, and the option to create a new one. I used the existing "Private" category name. Here are the two new methods:
tempFileContents
^ 'test'
createTempFile
| dir time fname fstream |
dir := FileDirectory default.
time := Time now.
fname := time hhmm24 , time seconds printString , '.tmp'.
(dir fileExists: fname)
ifTrue: [dir deleteFileNamed: fname].
fstream := dir forceNewFileNamed: fname.
fstream nextPutAll: self tempFileContents.
fstream close.
^ dir -> fname
The #createTempFile method answers an Association containing the location and actual file name of the temporary file we create. Since we will be using a GUI list and menu to test our new code we'll have to write a little bit of support code to make it easy to interact with the FileList GUI.
fileListMorph
^ ((gui allMorphs
select: [:m | m isKindOf: PluggableListMorph])
select: [:m | m getListSelector = #fileList]) first
Now we can create the test code. SUnit will exercise all methods in subclasses of TestCase that begin with "test" in the selector name. Here's the test code. I put it in the "Running" method category.
testFileNameOperations
| assoc dir fname entry index listMorph string |
Smalltalk isMorphic
ifFalse: [^ nil].
assoc := self createTempFile.
dir := assoc key.
fname := assoc value.
gui := FileList openAsMorph.
"Since we just opened up the FileList we can assume it's directory
is the same as ours."
entry := gui model fileList
detect: [:each | each includesSubString: fname]
ifNone: [].
index := gui model fileList indexOf: entry.
listMorph := self fileListMorph.
listMorph selectionIndex: index.
gui model fileListIndex: index.
"Now that we faked the selection of our test file, ask our FileList
to copy the name."
gui model copyName.
string := Clipboard clipboardText string.
self assert: string = (dir fullNameFor: fname).
gui model copyJustFileName.
string := Clipboard clipboardText string.
self assert: string = fname.
"Done with the file."
dir deleteFileNamed: fname.
gui delete
There's a bunch of little "tricks" going on in here. First we got lucky because the FileList #openAsMorph class method answers a SystemWindow but doesn't actually open it. So we can use that window morph and ask it questions about it's submorphs.
Also, once we had all the hooks in place to test the new #copyJustFileName method we wrote we may as well test the existing one that answered the full path: #copyName. The #assert: methods will generate an error that SUnit will log if the condition we assert is not true.
Run the SUnit tests again after adding this method.
Go on to Second Enhancement to File List.
Back to the beginning of this example.