21
1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang Da Contributors: Xie Kai, Zeng Yong Edited by: Henry Chia Last modified: 2015 Feb 4 You can help us improve this document by reporting typos, or areas that are erroneous or unclear. Kindly email us at [email protected]. This is the second part of the document that describes exception handling, unit testing and product release. Tip boxes contain information that are not absolutely necessary, but may be useful at times. Highlight boxes contain important information. Pay extra attention to them. Extra info boxes contain additional information that can be safely skipped. Refer to them if you are interested to learn more.

An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

  • Upload
    others

  • View
    0

  • Download
    0

Embed Size (px)

Citation preview

Page 1: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

1

An Introduction to Visual Studio 2012

(Visual C++) – Part II

Author: Huang Da

Contributors: Xie Kai, Zeng Yong

Edited by: Henry Chia

Last modified: 2015 Feb 4

You can help us improve this document by reporting typos, or areas that are

erroneous or unclear. Kindly email us at [email protected].

This is the second part of the document that describes exception handling, unit

testing and product release.

Tip boxes contain information that are not absolutely necessary, but may be

useful at times.

Highlight boxes contain important information. Pay extra attention to them.

Extra info boxes contain additional information that can be safely skipped.

Refer to them if you are interested to learn more.

Page 2: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

2

CONTENTS

CHAPTER 6 – UNIT TESTING USING VS ...................................................................................... 3

DOCKING THE TEST EXPLORER WINDOW .................................................................................................... 3

SETTING UP A UNIT TEST ................................................................................................................................. 3

UNIT TEST DESIGN AND IMPLEMENTATION .................................................................................................. 8

CHAPTER 7 – EXCEPTION HANDLING ......................................................................................12

THROWING EXCEPTIONS .............................................................................................................................. 12

CATCHING EXCEPTIONS ................................................................................................................................ 13

RE-THROWING EXCEPTIONS ........................................................................................................................ 14

SUBJECTING EXCEPTION HANDLING TO UNIT TESTING ............................................................................ 15

EXCEPTION HANDLING IN CLI/C++ .......................................................................................................... 17

CHAPTER 8 – PRODUCT RELEASE .............................................................................................19

FROM DEBUG TO RELEASE ............................................................................................................................ 19

REFERENCES .....................................................................................................................................21

Page 3: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

3

Chapter 6 – Unit testing using VS

This chapter provides guidance on setting up unit tests as well as suggestions on

test design.

Docking the Test Explorer window

Before setting up any unit test, dock the Test Explorer window to the side bar.

The Test Explorer will be used to manage the test classes and methods.

From the menu, click TEST -> Windows and select Test Explorer.

The Test Explorer window appears docked at the right of the application window.

Setting up a unit test

A unit test is a test that focuses on a single function (or unit). A unit test can be

performed on a target function by adding the test unit to the solution. Right click on

the solution in Solution Explorer and select Add -> New Project. In the Add New

Project dialog navigate to Visual C++ Tab and select Test.

Page 4: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

4

In the Test page, select Native Unit Test Project. Name the project LibraryTest

and click OK. A Test project is created within the Solution Explorer.

Page 5: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

5

Since LibraryTest tests the Library, proceed to add reference and dependencies

to Library. Right click on LibraryTest and select Properties. Add a new reference

by clicking Add New Reference and select the Library project. In addition, the

additional include path has to be set. Under Configuration Properties – C/C++ –

General, click the dropdown button of Additional Include Directories and select

<Edit..>. Click the “new folder” icon at the top and add a new include directory by

typing “..\Library” as the path, or navigate to the correct directory by clicking on

“…”. As the process of adding references and dependencies is similar to the GUI

project, refer to Chapter 4 in part I of the document for more details.

Next, include the Library’s header PrimeGenerator.h so that the unit tests can

proceed to test the functions. Under the Header Files of the LibraryTest project,

double click stdafx.h to edit, and append the header of the library to the file.

#pragma once

#include "targetver.h"

// Headers for CppUnitTest

#include "CppUnitTest.h"

// TODO: reference additional headers your program requires here

#include "PrimeGenerator.h"

Under Source Files, rename unittest1.cpp as PrimeGeneratorTest.cpp and

double click to edit the source. Notice that the code template has been created.

#include "stdafx.h"

#include "CppUnitTest.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace LibraryTest

{

TEST_CLASS(UnitTest1)

{

public:

TEST_METHOD(TestMethod1)

{

// TODO: Your test code here

}

};

}

Page 6: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

6

Each unit test is to be defined within TEST_METHOD. This macro generates an

instance function that returns void during compile time. It also generates a static

function that returns the feedback message about the test method. This message

is available in Test Explorer (more on this later). Multiple test methods can be

created with each performing a specific unit test.

All the test methods are grouped up into the class TEST_CLASS. This macro

generates a test class instance during compile time and run all the test methods

during run time. Multiple test class can be created within the same namespace,

and these test classes will be instantiated and run sequentially during run time.

Notice that the code in the generated template is not consistent with the coding

standard of CS2103. Hence refactoring is required:

Rename the UnitTest1 parameter that is passed to the TEST_CLASS

macro so as to reflect the purpose the test class, say PrimeGeneratorTest.

Rename the TestMethod1 parameter passed to macro TEST_METHOD so

as to reflect the purpose of the test method. To test the constructor of

PrimeGenerator class, rename the parameter as ConstructorTest.

TEST_CLASS(PrimeGeneratorTest) {

public:

TEST_METHOD(ConstructorTest) {

// TODO: Your test code here

}

};

The following assertions are frequently used when writing test code.

Assert::AreEquals(expect, actual) verifies if two objects are equal;

Assert::AreSame(expect, actual) verifies if two references are equal;

Assert::IsTrue(condition) verifies if a condition is true;

Assert::Fail(message) assert a test failure with specific error message (in

wide character form).

Entering Assert:: in Visual Studio provides a view of its member functions, as well

as the documentation of each function. Select an appropriate assertion that fits the

test purpose.

Page 7: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

7

Include the following assertion.

TEST_CLASS(PrimeGeneratorTest) {

public:

TEST_METHOD(ConstructorTest) {

Assert::AreEqual(1, 1);

}

};

Build the solution. Notice the test method appears within Test Explorer and placed

under Not Run Tests.

Click on the Run All button on the Test Explorer to run all available tests (the

Run… button allows a specific running mode to be selected). The result of this run

appears as a passed test, since clearly 1 and 1 are equal.

Page 8: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

8

Now, include another assertion with TEST_METHOD:

TEST_METHOD(ConstructorTest) {

Assert::AreEqual(1, 1);

Assert::AreEqual(2, 1);

}

Build and Run All tests. Notice the failed test case in Test Explorer. Clicking on

the failed test case brings up the details of the failure in the summary area.

The test result for a test method is a short-circuited AND operation over all

assertions within the method. As such, the result reflects the very first assertion

failure (with the remaining assertions ignored) or a complete pass over all

assertions.

Unit test design and implementation

Suppose the constructor of PrimeGenerator constructs a list of primes containing

all primes less than 100. To verify the functionality of the constructor, we need to

compare the private vector primeList with a correct list of prime numbers. In order

to access primeList from LibraryTest, include a public getter function in

PrimeGenerator.h.

Page 9: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

9

std::vector<int> PrimeGenerator::GetPrimeList() {

return primeList;

}

To implement the test, simply hard-code a vector of 25 primes ranging from 2 to 97,

then use a for loop to compare every value in the vector generated by constructor

and the hard-coded vector.

TEST_METHOD(ConstructorTest) {

PrimeGenerator prime;

int totalPrime = 25;

std::vector<int> actualList = prime.GetPrimeList();

int hardCodedPrime[] = {2, 3, 5, 7, 11,

13, 17, 19, 23, 29,

31, 37, 41, 43, 47,

53, 59, 61, 67, 71,

73, 79, 83, 89, 97};

Assert::AreEqual(totalPrime, (int)actualList.size());

for (int i = 0; i < totalPrime; i ++) {

Assert::AreEqual(hardCodedPrime[i], actualList[i]);

}

}

Build and Run All tests. The result will depend on the correctness of the

implementation.

Now, add another unit test to test the GetPrimeLessThan() function. Suppose that

GetPrimeLessThan() simply queries the prime list generated by the constructor

To isolate the behavior of the function, inject a hard-coded prime list into the class

via a public setter function.

void PrimeGenerator::SetPrimeList(std::vector<int> pList) {

primeList = pList;

}

With the above included in PrimeGenerator.h, test using the following

TEST_METHOD.

Page 10: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

10

TEST_METHOD(GetPrimeLessThan_BasicTest) {

PrimeGenerator prime;

int totalPrime = 25;

int totalPrimeLessThan50 = 15;

int hardCodedPrime[] = {2, 3, 5, 7, 11,

13, 17, 19, 23, 29,

31, 37, 41, 43, 47,

53, 59, 61, 67, 71,

73, 79, 83, 89, 97};

prime.SetPrimeList(std::vector<int>(hardCodedPrime,

hardCodedPrime + totalPrime));

std::vector<int> actualResult = prime.GetPrimeLessThan(50);

Assert::AreEqual(totalPrimeLessThan50, (int)actualResult.size());

for (int i = 0; i < totalPrimeLessThan50; i++) {

Assert::AreEqual(hardCodedPrime[i], actualResult[i]);

}

}

Build and Run All tests. Once again, the result depends on the correctness of the

method implementation.

To restrict the use of the public getter and setter methods for unit testing only,

logical preprocessor directives can be used to conditionally define these methods.

In PrimeGenerator.h, wrap the getter and setter methods around #ifdef and

#endif preprocessor directives.

#ifdef TESTMODE

std::vector<int> PrimeGenerator::GetPrimeList() {

return primeList;

}

void PrimeGenerator::SetPrimeList(std::vector<int> pList) {

primeList = pList;

}

#endif

Page 11: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

11

The accessibility is determined by defining a macro TESTMODE in stdafx.h.

#pragma once

#define TESTMODE

#include "targetver.h"

// Headers for CppUnitTest

#include "CppUnitTest.h"

// TODO: reference additional headers your program requires here

#include "PrimeGenerator.h"

Since TESTMODE is only defined within the test library, accessibility to the getter

and setter methods is only granted during unit testing.

More information on preprocessors is available from this wiki and this reference).

Testing private functions using preprocessors

Rather than using public getter and setter functions to access and modify private

members, logical preprocessor directives can be used to conditionally turn private

members of the class into public members during unit testing.

Using the Library project as an example, to change the accessibility of primeList

during compile time, make the following modifications in PrimeGenerator.h:

#ifndef TESTMODE

private:

#else

public:

#endif

std::vector<int> primeList;

The accessibility is determined by defining the macro TESTMODE in stdafx.h.

When TESTMODE is not defined, the accessibility is private; else it is public.

Note the use of #ifndef which means “if not define”.

Page 12: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

12

Chapter 7 – Exception handling

This chapter describes exception handling in C++.

Throwing exceptions

An exception is the occurrence of an event during the execution of a program that

disrupts the normal flow of the program's instructions. Exception handling is a

programming mechanism to handle exceptional circumstances during program

execution, such as when the program expects to read from a file but the file cannot

be found. This is not the ‘usual’ expected situation, but nonetheless, the program

should be able to handle such contingencies.

An exception is thrown using the throw keyword.

throw std::exception("This is an exception example.");

The exception example given above is a general type exception. In Visual Studio,

such an exception instance can be instantiated with an error message specified by

the user. This error message is used to identify the exception instance during

exception handling. The C++ STL provides some specific exception types which

are described here. Moreover, exception classes can be derived from the base

exception class. By overriding the what() method, an exception can be customized

that returns a specific message when the exception is thrown. Below is an

example of a derived exception class.

class MyException : public std::exception {

virtual const char* what() const throw () {

return "This is a customized exception.";

}

};

Always remember to throw an exception by value. Throwing by any other

means such as reference or pointer (using the new keyword) will result in a severe

memory leak.

When an exception is thrown, all code that logically follows the exception

will not be executed. Therefore, be very careful when throwing exceptions in the

context of dynamic allocated memory management and file operations.

Page 13: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

13

Catching exceptions

Exceptions thrown in a try block are handled in the corresponding catch block. To

catch an exception, the catch keyword is used coupled with the exception type.

The exception handler routine is defined within the catch block.

try {

throw std::exception("This is an exception example.");

} catch (std::exception const &sampleException) {

std::cout << sampleException.what() << std::endl;

}

The sample code above simply prints out the error message associated with the

general type exception.

Unlike throwing, when catching an exception, the better way is to catch it by

const reference as shown in the code above, especially when catching a more

general exception. Consider catching the derived exception MyException as a

value of the general exception class.

try {

throw MyException();

} catch (std::exception const sampleException) {

std::cout << sampleException.what() << std::endl;

}

Object slicing causes the overridden what() method of the derived class to be

sliced and hence output “Unknown exception”. To retain the overridden properties

and methods of the derived class, catch the general exception as a reference

instead.

try {

throw MyException();

} catch (std::exception const &sampleException) {

std::cout << sampleException.what() << std::endl;

}

The code fragment now outputs “This is a customized exception”. In addition, the

const keyword forbids the exception from being modified unnecessarily.

Page 14: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

14

To allow a catch block to catch all types of exception, the exception type … is used.

In particular, this allows asynchronous system exceptions caused by hardware,

such as floating point exception, access violation, etc. to be caught.

catch (...) {

std::cout << "Caught unhandled exception" << endl;

}

The use of catch (…) may pose a potential problem when the solution is

built under Release mode. Details are provided in Chapter 8.

Using catch (…) may have a potential problem when the solution is built under

Release mode. We will come back to this topic in Chapter 8 that deals with

releasing the product.

Try-catch blocks may also be nested. In this case, if an exception was thrown in the

inner try block but not caught by the inner catch block, the exception will propagate

to the outer try block and caught by the associated outer catch block if possible. A

better way to implement nested try-catch blocks is to move the inner try-catch

block into a separate method or function.

Re-throwing exceptions

Re-throwing provides a way to throw the exception caught by the catch block. To

do that, simply use throw within the catch block.

try {

throw std::exception("This is an exception example.");

} catch (std::exception const &sampleException) {

throw;

}

Contrast this with the following code where a new exception object is initialized

from the value of the throw expression in the catch block.

try {

throw std::exception("This is an exception example.");

} catch (std::exception const &sampleException) {

throw sampleException;

}

Page 15: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

15

General rules on throwing exceptions

Do not throw or catch exceptions that can never occur. It will confuse future

project developers as to the intent of the extraneous exception handling routines.

Do try to catch a specific exception type. Catching general exceptions make

the handling routine very tedious since we need a secondary signature (e.g. the

message identifier) to determine the exact nature of the exception.

Do not simply catch an exception without any corrective action unless you

have a strong reason for it. Instead, re-throwing an inner exception can provide

additional information to the outer exception handling routine, which may help in

handling the exceptional situation.

Do not throw exceptions to signal programming errors. Exception occurs

when the user does not behave as expected. If the misunderstanding is between

developers, use assertions instead of exception.

Exception chaining

Knowing if the current exception is being caused by some other exception may

sometimes be useful. In such cases, exception chaining is required in which an

inner exception is chained within a new exception and then thrown to the outer

catch. Unfortunately, C++ exception chaining is quite obscure, and thus not

recommend for beginners. For details, refer here and here.

Subjecting exception handling to unit testing

Exception handling routines can be tested in a similar way via unit tests.

Suppose that the GetPrimeLessThan() function throws an out-of-range

exception when the input to the function is non-negative.

Under LibraryTest – Source Files, create another test class in another file

PrimeGeneratorExceptionTest.cpp to handle the exception test.

Page 16: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

16

Notice in the new file that the exception unit testing comes under the same

namespace as the unit tests performed in chapter 6.

namespace LibraryTest

{

TEST_CLASS(ExceptionTest)

{

public:

TEST_METHOD(GetPrimeLessThan_ExceptionTest)

{

// TODO: Your test code here

}

};

}

Modify TEST_METHOD to the following:

TEST_METHOD(GetPrimeLessThan_ExceptionTest) {

PrimeGenerator prime;

bool correctException;

try {

prime.GetPrimeLessThan(-1);

Assert::Fail(L"No exception has been thrown.");

}

catch (std::out_of_range) {

correctException = true;

}

catch (...) {

correctException = false;

}

Assert::IsTrue(correctException);

}

Page 17: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

17

In addition, include bounds checking to the GetPrimeLessThan function in

PrimeGenerator.cpp.

std::vector<int> PrimeGenerator::GetPrimeLessThan(int upperBound) {

if (upperBound < 0) {

throw std::out_of_range("Non-negative interger required.");

}

...

}

Build the solution and select Run… -> Run Not Run Test and observe that the unit

test passes.

Exception handling in CLI/C++

Handling exceptions in CLI/C++ (UI project) is almost similar except for the need to

throw an exception by reference.

throw gcnew System::Exception("This is a CLR exception example");

In CLI/C++, gcnew returns a reference to an object in the garbage collection heap.

Therefore, any potential memory leak problem is fixed by the garbage collection

mechanism.

In addition, CLI/C++ provides a sophisticated exception chaining mechanism. By

simply putting the active exception as a second parameter of the new exception,

the exceptions can be chained.

Page 18: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

18

try {

// Your code

} catch (System::Exception const ^ cliSampleException) {

throw gcnew System::Exception("This is a chained exception",

clrSampleException);

}

Having looked into unit testing for both functional code as well as exception

handling, the final stage on product release is up next.

Page 19: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

19

Chapter 8 – Product release

This chapter discusses product release using VS.

From debug to release

On the toolbar, change the build mode from Debug to Release.

After changing the building mode, there is a need to configure the projects’

properties once again, particularly on configuring the Library and UI projects,

setting the additional include directories and adding the entry point. Refer to

chapter 4 of part I of the document for more details.

Moreover, there is a need to configure the Exception Handling mode.

Open the property page of a target project and go to C/C++ – Code Generation.

Change Enable C++ Exceptions option to Yes with SEH exceptions (/EHa).

Replicate this procedure to change all necessary projects.

Page 20: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

20

Why configure Exception Handling Mode?

Under Release mode, any catch (…) statement with no throw in the

corresponding try block will be dropped due to the default compile time

optimization. All catch (…) without explicit throw statements in the corresponding

try block are treated as unreachable statements and sliced. This default setting is

known as synchronous exception handling. Hence, there is a need to

configure the project’s property for asynchronous exception handling so that

catch (…) will be entertained. Check out this thread for more details.

With the entire solution rebuilt, go to the Release directory under the root

PrimeGenerator solution folder, and double click on UI.exe. This is the executable

of your product.

Page 21: An Introduction to Visual Studio 2012 (Visual C++) Part IIcs2103/AY1516S1/files/An introducti… · 1 An Introduction to Visual Studio 2012 (Visual C++) – Part II Author: Huang

21

References

1. Walkthrough: Explore the Visual Studio IDE with C# or Visual Basic, MSDN,

http://msdn.microsoft.com/en-us/library/jj153219.aspx

2. Unit testing native code with Test Explorer, MSDN,

http://msdn.microsoft.com/en-us/library/hh270864.aspx