Chatbot 3: Long Term Memory using a file

In the previous post, we covered Golang Slices used for short term memory for the chatbot. As we noted – this only lasts as long as the program is running. Now, let’s see if we can keep information when the chatbot goes offline. That is – long term memory. There are a few different approaches for long term memory. Writing to a file or to a database are the most common. For this example, we’re going to use a file. For future development, most use a database. This allows the database to handle the i/o (input/output) and maintain performance. This is because a database is designed specifically for the use case of storing and retrieving data.

Step 1: Breaking it down

Conceptually, File I/O is actually much more straightforward then previous concepts. The program needs a destination file, a payload, and permission when writing to a file. It needs a source file, destination and permission when reading from a file. It will also need to know how to interpret or parse the data when reading. Thus, it also needs to store the payload in a consistent format. There are a large number of guides in any given programming language for writing to / reading from a file. I’m not going to recommend one currently – and will focus on the actual implementation.

Step 2: File I/O in golang

The first example we have, is creating a file. Here is how to create ‘test.txt’ and avoid a memory leak. A memory leak is when something is reference by the program, but the code flow of the program no longer has context for that reference.

// Get a read/write file reference by creating it if it doesn't exist, or appending to it if it does exist.
file, err := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) // This provides a reference to the file 'f', or an error if it cannot. 

// check for the error. 
if err != nil { // a 'nil' error means no error occurred. 
  fmt.Println(err) // print the error
  return // stop processing the context.
}

// ##IMPORTANT## Always close the file reference to prevent a memory leak!
defer func() { // defer is an instruction that runs when the 'context' closes.
	_ = file.Close() // close the reference!, the underscore is a way to ignore the value returned by Close()
}()

Reading from an open file reference

// Reading each line in a file into an array
var lines []string // declare the slice to hold the data
scanner := bufio.NewScanner(file) // use the buffer input output package to start a file scanner
for scanner.Scan() { // loop through the file scanner
  lines = append(lines, scanner.Text()) // add the scanned line to the lines slice.
}

Writing to an open file reference

text := "Coming to a file near you" // string to write to file
_, err = fmt.Fprintln(file, text) // write the data to the file
if err != nil { // error handling if file I/O fails.
   fmt.Println(err)
}

\\ or alternatively
linesWritten, err := f.WriteString(text)

Step 3: Date storage format

There are many ways data can be stored within a file. If you’ve messed with files in the past – you’ll know a bit about text .txt files. Maybe you’ve seen markdown .md files. There are many different types of files – and these all serve different purposes. markdown, text, and even .csv files are all Human Readable Formats. If you open any of these in a text editor, you’ll be able to make sense of them quickly. However – they aren’t exactly the best for storing data from a program. For this particular example, we are going to simply use a txt file to place human readable data into the file.

Step 4: Chatbot Enhanced Memory

  1. launch program
  2. check if history exists (create it if it doesn’t exist)
    1. load the history into the history slice
  3. send a friendly greeting and store it to history
  4. send a simple instruction and store it to history
  5. Have the user put something in and store it to history
  6. echo what the user put in and store it to history
  7. Check if the user typed ‘history’
    1. Store the request to history
    2. don’t repeat the history in the history file, just count the number of lines.
    3. Loop through the history slice
    4. print the key and the value
  8. repeat 4-7 until the user says ‘bye’

Step 5: Putting it all together

Now that we’ve covered the details, we can run the program. We’ll find that it isn’t much different from running the program during lesson 2. However – when we say ‘bye’ and start the program again, that’s where the changes are noticeable.

  1. run the program
  2. type hello
  3. type history
  4. type bye
  5. run the program again
  6. type history

Example output

Here is the final example used above. It is also available in github in chatbot, lesson 3

func main() {
	f, err := os.OpenFile("history.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)

	// check for the error.
	if err != nil { // a 'nil' error means no error occurred.
		fmt.Println(err) // print the error
		return           // stop processing the context.
	}

	defer func() {
		_ = f.Close()
	}()

	// string for storing input
	var input string

	// Slice for storing the history of the world.
	var history []string
	scanner := bufio.NewScanner(f) // use the buffer input output package to start a file scanner
	for scanner.Scan() {           // loop through the file scanner
		history = append(history, scanner.Text()) // add the scanned line to the lines slice.
	}

	fmt.Println("Hello, World!")
	fmt.Println("I am Echo! Please tell me something to say by typing it in, and pressing enter!")
	_, _ = f.WriteString("Hello, World! \n")
	_, _ = f.WriteString("I am Echo! Please tell me something to say by typing it in, and pressing enter! \n")
	// Stop when we see "bye"
	for input != "bye" {

		fmt.Print("You: ")
		scanner := bufio.NewScanner(os.Stdin)
		for scanner.Scan() {
			input = scanner.Text()
			break
		}
		_, _ = f.WriteString("You: " + input + "\n")
		// Add the new input to the history
		history = append(history, input)
		fmt.Print("Echo: " + input + "\n")
		_, _ = f.WriteString("Echo: " + input + "\n")
		// Check if the input was the string 'history'
		if input == "history" {
			// We won't add the history to the history, instead we can add how many lines of history exist.
			_, _ = f.WriteString("<Truncated> " + strconv.Itoa(len(history)) + " Lines of history repetition.")
			// iterate (loop) through the history
			for k, v := range history {
				// print out the Key and the Value
				fmt.Println(k, v)
			}
		}
	}
}

I don’t know about you – but that code is starting to look like my pantry after my kids try finding the peanut butter. In our next lesson – we will start to look at organizing code a little bit better.

Chatbot 2: Improve the chatbot with a memory

Many people can recount their first memory. We don’t really know how we do it, or why it is a first memory. It’s just there. We can also remember things more recently – or – remember information that is imaginary. For this lesson, we will be covering memory in terms of programming. Because a chatbot without a history, is like playing Memory and just choosing random cards each turn. Lesson examples can be found on Github

Step 1: Understanding the Lesson

Before we dig into code – let’s see if we can understand a little more about storing things in program memory. For most languages a developer can write, there are a few different ways to store data. In previous lessons, we’ve covered what a variable is – including a few types of variables. In this lesson, we are diving more deeply into the array type of variable.

One way to understand an array, is to think of series of mailboxes. They all have a number on them so the delivery person can put the mail into the correct slot. When a large neighborhood is built, there are only so many houses – and – there are only so many mailboxes. Depending on how this neighborhood is setup, any new houses will either be an easy addition (or) for a grouped mailbox setup – a new group would eventually need to be created. Arrays are like the grouped mailbox setup.

But what is an array, really? An array is a declared list of keys and values. From the mailbox example, the mailbox number is the key, and the things inside the mailbox are the value. At a more physical layer – arrays are blocks of ram reserved for storing sets of specific types of variables. In different programming languages, there are different mechanics for using arrays. In c++, one needs to program what happens if you try to add more keys to an array then the original requested amount. In golang – the language itself handles this for you using slices – so you don’t have to program the tedious memory management. Most modern languages handle the array management operations in some similar fashion.

Step 2: Using an Array in Golang

Now that we’ve briefly covered the definition of an array, let’s look at a few array operations. The Golang Tour provides an example of arrays here: A Tour of Go (golang.org).

Example from the golang tour with notes:

func main() {
  // creating a new array
  var a [2]string

  // adding to an array using a key
  a[0] = "Hello"
  a[1] = "World"

  // looking up something in an array
  fmt.Println(a[0], a[1]) // Prints Hello World

  // Creating an array with values
  primes := [6]int{2, 3, 5, 7, 11, 13}
  fmt.Println(primes) // Print [2 3 5 7 11 13]

  // removing the item with key 3 from the array
  var slice []int                                // create a 
  slice - notice how it doesn't have a length specified?
  slice = append(slice, 1, 2, 3, 4, 5, 6) // add values 
  to the array
  removalKey := 3                                       // set a key for removal

  // remove an item by adding the first portion (before the key, and the second half after the key together) 
  slice = append(slice[:removalKey], 
  slice[removalKey+1:]...)
  fmt.Println(slice) // Print [1 2 3 5 6]        // notice how the '4' is gone?

  // iterate (loop) through the slice and print the key and the value
  for key, value := range slice {
    fmt.Println(k, v)
  } 
}

Step 3: Chatbot wants a slice

With only a few lines of change, we are going to have chatbot keep a short term memory of the conversation. Here is a breakdown of the logic we’re going to use for chatbot:

  1. launch program
  2. send a friendly greeting
  3. send a simple instruction
  4. Have the user put something in
  5. echo what the user put in
  6. Check if the user typed ‘history’
    1. Loop through the history slice
    2. print the key and the value
  7. repeat 4-6 until the user says ‘bye’

To start, we will initialize a new slice.

// Slice for storing the history of the world.
var history []string

Next, whenever we get new input, we are going to store it in the history

// Add the new input to the history
history = append(history, input)

Finally, chatbot is going to check for a ‘prompt’ that it will use to print the history.

// Check if the input was the string 'history'
if input == "history" {
	// iterate (loop) through the history
	for k, v := range history {
		// print out the Key and the Value
		fmt.Println(k, v)
	}
}

Step 4: Putting it all together

The completed above code can be found in this github repository. There is a main.go file (the code) and a README.md file (the directions).

To get the following output, run the program, and enter the following lines

  1. type test
  2. type cool
  3. type history
Program Output

Well, we’ve now got a chatbot that is still a copycat. It can now recount all the things it has copied. This ‘history’ only lasts while the program is running. In future lessons, we can explore more permanent memory, command logic, and making the code organized.

Introduction to Programming 000

Quite some time ago, I went to college for Computer Science degree. I knew that I wanted to provide for a family, that I didn’t want to perform a job with a lot of manual labor, and that doctors and lawyers would fit the bill. But I also knew that I was good with computers, I’d hacked a few games after all. I’d even written a few programs. In fact, the first one I ever “wrote” I can still provide from memory.

10 print Hello World
20 goto 10

But let me get into what this post is really about. An introduction to the world of programming. Currently, I’m teaching my son 6th grade math. It’s introducing him to Algebra. Algebra is cool. It makes math useful, repeatable even. It’s kind of what programming does. The first things you learn in school are definitions, terminology, the words, and what they mean. That way you can communicate with everyone else that learns about the same subject. Without further ado, let me introduce you to some common concepts of programming.

Operators

Just like 6th grade math, you need operators to do anything. Consider 2+2. It equals 4. That’s what your 1st grade teacher taught you. The ‘+‘ sign is the most important piece here. It is the operator. It tells you what to do with the things around it. There are lots of operators out there. Most of the ones you learned in Math apply. And there are few other special operators – but we need to cover other items before we introduce them.

Variables

Leaning back on algebra, variables are representational. They can vary in what they represent. For example, if we say x is a variable, and we have an equation x+1=2, we know that the x variable is 1. Or, if we say, the total is x+1, we can set x to a certain number, say 2, and know that the total is 3. Variables are a huge part of programming. Without them, we wouldn’t be able to record the result of operations. Now, speaking of operations, one of the special operators, is the assignment operator.

In many languages, the assignment operator is notated by the = sign. In other languages, it is a combined => symbol. An example of an assignment operation, would be as follow.

x = 10+2

if we were to print the value of x, it would say 12.

Types

Unlike Algebra, programming variables can be different types. This is because, different things need to be tracked in a computer. In algebra, you’re just tracking numbers. Examples of different types are:

  • integer
  • boolean
  • string
  • object
  • float

An analogy would be something like measurements. If you try to measure flour for a cookie recipe, and you use 2 tablespoons instead of cups – or – you used 2 grams instead of 2 cups – you end up with goop (I know from experience, goop doesn’t taste good). And for this reason, programming languages use types. Keeping track of a of number is an integer. Keeping track of someone’s name, well, that’s a string, and keeping track of your next door neighbor and their email address (is creepy) but that group of types, that’s an object.

Differences between Programming languages

I’m not going to cover a lot on different programming languages at this time. Just know they are out there. A lot of the core principles are the same, or similar, even if the terminology is different. This is because the C programming language became an effective way for programmers to convey ideas. If you’re into language history, think of it as the ‘Latin’ of the programming world in that a lot of other languages stem from it.

Next step

At this point, I’m going to recommend downloading Golang and finding a text editor or IDE. Because eventually, we’ll get started on using some of these concepts in an actual application. Once you’ve gotten an IDE – head on over to Program 1: Build a simple text bot!

5 Interview tips for the Developer Culture Fit

I’ve seen a lot of articles about missing out on jobs because someone wasn’t a culture fit. Older developers think it is because of age discrimination. Younger developers think they don’t have enough experience. Minorities blame it on being a minority, and the same for special interest groups, and don’t forget Gender discrimination. Naturally, that’s what’s at fault. Well, except for a few scenarios… I call hogwash! Poppycock. Etc. You didn’t get the job, because you weren’t a good fit. But there is hope. Here are 5 ways to understanding culture fit and land a job where you’ll fit right in (pun intended).

Soft Skills

Depending on where you are in your career, you may or may not have heard of soft skills. Soft skills are a collection of empathy related abilities tied to Emotional Intelligence.  I cannot emphasize enough how important soft skills are to get ahead. I’ve seen incredibly talented individuals get stuck in their career, or even fail completely at a company because they couldn’t relate to the people around them, or their direct supervisor.

Take myself, for example, I had a huge problem with being passive aggressive. I didn’t know what it was until someone sarcastically called me out on it. I had to go lookup what it meant, and then I apologized to the individual. As I became more aware of the problem in myself, I was much more easily able to recognize it in others. It spurred a desire to understand my coworkers better – because I had come to realize the importance of teamwork, and how to maximize the teamwork.  Now – I’m by no means perfect – and I’m still learning more about this all the time.

If you are not familiar with soft skills, take some time to read through some quick posts online – or find a self help book. I can guarantee it will improve your ability to tell if you’re a good culture fit before your interviewer! Which in turn will enable you to present yourself as a good culture fit. Or decide you didn’t want that job anyhow.

Quit Complaining!

Nobody likes a quitter complainer! Ever had that coworker that just couldn’t catch a break, or that linked in contact that could never land that job they were after, or the friend that just didn’t like their situation? Well, even if you haven’t, you’ve probably heard complaining, or participated in it yourself. Complaining is annoying! Complaining, even on a public form, can work against you in the hiring process. Over 70% of people I’ve interviewed I needed to reject, because they were used to complaining about a bad manager, team, or coworker – they couldn’t focus enough to identify what they could do differently. 

If you’re guilty of too many complaints – try to take some time and focus on yourself as the problem in the situations.  Maybe, there’s more to the situation then you realized.  If anything, reducing the amount you complain everyday will improve your interview skills. 

Introspection

The concept of introspection has been around for a long time. See Know Thyself . Just because the concept is old, doesn’t mean that it is easy. And in the concept of a developer interview – it means something more entirely. I’ll use a couple of examples here to make this a little more obvious, if not exaggerated.

Let’s start with Gary. Gary is fresh out of college, and has been applying at startups and large companies but can’t seem to land the job. He’s never had an internship, but was one of the top achievers in his university. He’s extremely confident he can solve any problem or fill any roll given the opportunity. Gary flies through interviews, he’s got no problem with the technical challenges – and the questions he’s asked don’t seem to be a problem. He can’t figure out why they told him no. What do you think might be his problem?

Larry is another interviewee.  He’s much more senior then Gary, he’s had a good number of positions as different companies over the years, but now, he feels older and has grown frustrated as jobs that were once easy for him to land now turn him down on culture fit of all things. A typical interview goes pretty much how he remembers them all going. Except now, he’s able to fill in the blanks of how exactly he’d solve that problem he saw a few years back. Later he finds out he wasn’t a good culture fit. Why would Larry be having a problem getting a role he’s clearly qualified for?

Going back to Gary, A problem I’ve seen in myself, and about 10% of candidates I’ve interviewed (disclaimer: I don’t interview junior roles frequently). Was that I had a brilliant candidate that couldn’t figure out where they’d made a mistake. Asking them to describe projects, or projects with team members – they had no fault in anything they didn’t.  Gary didn’t understand what he was doing wrong. He hadn’t learned to be introspective.

Larry, on the other hand, had a significant amount of introspection. HE knew when he’d made mistakes in the past, and could draw on his wealth of knowledge to solve problems, especially using them for questions in interviews.  What Larry failed to recognize in himself, is that he was getting older – yet he was interviewing like he was fresh out of college, or worse – had a complaint mindset. A younger interviewer is often naïve – willing to work excessive hours – more likely to argue from passion then experience – and attuned to learn new things.  Whereas someone with a few years experience is much more likely to argue from a position of experience, and less attuned to learn new things, when after all, the previous way of doing things worked just fine. The best tip for Larry, would be to understand himself as an experienced individual, own that, and then be passionate about new things. Show an online course, a Github repo, or something else in that manner.

There’s a lot to cover on introspection – and I’m no expert – I probably even offended someone with the two examples and my conclusions. Hopefully I didn’t.

Be Politely Passionate

90% of interviewees I’ve had – I’ve known within 2 minutes if the person was passionate. If they weren’t – that meant I wasn’t hiring them. Naturally, the interview would continue, and I would try to prod passion from the interviewee, coax it out even.  In that remaining 10%, I’d say only 1% of them actually proved me wrong.

On top of being passionate, be polite. If you’re passionate to the point where you are interrupting the interviewer, maybe you need to check yourself, that’s right – use some introspection – and some soft skills, and be passionate – but not at the expense of the interviewer.

Ask Questions

One of the worst interviews I gave, was where the interviewee had no questions for me. They didn’t even try to think of any. To me, it came of as if they didn’t care. Needless to say, they would not have been a good culture fit. It’s hard to be a passionate candidate if you don’t have any questions – stall for time if you need to – say something like, “Well, you covered a lot of information about the job – could you tell me more about the commute?”.  Just asking a simple question like that shows how much you’ve been paying attention – and increases your engagement level. 

Summary

Remember – if you’re at the point where your actually talking to someone – be it face to face or over the wire – you are actually talking to someone. That person is getting a first impression of you. That impressions will make or break your interview process.

If you liked this article, please let me know by re-sharing or commenting! Any questions/comments/not-spam interactions are appreciated!

Guide: Build a RESTful API microservice with PHP

Too many times I’ve seen people lost in the weeds arguing about endpoints, design patterns, reusability, etc. It turns out – with a decent microservice – the architecture lends itself to simplicity. For this guide – I’ll take you step by step on how to standup a minimal Restful API Microservice using PHP. I’ll be using the Waryway Microserver Engine.

Setup the Project

This guide won’t cover adding your project to GIT.

  • Create your project directory
  • Add a composer.json file
  • Add the dependencies
    • waryway/micro-service-engine
    • waryway/php-traits-library
  • Add the autoloader to the composer.json
  • Add the src directory
"autoload": {
  "psr-4": {
    "OrgName\\ImagerServerRest\\": "src/"
  }
}

At this point, your project is going to look something like this:

ImageServerRest
  src
    ...
  vendor
    ...
  composer.json
  composer.lock

Create an initial endpoint

For the initial endpoing, I’m using the example from the readme.md.

  1. Copy the Router.php from example
  2. Replace Waryway\example with OrgName\ImageServerRest
  3. Change the setRoute to only ‘/’ and respond with ‘Hello World
    • $this->setRoute(['GET', 'POST', 'PUT', 'DELETE'], '/', [__CLASS__,'helloWorld']);
    • This was mostly just to provide a ‘test’ endpoint.
  4. Run composer update
  5. Run vendor\bin\server OrgName\ImageServerRest 0.0.0.0:99
  6. In a browser, navigate to localhost:99
Tada! You got a ‘Hello World’ response.

Add a real endpoint

The real cool thing. Adding an endpoint. If you ever want to add an endpoint, make certain to use a proper router. Frameworks have them. I like nikic’s fastrouter in PHP, or Mux is also fine in Golang.

Not getting too fancy – I just add a new route (/images), and a route handler (getImageStatistics) – and set them up to return according to what I find out. The example itself gets rather long – so I’ll simply link to it in github. Once I’ve gotten all the changes in place, I can run vendor\bin\server.bat orgname\ImageServerRest 0.0.0.0:99 and try out the URL. Eureka! http://127.0.0.1:99/images now returns a list of information about the available images.

Make it Conventional

It’s one thing to build an endpoint. It’s another to publish an endpoint. What I’m saying is this: If all lightbulbs had different size sockets, it would be much harder to go to the store to buy lightbulbs. In the same way – if all RESTful api’s have a similar standard for definition to clients, they become much simpler to use overall. The OpenAPI Specification provides just that, and Zircote/Swagger-PHP is a great tool to implement it.

Start by updating the composer.json with the swagger-php composer setting as a dev require. Then run composer update. Who knows, maybe Waryway will add it to the microservice engine at some point.

"require-dev": {
  "zircote/swagger-php": "dev-master"
},

Using the documentation from Zircote is pretty straightforward and the annotations are pretty obvious. That said – I ended up using this command to generate a nice little swagger.json file:

vendor\bin\openapi.bat --pattern "*.php" --output "./src/swagger.json" --format json ./src

The easiest way to see if the generated file works correctly, is to use the swagger editor, and paste the file contents into it. The editor will automatically convert any json to yaml – and will nicely update the generate API documentation. It’s really neat!

example of editor.swagger.io

Another cool trick with swagger, is the simplicity of adding the swagger view into the codebase itself, and exposing the entire utility via an endpoint. For this guide, I’m simply adding the swagger doc to view it at /swagger.json

Wrap the Package

If you thought containers weren’t going to make an appearance while talking about microservices, you should have paid for insurance. If you are considering the cardboard boxes, plastic totes, or even shipping containers – you might want to brush up on Docker!

The first piece of wrapping a package, is to take a step back from the /src directory and create a /build directory. The goal here, is that the build directory never actual lives where the program is deployed. Once you’ve added the build directory – let’s add a Dockerfile. Now, it doesn’t have to be a fancy Dockerfile – at least not for getting started. I am going to use the 7.3.10-cli-alpine from Docker Hub as a base image.

Note: The only files you really want to work with are your composer.json, composer.lock, and src directory.

There are many different flavors of docker files. For this one, I’m not really optimizing for size. I don’t clear the composer cache, for example. Notice however, that the swagger.json generator is included in the build. I also added the build command I used as a comment.

FROM php:7.3.10-cli-alpine

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer
COPY ./composer.json ./composer.json
COPY ./composer.lock ./composer.lock
RUN composer install
COPY ./src ./src
RUN composer dump-autoload
RUN mkdir -p /assets/images
RUN ./vendor/bin/openapi --pattern "*.php" --output "./src/swagger.json" --format json ./src
EXPOSE 80

CMD php /vendor/bin/server orgname\\ImageServerRest 0.0.0.0:80

# docker build -t orgname/image-server:v1 -f build\Dockerfile .

Once you’ve gotten the Dockerfile built – simply kick it off, and remember to mount the ‘images’, or else there will be none.

docker run -it -p 80:80 -v C:/development/ImageServerRest/assets/images:/assets/images --name test orgname/image-server:v1

What now?

Well, now we’ve built a microservice, put it in a container, defined it with swagger, and even got it up and running locally – the next logical step is to ship the container! You’ll need to harden it (because containers are best not run as root). You’ll need to get it to a registry (dockerhub is my favorite currently). CICD for the entire project would be a huge boon… And, of course! you’ll probably want to write a different endpoint then my contrived ‘image stats’ endpoint.

I hope you’ve enjoyed this guide. If you have any problems following it – feel free to comment and ask about it! If there is a bug in the code – a new issue on GitHub would be greatly appreciated. If you’d like more guides like this: like, subscribe, and comment! Finally, thanks for reading!

Guide: Implementing a PhpUnit Development Environment in PhpStorm

Preperation

For this example, I will be starting with the following:

  • PhpTraits repository https://github.com/Waryway/PhpTraits
  • PhpStorm Project
    • Make certain you have a php interpreter referenced in your storm project
    • Also make certain you have Composer referenced by PHPStorm
    • A desire to test.allTheThings()
Adding the PHP interpreter reference.

Installing Libraries

First, use composer do some work. We need a copy of phpunit. Get it with this:

"require-dev":{
    "phpunit/phpunit":"6.4.3"
}

As this is a ‘library’ style app – use an exact version. The lock file gets ignored in favor of precise dependencies in the composer.json.

Now run composer update to pull in the requested phpunit package.

Setup phpunit in the general settings, under phpunit

Adding a new test

Or rather, a test skeleton

  1. Open the file to test in the Project View
  2. Place you cursor on a method to test within the file
  3. Go To Navigate -> Test
  4. Create New test
  5. I tend to set mine up with a test prefix to keep tests more obviously separate from code.
  6. Click Ok then Open up the new file
  7. Add a line to pull in the composer autoloader
  8. Clean up the Namespace reference to the TestCase if you are using PSR-4
  9. Disclaimer: Strongly avoid namespacing unit tests. They should not be built out like a code base. They are testing units.
  10. Add an empty testHydrate method.
  11. Add a super obvious assertion.
  12. Add a phpunit.xml fileI put it in the test directory.
  13. Run the test (At this point, I needed to restart phpstorm to detect that I actually had a ‘test’ to run.
  14. Build the tests out further.

Conclusion

Ending of the beginning

I’ve gone ahead and built out the ‘hydrate’ test against a trait. Note the use of the trait object that phpunit provides. Pretty slick, right?  I’ll continue to build out this repository – core libraries need the most directed test coverage.

Even adding the ‘test’ cases around the hydrate method – I have found use cases I didn’t consider while writing the code – and have adjusted the code to reflect the behavior I expect.