-
-
Notifications
You must be signed in to change notification settings - Fork 262
2 15 2012 (Wed) at Kabam minutes
- Ruby is dynamically typed: you don't have any guarantees on the class or structure of any object.
- Ruby is interpreted: calls and references are resolved at runtime (if i'm trying to send a method to a class, I won't know if that'll work or not til runtime). Learned about TDD in Java but didn't use it, because if I got my program to compile in Java, that was 90% of testing already, so you could get away without TDD in Java.
- Ruby is mutable: (almost) any class or library can be modified, even at runtime. "This is kind of a scary environment, so it's importatnt to be able to have tests so that your code is able to run"
- Tests codify and describe expected behavior. "How many of you have read someone else's code and had no idea what was happening?" "A: I look at my OWN code and don't know what's going on sometimes" lol.
- Test suites can be run repeatedly to detect code regressions ("if I write a piece of code here which introduces a bug somewhere else, you can detect that problem
- You can refactor code with tests with ease of mind. "I've been on projects where there's crazy code, but no one changes because you think if it aint broke, don't fix it. So you leave messy code in there"
You have the whole model view controller thing, so start from the bottom with your model.
-
Unit Tests: models and library classes: anything that's an isolated chunk of functionality
-
Functional Tests: what you use for testing controllers, focus on dispatching and stuff like that.
-
View Tests: HTML rendered templates: is what you're rendering what was expected An interesting story: rails 235 was released; the next day, a new version was released immediately… because there was a horrible bug detected in the view layer. Had they had their view tests written, they'd have caught it.
-
Helper Tests: helper logic (some peole roll these into the unit tests, but this is another suite you could have) The above are all specific parts of your code.
-
Integration Tests: Entire stack and external services. There are several ways you could test your entire stack. Test everything together, make sure
-
UI Tests:
- Selenium: browser-based. Great tool for testing the browser because it runs on the browser, so you look at what's being presented in the browser, you can click on things, examine tags in there; if you have ajax behavior, you can test to make sure it's working properly; there are also tests you could do for javascript specifically (jasmine is one example)
- Webrat
- Capybara
- Business Driven Development (BDD) Tests
- super test that describes the function of an entire system
- people praise BDD because you can write tests that look like they're written in english
- you can describe particular behavior and have it directly mapped to your code
- by writing a specification for how a program is to behave, then have actual tests backing it up to make sure it's true. great way of specifying how a program is actually supposed to run.
- behavior driven development? you are writing the same sorts of tests that your'e describing, but it's a different way of thinking of testing. TDD is what the code does/returns. BDD is same tests but you're thinking about it from the point of view of the consumer that's testing. top layer: thinking from user's point of view. a layer down: brwoser's point of view. what the consumer expects to happen. Q: is cucumber necessary? A: no. you could write BDD using the standard test framekworks. it applies to all levels of tests--it's a way of thinking about how you write the tests.
TDD is a continuous cycle Before I learned TDD, I would typically come up with a general idea of the structure and write the code. Then I'd figure oout if the code is doing what i wanted it to do. With TDD, it's the reverse. And it's an ongoing continuous cycle.
- Write a failing test: make the tests go "red"
- I want to change the behavior of some piece of code, so I write the test that explains what I want from that type of behavior. So you write the test, and since the code that would make it pass is not there, the test will fail.
-
Add enough code to get the test to pass: make the tests "green"
-
Refactor as appropriate
- restructure the code and dimplify things. "DRY" means "don't repeat yourself" -- if you find the same code in multipele places, you can extract that. or if you think, I can make the code a lot simpler, but is it still passing?
That's all that TDD is about!
If you have an existing program and there's a bug that you need to fix, the first thing i'll do is write a test that exploits that bug (because my tests didn't catch this edge case before). That's a way to make sure I fully understand what the defect is in the program. Then I'll fix it, then I'll know the bug is fixed.
Demo time!
We're gonna use rspec for this. I recommend rspec because it has a better syntax in general for describing tests, and there are a few advantages of rspec over testunit (testunit is the default). There are many many testing frameworks, but rspec and testunit are the most common.
Look in the project directory. There's a folder called spec where the tests live. In the spec, there's a structure similar to your app directory: controllers, helpers, models; this is where your test suites go. And this would be where your cucumber suite would go too.
1st thing you do when you're in rspec: tell it what your'e actually going to be working on. Here, i am describing a user. describe User do
Then you describe your test conditions. it "should have a first name"
Then you create a user and check.
This line is called an assertion: user.first_name.should == 'George' user.first_name.should is what we're testing, and 'George' is the expectation. In rspec you have some extra syntax that's added to Ruby here. "should" is a method that says, "hey I'm going to take user.first_name (the subject we're testing), and it should equal the thing on the other side of the == which is 'George' in this example)
I run the test and it passes.
Let's think about some of the behaviors that I might want. Let's say every user should enter an email address.
it "should require an email address" do user = User.create user.errors_on(:email).should == ["must be entered"] end
user = User.create ---We're creating a new user and instantiating in the database.
It failed because we got no errors.
Now, go to user and add a valitator
validates_presence_of :email
We got "can't be blank" as the error message, it's the default one. Okay, go back and change the test to say "can't be blank" and now the test passes =D