Unit Testing with gunit



Similar documents
SUnit Explained. 1. Testing and Tests. Stéphane Ducasse

Python Loops and String Manipulation

Approach of Unit testing with the help of JUnit

PHP Debugging. Draft: March 19, Christopher Vickery

Author: Sascha Wolski Sebastian Hennebrueder Tutorials for Struts, EJB, xdoclet and eclipse.

Relative and Absolute Change Percentages

Software Engineering Techniques

16.1 DataFlavor DataFlavor Methods. Variables

C++ INTERVIEW QUESTIONS

Designing with Exceptions. CSE219, Computer Science III Stony Brook University

CS 1133, LAB 2: FUNCTIONS AND TESTING

TypeScript for C# developers. Making JavaScript manageable

Introduction to Algorithms March 10, 2004 Massachusetts Institute of Technology Professors Erik Demaine and Shafi Goldwasser Quiz 1.

Lab 4.4 Secret Messages: Indexing, Arrays, and Iteration

Fail early, fail often, succeed sooner!

GDB Tutorial. A Walkthrough with Examples. CMSC Spring Last modified March 22, GDB Tutorial

How to test and debug an ASP.NET application

Introduction to Programming System Design. CSCI 455x (4 Units)

CS1102: Adding Error Checking to Macros

3. Mathematical Induction

Test Driven Development

Automated Offsite Backup with rdiff-backup

CS 111 Classes I 1. Software Organization View to this point:

Licensed for viewing only. Printing is prohibited. For hard copies, please purchase from

Glossary of Object Oriented Terms

Logistics. Software Testing. Logistics. Logistics. Plan for this week. Before we begin. Project. Final exam. Questions?

University of Hull Department of Computer Science. Wrestling with Python Week 01 Playing with Python

Structural Design Patterns Used in Data Structures Implementation

Code Kingdoms Learning a Language

Programming Languages

Software Engineering for LabVIEW Applications. Elijah Kerry LabVIEW Product Manager

Effective unit testing with JUnit

Base Conversion written by Cathy Saxton

Randy Hyde s Win32 Assembly Language Tutorials (Featuring HOWL) #4: Radio Buttons

CHAPTER 18 Programming Your App to Make Decisions: Conditional Blocks

JMulTi/JStatCom - A Data Analysis Toolkit for End-users and Developers

Tutorial: Getting Started

Basics of I/O Streams and File I/O

Introduction to Python

Chapter 2 Writing Simple Programs

Variable Base Interface

Evaluation of AgitarOne

KITES TECHNOLOGY COURSE MODULE (C, C++, DS)

This section provides a 'Quickstart' guide to using TestDriven.NET any version of Microsoft Visual Studio.NET

Outline. 1 Denitions. 2 Principles. 4 Implementation and Evaluation. 5 Debugging. 6 References

CSE 326: Data Structures. Java Generics & JUnit. Section notes, 4/10/08 slides by Hal Perkins

Calling the Function. Two Function Declarations Here is a function declared as pass by value. Why use Pass By Reference?

Unit-testing with JML

Boolean Expressions, Conditions, Loops, and Enumerations. Precedence Rules (from highest to lowest priority)

Intro to scientific programming (with Python) Pietro Berkes, Brandeis University

Java Interview Questions and Answers

Hypercosm. Studio.

Writing Thesis Defense Papers

Ajax Development with ASP.NET 2.0

Unit Testing. and. JUnit

Writing Self-testing Java Classes with SelfTest

Software Testing. Definition: Testing is a process of executing a program with data, with the sole intention of finding errors in the program.

Debugging Strategies

Introduction to Synoptic

AppleScript and Apple Remote Desktop

5544 = = = Now we have to find a divisor of 693. We can try 3, and 693 = 3 231,and we keep dividing by 3 to get: 1

Lecture 2 Mathcad Basics

6. Control Structures

Test Driven Development Part III: Continuous Integration Venkat Subramaniam

Pseudo code Tutorial and Exercises Teacher s Version

Programming Exercises

Python for Rookies. Example Examination Paper

Demonstrating a DATA Step with and without a RETAIN Statement

14:440:127 Introduction to Computers for Engineers. Notes for Lecture 06

Advanced Tornado TWENTYONE Advanced Tornado Accessing MySQL from Python LAB

The partnership has also led to a joint library catalogue between Suffolk and Cambridgeshire.

Keil Debugger Tutorial

PART-A Questions. 2. How does an enumerated statement differ from a typedef statement?

language 1 (source) compiler language 2 (target) Figure 1: Compiling a program

Unit Testing & JUnit

Regular Expressions and Automata using Haskell

Table of Contents. LESSON: The JUnit Test Tool...1. Subjects...2. Testing What JUnit Provides...4. JUnit Concepts...5

Chapter 8 Software Testing

RTOS Debugger for ecos

MATLAB Functions. function [Out_1,Out_2,,Out_N] = function_name(in_1,in_2,,in_m)

Using JUnit in SAP NetWeaver Developer Studio

Survey of Unit-Testing Frameworks. by John Szakmeister and Tim Woods

CS330 Design Patterns - Midterm 1 - Fall 2015

Object-Oriented Design Lecture 4 CSU 370 Fall 2007 (Pucella) Tuesday, Sep 18, 2007

UNIVERSITY OF CALIFORNIA Department of Electrical Engineering and Computer Sciences Computer Science Division. P. N. Hilfinger

10CS35: Data Structures Using C

Enjoying EPUB ebooks on Your Nook

Visual Logic Instructions and Assignments

Code Qualities and Coding Practices

Microsoft Dynamics GP. Project Accounting Billing Guide

Java course - IAG0040. Unit testing & Agile Software Development

Getting off the ground when creating an RVM test-bench

Inside the Java Virtual Machine

Paper Creating Variables: Traps and Pitfalls Olena Galligan, Clinops LLC, San Francisco, CA

Introduction to C Unit Testing (CUnit) Brian Nielsen Arne Skou

Transcription:

Unit Testing with gunit Revision 2052 Andrew P. Black July 7, 2015 Abstract Unit testing has become a required part of software development. Every method that might possibly go wrong should have one or more unit tests that describe its behaviour. These tests act as executable documentation: they describe what the method should do in readable terms, by giving examples. If the tests pass, we can be sure that the implementation conforms to this specification. Of course, specification by example cannot be complete, and testing cannot ensure correctness, but it helps enormously in finding bugs, and speeds up development. Test Driven Development (TDD) takes testing to the next level: with TDD, you write your tests before you write your code. This helps you choose good interfaces for your methods interfaces that make sense to the client, not to the implementor. You also know what to do next, and when you are done. The unit testing framework for Grace is called gunit. This document outlines how to use it. 1 An Example The best way to explain how to use gunit is probably by example: 1 import "gunit" as gu 2 import "setvector" as sv 3 4 class settest.formethod(m) { 5 inherits gu.testcasenamed(m) 6 7 var emptyset // :Set<Number> 8 var set23 // :Set<Number> 9 10 method setup { 11 super.setup 1

12 emptyset := sv.setvector.new 13 set23 := sv.setvector.new 14 set23.add(2) 15 set23.add(3) 16 } 17 18 method testempty { 19 assert (emptyset.size == 0) description ("emptyset is not empty!") 20 deny (emptyset.contains 2) description ("emptyset contains 2!") 21 } 22 23 method testnonempty { 24 assert (set23.size) shouldbe 2 25 assert (set23.contains 2) 26 assert (set23.contains 3) 27 } 28 29 method testduplication { 30 set23.add(2) 31 assert (set23.size == 2) description "duplication of 2 not detected by set" 32 } 33 } 34 35 gu.testsuite.fromtestmethodsin(settest).runandprintresults Line 1 imports the gunit package, so that we can use it to implement our tests. Line 2 imports the package under test, here an implementation of Sets called setvector. Starting on line 4 we define a class whose instances will be the individual tests. The actual tests are methods in this class; the names of these methods are chosen to describe what is being tested. We call a class that contains test methods a test class. After the definition of the class, the last line of the example runs the tests. It s a bit complicated, so lets look at it in parts. A testsuite (pronounced like sweet ) is a collection of tests. Like an individual test, you can run a testsuite, which runs all of the tests that it contains. There are several ways of making a testsuite; here we use the method fromtestmethodsin on the object testsuite. This method takes as its argument the test class; it builds a test suite containing one test for each of the test methods in the test class. What do we do with the test suite once we ve made it? Run all of the tests, and print the results! There are other things that we could do too; we will look at test suites later in a bit more detail. 2 Test Classes and Test Methods Why does gunit insist that our tests are methods in a class, rather than simply objects with a run method? The answer is that it s sometimes useful to run a test more than once (for example, when 2

hunting for a bug), and its impotent that each test should start with a clean slate, and not be contaminated by previous failed tests. So gunit needs to be able to generate a new instance of a test when required. This is the function of the test class, here the class called asettest. What makes a class a test class? 1. Its instances inherit from testcase.formethod(m). 2. Its constructor method is called formethod(m). 3. Its instances have methods corresponding to the tests that we want to run; these test methods have no parameters and must have a name starting with test. For example, starting on line 18 we see a test called testempty. 4. It s conventional to name the class after the class or object that it s testing, and to include the word Test as a suffix. In our example, the class is called asettest because it s testing the class set. 5. The test class can have method setup and teardown; if they exist, these methods will be run before and after each test method (whether or not the test passes). If you do override these methods, be sure to request super.setup or super.teardown before you do anything else. 6. The test class can have fields and other methods as necessary. For example, it is sometimes convenient to have helper methods for clarity, such as asset()isapproximatelyequalto(). Any defs and vars in the test class will be initialised afresh before each test method is run. What s in a test method? The only thing that has to be in a test is one or more assertions, which are self-requests of various assertion methods inherited from atestcase.formethod(m). 1 method assert (bb: Boolean) description (message) 2 // asserts that bb is true. If bb is not true, the test will fail with message 3 method deny (bb: Boolean) description (message) 4 // asserts that bb is false. If bb is not false, the test will fail with message 5 method assert (bb: Boolean) 6 method deny (bb: Boolean) 7 // short forms, with the default message "assertion failure" 8 method assert (s1:object) shouldbe (s2:object) 9 // like assert (s1 == s2), but with a more appropriate default message 10 method assert (block0) shouldraise (desiredexception) 11 // asserts that the desiredexception is raised during the execution of block0 12 method assert (block0) shouldntraise (undesiredexception) 13 // asserts that the undesiredexception is not raised during the execution of block0. 14 // The assertion holds if block0 raises some other exception, or if it completes 15 // execution without raising any exception. 16 method failbecause (message) 17 // equivalent to assert (false) description (message) 18 method assert(value:object) hastype (Desired:Type) 19 // asserts that value has all of the methods of type Desired. 20 method deny(value:object) hastype (Undesired:Type) 21 // asserts that value is missing one of the methods in the type Undesired 3

In addition to the assertions, a test can contain arbitrary executable code. However, because part of the function of a test is to serve as documentation, its a good idea to keep tests as simple as possible. What happens when a test runs? runs. In general, one of three things might happen when a test 1. The test passes, that is, all of the assertions that it makes are true. 2. The test fails, that is, one of the assertions is false. 3. The test errors 1, that is, a runtime error occurs that prevents the test from completing. For example, the test may request a method that does not exist in the receiver, or might index an array out of bounds. In all cases, gunit will record the outcome, and then go on to run the next test. This is important, because we generally want to be able to run a suite of tests, and see how many pass, rather than have testing stop on the first error or failure. For example, when we run the set test suite shown above, we get the output 3 run, 0 failed, 0 errors What happens when your tests don t pass? The rule for Test Driven Development (TDD) is to write the test that documents new functionality before you implement that functionality. Let s illustrate this by adding the remove operation to sets. First, we add another test to asettest: method testremove { set23.remove(2) deny (set23.contains 3) description "{set23} contains 3 after it was removed" } When we run the tests again, we get this output, which summarizes the test run: 4 run, 0 failed, 1 error testremove: no method remove in setvector. The summary output will contain a list of all of the tests that failed, and a list of all of the tests that errored, but not a lot of debugging information. To get more information on the tests that don t pass, we debug them. To do this, we add the following line of code to the test module: asettest.formethod("testremove").debugandprintresults 1 Yes, I m using to error as a verb. How daring! 4

This creates a test suite that contains a single method (the method testremove, named in the string argument to formethod), and debugs it. Here is the output: 4 run, 0 failed, 1 error testremove: no method remove in setvector. debugging method testremove... No such method on line 35: no method remove in setvector. called from setvector.remove (defined nowhere in object at setvector:146) at setvectortests:35 called from settest.testremove (defined at setvectortests:34 in object at setvectortests:42) at gunit:142 called from MirrorMethod.request (defined at unknown:0) at gunit:142 called from Block[gUnit:139]._apply (defined at gunit:130) at gunit:143 called from Block[gUnit:139].apply (defined at unknown:0) at gunit:143 called from Block[gUnit:136]._apply (defined at gunit:130) at StandardPrelude:147 called from Block[gUnit:136].apply (defined at unknown:0) at StandardPrelude:147... 1 run, 0 failed, 1 error testremove: no method remove in setvector. When we debug a test, we see the error message, but any code subsequent to the test that didn t pass is not run. In this example, we see that the problem is that the object under test doesn t actually have a method called remove. This is hardly a surprise, because we haven t implemented it yet! If we implement this method, and run the same tests again, this is what happens: 4 run, 1 failed, 0 errors Failures: testremove: <SetVector: <Vector: 2 3>> contains 3 after it was removed debugging method testremove... Error around line 63: Assertion Failure: <SetVector: <Vector: 2 3>> contains 3 after it was removed Called asettest.debugandprintresults (defined at gunit:149 in object at SetTests:44) on line 155 Called asettest.debug (defined at gunit:132 in object at SetTests:44) on line 151 Called Block«gUnit:134».apply (defined at unknown:0 in object at unknown:0) on line 140 Called Block«gUnit:134»._apply (defined at gunit:128 in object at unknown:0) on line 140 Called MirrorMethod.request (defined at unknown:0 in object at unknown:0) on line 139 Called asettest.testremove (defined at SetTests:36 in object at SetTests:44) on line 139 Called asettest.deny()description (defined at gunit:67 in object at SetTests:44) on line 287 Called asettest.assert()description (defined at gunit:60 in object at SetTests:44) on line 68 Called Exception.raise (defined at unknown:0 in object at unknown:0) on line 63 62: then { 63: failure.raise(str) 64: } minigrace: Program exited with error: SetTests Whoops! We made a mistake when we implemented the remove method; it looks like it doesn t actually remove the argument. 5

The one line summary really tells us all that we need to know. The debugging information isn t all that useful: it tell us that the assertion failed, and then gives a stack trace of the path through gunit, which isn t what we want. Perhaps one day Grace will have a debugger that will let us go back in time through the test that failed. Until then, if you make your tests small and simple, you will find that the test itself will give you most of the information that you need to fix the problem. If I go back and correct my implementation of remove, this is the output on the next run: 4 run, 0 failed, 1 error testremove: undefined value used as argument to []:= debugging method testremove... RuntimeError on line 237: undefined value used as argument to []:= called from Block[vector:236]._apply (defined at vector:152) at vector:236 called from Block[vector:236].apply (defined at unknown:0) at vector:236 called from NativePrelude.while()do (defined at unknown:0 in StandardPrelude module) at vector:236 called from vectorclass.removefromindex (defined at vector:228 in object at vector:343) at vector:127 called from vectorclass.removevalue (defined at vector:122 in object at vector:343) at setvector:77 called from setvector.remove (defined at setvector:76 in object at setvector:149) at setvectortests:35 called from settest.testremove (defined at setvectortests:34 in object at setvectortests:42) at gunit:142... 1 run, 0 failed, 1 error testremove: undefined value used as argument to []:= This output is telling us about an error at line 231 of module vector, which is imported by setvector. On this line, the code accesses an undefined array element in method removefromindex. If the vector module had been developed using TDD, the developer would have found this bug, rather than having it annoy the clients of vector. 3 Test Suites A lot of the power of automated testing comes form being able to run large numbers of tests unattended, and to have a human alerted only when there are errors or failures. To this end, it s useful to be able to collect tests together into collections called test suites. gunit s testsuite implements the Composite pattern, and lets the testing framework treat individual tests and compositions of tests uniformly. A testsuite contains zero or more tests, and zero or more testsuites. A testsuite implements the enumerable interface of collections; you can ask a test suite its size, add a new test or test suite to it, and create an iterator to sequence through it. It also implements the runnable interface of a test: you can run a test suite, runandprintresults, or debugandprintresults. As with any collection factory, testsuite responds to the with(), empty, and withall() requests, answering a new test suite. You can also make a test suite using testsuite.fromtestmethodsin(atestclass), which populates the suite with all of the test methods in atestclass. 6

4 Dependencies The implementation of gunit depends on collections and on mirrors. exceptions to print diagnostics about failing tests. Specifically: It also uses methods on 1. testsuites contain lists of tests. 2. testresults contain sets of failures and errors. 3. testsuite.fromtestmethodsin(atestclass) reflects on an instance of atestclass using mirror.reflect, and looks at the names of the available methods. 4. testcasenamed().run uses reflection to request execution of the test method. 5. debugging a test involves extracting the exceptionkind, message, linenumber and backtrace from a dynamically-generated Exception object, so that these things can be printed. 7