Thinking about Test driving the 3 n plus 1 problem solution

10 Aug 2019

Problem source: Programming Challenged: The Programming Contest Training Manual

Start with an integer n. If n is even, divide by 2. If n is odd, multiply by 3 and add 1. Repeat this process with the new value of n, terminating when the value is 1. For an input n, the cycle length of n is the number of numbers generated up to and including the 1. The cycle length of 22 is 16. Given any two numbers i and j, you are to determine the maximum cycle length over all numbers between i and j, including both endpoints.

I'm building the solution to the above problem using Pharo, which is a pure object-oriented language based on Smalltalk. I am not confident with object-oriented design or test-driving solutions, so I'm trying to correct that by building out as many solutions as I can.

First, I solve the problem myself. Then I ask for feedback from developers who know object-oriented design and test-driving better than I do. This entry is the solution I built first by myself.

The first thing I did was read through the problem to make sure I understood it properly.

The second thing I did was set up an empty Pharo project with version control and a test environment.

Setting up Test class

I started my program by creating a class called Test3nPlusOne

I wrote a class comment which explains what the class represents, what it's responsibilites are and what other classes it collaborates with. This is based on the CRC approach developed by Kent Beck and Ward Cunningham (who argue that index cards are better than a digital representation for this). Here is what the comment says:


Class

I represent the test suite for the 3nPlusOne class.

Responsibility

I make sure that:

Collaborators

I work with the 3nPlusOne class API


The code for my test class initilization was:


TestCase subclass: #TestthreeNPlusOne
	instanceVariableNames: ''
	classVariableNames: ''
	package: '3-n-plus-one'

First failing test: if even divide by two

The first message (method) I gave my test class was initialized as follows:


testIfEvenDivideByTwo
  | threeNPlusOne |
  threeNPlusOne := ThreeNPlusOne new.
  self assert: (threeNPlusOne ifEvenDivideByTwo: 2) equals: 1.

I decided to test that numbers can be correctly divided by two as my first test. My assumption is that this is a behaviour that my program needs to be able to perform. I'm not sure if I'm able to identify the 'right' kind of behaviours to test yet. So I'm just doing it this way and will rely on feedback to steer me in a better direction if needed.

It's pretty scary being this open with my development process when I know I still have a lot to learn so may not be doing things in the best way. But later down the line, this documentation will serve as a record of improvement and capture the changes in thought processes as I learn more, which is going to be really interesting to look back on.

When I saved and ran this test, the test successfully failed (I love that phrasing haha).

The quality assistant provided a bunch of messages (called critic text) to inform me of the reasons why my test failed. It is a good idea to get in the habit of reading all of these messages and understanding how to fix them. So I'm going to write them down along with the explanation of what they mean, and how I fixed them one by one.

Fixing my failing test step-by-step

To make my test pass, I created a class called 'ThreeNPlusOne', whose CRC comment was written as follows:


Class

I represent the 3-n-plus-one-problem, otherwise known as the 'Collatz Conjecture'.

Responsibility

I have a number range. For that number range I count how many cycles it takes for each individual number to reach 1 using the collatz conjecture math approach. Then I return the longest of those cycles.

Collaborators

I'm not sure who I collaborate with yet.


Then I added the if even divide by two message to the class which returns the number one, as follows:


ifEvenDivideByTwo: evenNumber
  ^ 1.

We now have a passing test, which I committed to version control.

After writing the first few tests, I realised that I need to spend some time working through some Pharo solution examples because I have no idea how to use the language properly, which was preventing me from moving forward. So I'm going to work through Learning Object-Oriented Programming, Design with TDD Pharo which will get me to a better place.