Регистрация  |  Вход

Тестирование web callouts

Доброго времени суток, коллеги. Наткнулся я на большой затык с тестированем и решил спросить совета. Один мозг хорошо, а много - лучше.

Имеется такая вот схемка:
Написана страница на Visualforce целиком повторяющая разметку нашей стандартной страницы для Account'a.
К этой странице прицеплено расширение контроллера Account'a которое стягивает данные со страницы, обрабатывает их и собирает в объект (типа Account). Затем посылает другому apex классу (на самом деле там их несколько, так как сам интерфейс построен путём парсинга WSDL), который синхронно стучится к нам в SAP через SOAP callout, посылает данные, через ответ забирает что ему нужно, обновляет наш объект после чего делает комит нового аккаунта в БД.
На этом история не заканчивается и начинает работать триггер (on account, after insert). Он делает асинхронный web сall в тот же SAP чтобы передать Account ID свежесозданого класса. Делается это таким путем из-за того, что SalesForce не позволяет делать web calls после DML операции.

Все написано, всё отлично работает. Только как это тестировать - ума не приложу. Пробовал несколько вариантов и либо натыкаюсь на ограничения платформы, либо как-то совсем безобразно получается. Идеально было бы програмно создать тестовый аккаунт со всеми необходимыми данными, передать его в расширение контроллера, который дальше бы шел по всей цепочке. Ответ от SAPа мы бы получили из mock web call. Вот только получается так, что SalesForce все равно жалутся на "You have uncommited work pending" на стадии вызова web callout (через test.setMock) . Тестировать частями как-то не хочется, потому как это хоть и поможет получить 75%, но не имеет большого практического смысла.

Кто-нибудь уже сталкивался с подобными проблемами? Надеюсь объяснение не очень сумбурное и позволило уловить общий смысл Спасибо.

Доброго времени суток, коллеги. Наткнулся я на большой затык с тестированем и решил спросить совета. Один мозг хорошо, а много - лучше.

Имеется такая вот схемка:
Написана страница на Visualforce целиком повторяющая разметку нашей стандартной страницы для Account'a. 
К этой странице прицеплено расширение контроллера Account'a которое стягивает данные со страницы, обрабатывает их и собирает в объект (типа Account). Затем посылает другому apex классу (на самом деле там их несколько, так как сам интерфейс построен путём парсинга WSDL), который синхронно стучится к нам в SAP через SOAP callout, посылает данные, через ответ забирает что ему нужно, обновляет наш объект после чего делает комит нового аккаунта в БД. 
На этом история не заканчивается и начинает работать триггер (on account, after insert). Он делает асинхронный web сall в тот же SAP чтобы передать Account ID свежесозданого класса. Делается это таким путем из-за того, что SalesForce не позволяет делать web calls после DML операции. 

Все написано, всё отлично работает. Только как это тестировать - ума не приложу. Пробовал несколько вариантов и либо натыкаюсь на ограничения платформы, либо как-то совсем безобразно получается. Идеально было бы програмно создать тестовый аккаунт со всеми необходимыми данными, передать его в расширение контроллера, который дальше бы шел по всей цепочке. Ответ от SAPа мы бы получили из mock web call. Вот только получается так, что SalesForce все равно жалутся на "You have uncommited work pending" на стадии вызова web callout (через test.setMock) . Тестировать частями как-то не хочется, потому как это хоть и поможет получить 75%, но не имеет большого практического смысла.

Кто-нибудь уже сталкивался с подобными проблемами? Надеюсь объяснение не очень сумбурное и позволило уловить общий смысл :)
Спасибо.

Привет andreyzh.

Точного рецепта дать сейчас не могу, но можно порассуждать.

Во-первых вот как понравилось тестировать web callouts:

public with sharing class SomeClass {

public HttpResponse testResponse { get; set; }

public void SomeClassMethod() {
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
...
if (!test.isRunningTest()) {
res = http.send(req);
} else {
res = testResponse;
}

}
}

SomeClass ctrl = new SomeClass();
HttpResponse res = new HttpResponse();
res.setHeader('Content-Type', 'text/html');
res.setBody('some_successful_response_from_service');
res.setStatusCode(200);
ctrl.testResponse = res;
ctrl.SomeClassMethod();

res.setBody('some_wrong_response_from_service');
res.setStatusCode(200);
ctrl.testResponse = res;
ctrl.SomeClassMethod();

....

Преимущество данного метода в том что мы не выполяем настоящего callout, со всеми его ограничениями, а заменяем его нашим ответом. Таким образом Salesforce спокоен, а мы можем тестировать наш класс много раз, подсовывая ему разные ответы. Недостаток - остается непокрытой одна строчка - непосредственно сам callout, ну и надо немного подогнать сам класс под работу с тестами (но на SF это нормальная практика ).

Теперь непосредственно про твой случай. Вызов callout у тебя находится в триггере. Сам триггер "модифицировать" подобным образом как класс выше будет сложнее (чтобы можно было его инициализировать нашим ответом перед срабатыванием). Есть два выхода:
1. Вынести логику web callout из триггера в статический метод какого нибудь класса util. Это уже что-то из области паттернов проектирования. Естественно придется тестировать триггер и класс util отдельно.
2. Если не важно тестирование триггера с разными типами ответов (правильный, неправильный), то можно прямо в триггере подготовить правильный ответ и подсовывать его вместо вызова callout. Это намного упрощает процесс, но останется непокрытым код, который обрабатывает неправильные ответы.

На счет тестирования частями и практический смысл - это нормальная практика в SF и во многих случах это единственный выход. Каждый тест метод это отдельный процесс, со своими лимитами. И обычно тест класс это набор тест методов, которые тестируют отдельный функционал. Написать тесты, которые смогут протестировать "все" за один проход - это сложная задача и часто пустая трата времени. Да простят меня мои заказчики :), но тесты в SF больше нужны. чтобы покрыть код более чем на 75%, чем действительно протестировать функционал.

Привет andreyzh.

Точного рецепта дать сейчас не могу, но можно порассуждать.

Во-первых вот как понравилось [b]тестировать web callouts[/b]:

[code]
public with sharing class SomeClass {

    public HttpResponse testResponse { get; set; }

    public void SomeClassMethod() {
       HttpRequest req = new HttpRequest();
       HttpResponse res = new HttpResponse();
       ...
       if (!test.isRunningTest()) {
            res = http.send(req);
       } else {
            res = testResponse;
       }

    }    
}
[/code]

[code]
SomeClass ctrl = new SomeClass();
HttpResponse res = new HttpResponse();
res.setHeader('Content-Type', 'text/html');
res.setBody('some_successful_response_from_service');
res.setStatusCode(200);
ctrl.testResponse = res;
ctrl.SomeClassMethod();

res.setBody('some_wrong_response_from_service');
res.setStatusCode(200);
ctrl.testResponse = res;
ctrl.SomeClassMethod();

....

[/code]

Преимущество данного метода в том что мы [b]не выполяем [/b]настоящего callout, со всеми его ограничениями, а заменяем его нашим ответом. Таким образом Salesforce спокоен, а мы можем тестировать наш класс много раз, подсовывая ему разные ответы. Недостаток - остается непокрытой одна строчка - непосредственно сам callout, ну и надо немного подогнать сам класс под работу с тестами (но на SF это нормальная практика :) ).

Теперь непосредственно про твой случай. Вызов callout у тебя находится в триггере. Сам триггер "модифицировать" подобным образом как класс выше будет сложнее (чтобы можно было его инициализировать нашим ответом перед срабатыванием). Есть два выхода:
1. Вынести логику web callout из триггера в статический метод какого нибудь класса util. Это уже что-то из области паттернов проектирования. Естественно придется тестировать триггер и класс util отдельно.
2. Если не важно тестирование триггера с разными типами ответов (правильный, неправильный), то можно прямо в триггере подготовить правильный ответ и подсовывать его вместо вызова callout. Это намного упрощает процесс, но останется непокрытым код, который обрабатывает неправильные ответы.

На счет тестирования частями и практический смысл - это нормальная практика в SF и во многих случах это единственный выход. Каждый тест метод это отдельный процесс, со своими лимитами. И обычно тест класс это набор тест методов, которые тестируют отдельный функционал. Написать тесты, которые смогут протестировать "все" за один проход - это сложная задача и часто пустая трата времени. Да простят меня мои заказчики :), но тесты в SF больше нужны. чтобы покрыть код более чем на [b]75%[/b], чем действительно протестировать функционал.

Еще один момент про тест методы.

Есть такие замечательные команды, которые просто обязательны для использования:

Test.startTest()

Test.stopTest()

До команды startTest необходимо делать всю необходимую инициализацию, подготовку данных. После этой команды все лимиты сбрасываются и следующий код выполяется как бы с чистого листа.

Команда stopTest полезна тем что синхронно запускает все асинхронные процессы. Это позволяет запустить и проверить как отработали методы с анотацией @future и также batch.

Еще один момент про тест методы.

Есть такие замечательные команды, которые просто обязательны для использования:

[url=http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_System_Test_startTest.htm]Test.startTest()[/url]

[url=http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_System_Test_stopTest.htm]Test.stopTest()[/url]

До команды [b]startTest[/b] необходимо делать всю необходимую инициализацию, подготовку данных. После этой команды все лимиты сбрасываются и следующий код выполяется как бы с чистого листа.

Команда [b]stopTest[/b] полезна тем что синхронно запускает все асинхронные процессы. Это позволяет запустить и проверить как отработали методы с анотацией @future и также batch.

Я думаю это должно помочь для решения проблемы.
Test.startTest()

Test.stopTest()

Можно еще здесь почитать.

Testing Webservice Callouts with Winter '13 Test.setMock fails because of Test Data creation.

Я думаю это должно помочь для решения проблемы.
[url=http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_System_Test_startTest.htm]Test.startTest()[/url]

[url=http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_System_Test_stopTest.htm]Test.stopTest()[/url]

Можно еще здесь почитать.

[url=http://boards.developerforce.com/t5/Apex-Code-Development/Testing-Webservice-Callouts-with-Winter-13-Test-setMock-fails/td-p/510427/page/3]Testing Webservice Callouts with Winter '13 Test.setMock fails because of Test Data creation.[/url]

Спасибо за ответы!

В общем, как я понял, идеального решения что бы и на ёлку залезть и полностью протестировать нет пока что. Видимо придётся тестировать методы по отдельности так как код изначально не был написан для тестирования. Если переделывать, то получится довольно уродливо
Боюсь что Test.startTest() и Test.stopTest в моём случае не помогут так как там идёт цепочка вызовов методов и прервать её не получится. Вот если бы их можно было бы указывать напрямую в тестируемом коде, а не в тест методе...


P.S. Ну и если кому-то вдруг будет интересно, вот кусок кода который описывает Mock Web Callout и сам тест метод.
Собственно при прочих равных строка

extension.SaveAndSendToSap();
и должна пройтись через весь процесс создания, обменом данных синхронным и асинхронным методом итд итп.

Mock Callout:

global void doInvoke (
object stub,
object request,
map<string, object> response,
string endpoint,
string soapAction,
string requestName,
string responseNS,
string responseName,
string responseType )
{
//Create fake SAP response data assembly
SapCrm_RfcFunctions.Z_BAPI_CRM_CREATE_ACCOUNTS_Response_element responseElement = new SapCrm_RfcFunctions.Z_BAPI_CRM_CREATE_ACCOUNTS_Response_element();

SapCrm_RfcFunctions.EP_RETURN_element epReturnElement = new SapCrm_RfcFunctions.EP_RETURN_element();

SapCrm_RfcFunctions.ZBAPI_S_CRM_RETURN zbapiReply = new SapCrm_RfcFunctions.ZBAPI_S_CRM_RETURN();
SapCrm_RfcFunctions.ZBAPI_S_CRM_RETURN[] zbapiReplies = new list <SapCrm_RfcFunctions.ZBAPI_S_CRM_RETURN> ();

zbapiReply.CUSTOMER = '0123456789';
zbapiReply.TYPE_x = 'S';
zbapiReply.MESSAGE = 'Test success message';
zbapiReply.LOG_NO = '01';
zbapiReply.LOG_MSG_NO = 'ZZ';
zbapiReply.CRM_ID = '0000000';
zbapiReply.CODE = '000000';
zbapiReply.MESSAGE_V1 = '000000';
zbapiReply.MESSAGE_V2 = '000000';
zbapiReply.MESSAGE_V3 = '000000';
zbapiReply.MESSAGE_V4 = '000000';

zbapiReplies.add(zbapiReply);
epReturnElement.item = zbapiReplies;
responseElement.EP_RETURN = epReturnElement;

//system.debug('*** We Before response.put(\'response_x\', response_Success); statement exec ***');
response.put('response_x', responseElement);
}

Ну и тест метод:

static testMethod void accountIntegrationTest() {

//Set reference to a mock web call
Test.setMock(WebServiceMock.class, new SapCrm_WebServiceCallMock());

// Get active recordtypes
Map <Id, RecordType> rids = new Map <Id, RecordType>([select Id, DeveloperName, IsActive from RecordType where IsActive = true and SobjectType = 'Account']);

Map <String, Id> rtMap = new Map <String, Id>();
for(RecordType r : rids.values()){
rtMap.put(r.DeveloperName, r.Id);
}

// Insert Prospect Account
Account acc = new Account();
acc.Name = 'testSAPIntegration';
acc.RecordTypeId = rtMap.get('Prospect');
acc.BillingStreet = 'Keilasatama 3';
acc.Street_1__c = 'Keilasatama 3';
acc.Street_2__c = 'PL 1';
acc.Street_3__c = 'Keilasatama';
acc.Street_4__c = 'Keilalahti';
acc.Street_5__c = 'Kehä1';
acc.BillingCity = 'Espoo';
acc.xx_City__c = 'Espoo';
acc.BillingPostalCode = '02150';
acc.xx_Zip_Postal_Code__c = '02150';
acc.BillingCountry = 'Finland';
acc.xx_Country__c = 'Finland';
acc.Phone = '020202';
acc.Fax = '030303';
acc.Customer_Type__c = 'Builder';
acc.CurrencyIsoCode = 'EUR';
acc.Account_Language__c = 'Finnish';

//insert acc;

/**********************************************
* Testing Account Controller Extension
***********************************************/

//TODO: Test and assert fake web calls that return true or false

//Constructing instance of custom controller SapCrm_AccountOverride which does ???
ApexPages.Standardcontroller sc = new ApexPages.Standardcontroller(acc);
SapCrm_AccountOverride extension = new SapCrm_AccountOverride(sc);

//Test save method
extension.SaveAndSendToSap();
//extension.SaveEditAndSendToSap();
//extension.SaveWithoutSap();
}

Спасибо за ответы!

В общем, как я понял, идеального решения что бы и на ёлку залезть и полностью протестировать нет пока что. Видимо придётся тестировать методы по отдельности так как код изначально не был написан для тестирования. Если переделывать, то получится довольно уродливо :)

Боюсь что Test.startTest() и Test.stopTest в моём случае не помогут так как там идёт цепочка вызовов методов и прервать её не получится. Вот если бы их можно было бы указывать напрямую в тестируемом коде, а не в тест методе...


P.S. Ну и если кому-то вдруг будет интересно, вот кусок кода который описывает Mock Web Callout и сам тест метод.
Собственно при прочих равных строка [code]extension.SaveAndSendToSap();[/code] и должна пройтись через весь процесс создания, обменом данных синхронным и асинхронным методом итд итп.

Mock Callout:

[code]global void doInvoke (
		object stub,
		object request,
		map<string, object> response,
		string endpoint,
		string soapAction,
		string requestName,
		string responseNS,
		string responseName,
		string responseType )
		{
			//Create fake SAP response data assembly
			SapCrm_RfcFunctions.Z_BAPI_CRM_CREATE_ACCOUNTS_Response_element responseElement = new SapCrm_RfcFunctions.Z_BAPI_CRM_CREATE_ACCOUNTS_Response_element();
			 
			SapCrm_RfcFunctions.EP_RETURN_element epReturnElement = new SapCrm_RfcFunctions.EP_RETURN_element();
			
			SapCrm_RfcFunctions.ZBAPI_S_CRM_RETURN zbapiReply = new SapCrm_RfcFunctions.ZBAPI_S_CRM_RETURN();
			SapCrm_RfcFunctions.ZBAPI_S_CRM_RETURN[] zbapiReplies = new list <SapCrm_RfcFunctions.ZBAPI_S_CRM_RETURN> ();
		
			zbapiReply.CUSTOMER = '0123456789';
			zbapiReply.TYPE_x = 'S';
			zbapiReply.MESSAGE = 'Test success message';
			zbapiReply.LOG_NO = '01';
			zbapiReply.LOG_MSG_NO = 'ZZ';
			zbapiReply.CRM_ID = '0000000';
	               zbapiReply.CODE = '000000';
	               zbapiReply.MESSAGE_V1 = '000000';
	               zbapiReply.MESSAGE_V2 = '000000';
	               zbapiReply.MESSAGE_V3 = '000000';
	               zbapiReply.MESSAGE_V4 = '000000';
			
			zbapiReplies.add(zbapiReply);
			epReturnElement.item = zbapiReplies;
			responseElement.EP_RETURN = epReturnElement;
			
			//system.debug('*** We Before response.put(\'response_x\', response_Success); statement exec ***');
			response.put('response_x', responseElement);
		}[/code]

Ну и тест метод:

[code]static testMethod void accountIntegrationTest() {
    	
    	//Set reference to a mock web call
    	Test.setMock(WebServiceMock.class, new SapCrm_WebServiceCallMock());
        
        // Get active recordtypes
    	Map <Id, RecordType> rids = new Map <Id, RecordType>([select Id, DeveloperName, IsActive from RecordType where IsActive = true and SobjectType = 'Account']);     
        
        Map <String, Id> rtMap = new Map <String, Id>();
        for(RecordType r : rids.values()){
      		rtMap.put(r.DeveloperName, r.Id);
    	} 
    		
        // Insert Prospect Account
	    Account acc = new Account();
	    acc.Name = 'testSAPIntegration';
	    acc.RecordTypeId = rtMap.get('Prospect');
	    acc.BillingStreet = 'Keilasatama 3';
	    acc.Street_1__c = 'Keilasatama 3';
	    acc.Street_2__c = 'PL 1';
	    acc.Street_3__c = 'Keilasatama';
	    acc.Street_4__c = 'Keilalahti';
	    acc.Street_5__c = 'Kehä1';
	    acc.BillingCity = 'Espoo';
	    acc.xx_City__c = 'Espoo';
	    acc.BillingPostalCode = '02150';
	    acc.xx_Zip_Postal_Code__c = '02150';
	    acc.BillingCountry = 'Finland';
	    acc.xx_Country__c = 'Finland';
	    acc.Phone = '020202';
	    acc.Fax = '030303';
	    acc.Customer_Type__c = 'Builder';
	    acc.CurrencyIsoCode = 'EUR';
	    acc.Account_Language__c = 'Finnish';
	    
    	//insert acc;

	    /********************************************** 
	    *	Testing Account Controller Extension
    	***********************************************/
    	
    	//TODO: Test and assert fake web calls that return true or false
	    
	    //Constructing instance of custom controller SapCrm_AccountOverride which does ???
	    ApexPages.Standardcontroller sc = new ApexPages.Standardcontroller(acc);
	    SapCrm_AccountOverride extension = new SapCrm_AccountOverride(sc);
	    
	    //Test save method
	    extension.SaveAndSendToSap();
	    //extension.SaveEditAndSendToSap();
	    //extension.SaveWithoutSap();
    }[/code]

Ну и в качестве апдейта к предыдущему. Я не знаю как, почему и зачем, но оно вдруг резко заработало. Без каких-либо видимых измений в коде. Тест проходит полностью через все методы и calloutы. Мистика.

Ну и в качестве апдейта к предыдущему. Я не знаю как, почему и зачем, но оно вдруг резко заработало. Без каких-либо видимых измений в коде. Тест проходит полностью через все методы и calloutы. Мистика.

andreyzh
Я не знаю как, почему и зачем, но оно вдруг резко заработало. Без каких-либо видимых измений в коде. Тест проходит полностью через все методы и calloutы. Мистика.

Я конечно в мистику не верю, но сам пару раз сталкивался с подобными случаями Наверное это нормально среди программистов.

Спасибо за пример твоего кода. Для меня и, надеюсь, не только полезно, это будет полезным опытом.

Еще небольшое дополнение:
использование test.startTest и test.stopTest не просто можно но и нужно. Это, можно так сказать, обязательные команды в теле тест методов.

В твоем случае нужно сделать примерно так:

//Constructing instance of custom controller SapCrm_AccountOverride which does ???
...
ApexPages.Standardcontroller sc = new ApexPages.Standardcontroller(acc);
SapCrm_AccountOverride extension = new SapCrm_AccountOverride(sc);
test.startTest()
//Test save method
extension.SaveAndSendToSap();
//extension.SaveEditAndSendToSap();
//extension.SaveWithoutSap();
test.stopTest()
...

Поясню почему.

До первой команды test.startTest() у тебя идет подготовка данных. Затем идет непосредственное тестирование extension+trigger (то что касается синхронно выполняемого кода). После команды test.stopTest() должен запуститься @future метод, который ты инициализируешь в триггере (который по твоему описанию должен выполнить web callout).

Пока, если я правильно понял, твой @future метод не будет покрыт тестами. Проверь плиз и отпиши прав ли я.

[quote="andreyzh"]Я не знаю как, почему и зачем, но оно вдруг резко заработало. Без каких-либо видимых измений в коде. Тест проходит полностью через все методы и calloutы. Мистика.[/quote]

Я конечно в мистику не верю, но сам пару раз сталкивался с подобными случаями  :D Наверное это нормально среди программистов.

Спасибо за пример твоего кода. Для меня и, надеюсь, не только полезно, это будет полезным опытом.

Еще небольшое дополнение:
использование test.startTest и test.stopTest не просто можно но и нужно. Это, можно так сказать, обязательные команды в теле тест методов.

В твоем случае нужно сделать примерно так:

[code]
 //Constructing instance of custom controller SapCrm_AccountOverride which does ???
...
       ApexPages.Standardcontroller sc = new ApexPages.Standardcontroller(acc);
       SapCrm_AccountOverride extension = new SapCrm_AccountOverride(sc);
   test.startTest()    
       //Test save method
       extension.SaveAndSendToSap();
       //extension.SaveEditAndSendToSap();
       //extension.SaveWithoutSap();
   test.stopTest()
...
[/code]

 Поясню почему.

До первой команды test.startTest() у тебя идет подготовка данных. Затем идет непосредственное тестирование extension+trigger (то что касается синхронно выполняемого кода). После команды test.stopTest() должен запуститься @future метод, который ты инициализируешь в триггере (который по твоему описанию должен выполнить web callout). 

Пока, если я правильно понял, твой @future метод не будет покрыт тестами. Проверь плиз и отпиши прав ли я.

Да, я вписал Test.startTest и Test.EndTest, теперь этот кусок выглядит как-то так:

// Constructing instance of custom controller SapCrm_AccountOverride which does ???
ApexPages.Standardcontroller sc = new ApexPages.Standardcontroller(acc);
SapCrm_AccountOverride extension = new SapCrm_AccountOverride(sc);

// Test save method
Test.startTest();
extension.SaveAndSendToSap();
Test.stopTest();

// Test save anyway method
ApexPages.Standardcontroller sc1 = new ApexPages.Standardcontroller(acc1);
SapCrm_AccountOverride extension1 = new SapCrm_AccountOverride(sc1);
extension1.SaveWithoutSap();

А насчет покрытия - похоже на то, что сработало фактически всё. Желтым отмечены мои классы и триггер (см вложение). 62% в AccountOverride - это те случаи, когда идет обработка от неудачного Callout'a который я еще не сделал. Но так получается что покрытие можно сделать на 90%+

Да, я вписал Test.startTest и Test.EndTest, теперь этот кусок выглядит как-то так:

[code]// Constructing instance of custom controller SapCrm_AccountOverride which does ???
      ApexPages.Standardcontroller sc = new ApexPages.Standardcontroller(acc);
      SapCrm_AccountOverride extension = new SapCrm_AccountOverride(sc);
      
      // Test save method
      Test.startTest();
      extension.SaveAndSendToSap();
      Test.stopTest();
      
      // Test save anyway method
      ApexPages.Standardcontroller sc1 = new ApexPages.Standardcontroller(acc1);
      SapCrm_AccountOverride extension1 = new SapCrm_AccountOverride(sc1);
      extension1.SaveWithoutSap();[/code]

А насчет покрытия - похоже на то, что сработало фактически всё. Желтым отмечены мои классы и триггер (см вложение). 62% в AccountOverride - это те случаи, когда идет обработка от неудачного Callout'a который я еще не сделал. Но так получается что покрытие можно сделать на 90%+

Отлично, что получается и код покрыт тестами. Нет ничего приятнее взгляду чем исходный код выделенный синим цветом
Но опять небольшое замечание:
Если делать совсем правильно, то test.stopTest должен находиться в самом конце тест метода.

т.е. твой код выглядеть должен так:

// Constructing instance of custom controller SapCrm_AccountOverride which does ???
ApexPages.Standardcontroller sc = new ApexPages.Standardcontroller(acc);
SapCrm_AccountOverride extension = new SapCrm_AccountOverride(sc);

// Test save method
Test.startTest();
extension.SaveAndSendToSap();

// remove from here - Test.stopTest();

// Test save anyway method
ApexPages.Standardcontroller sc1 = new ApexPages.Standardcontroller(acc1);
SapCrm_AccountOverride extension1 = new SapCrm_AccountOverride(sc1);
extension1.SaveWithoutSap();

Test.stopTest(); // insert here

Ну и совсем если хочешь чтобы было идеально (приближено к боевым условиям), то надо отдельно написать тест метод для SaveWithoutSap() и отдельно для SaveAndSendToSap(), т.е. чтобы в одном тестовом классе получилось 2 тест метода. Ну это уже на вкус.

Отлично, что получается и код покрыт тестами. Нет ничего приятнее взгляду чем исходный код выделенный синим цветом :)

Но опять небольшое замечание:
Если делать совсем правильно, то test.stopTest должен находиться в самом конце тест метода.

т.е. твой код выглядеть должен так:

[code]
// Constructing instance of custom controller SapCrm_AccountOverride which does ???
      ApexPages.Standardcontroller sc = new ApexPages.Standardcontroller(acc);
      SapCrm_AccountOverride extension = new SapCrm_AccountOverride(sc);
      
      // Test save method
      Test.startTest();
      extension.SaveAndSendToSap();

      //   remove from here - Test.stopTest();
      
      // Test save anyway method
      ApexPages.Standardcontroller sc1 = new ApexPages.Standardcontroller(acc1);
      SapCrm_AccountOverride extension1 = new SapCrm_AccountOverride(sc1);
      extension1.SaveWithoutSap();

      Test.stopTest();  // insert here 

[/code]

Ну и совсем если хочешь чтобы было идеально (приближено к боевым условиям), то надо отдельно написать тест метод для SaveWithoutSap() и отдельно для SaveAndSendToSap(), т.е. чтобы в одном тестовом классе получилось 2 тест метода. Ну это уже на вкус.

Уху, замечания принимаются
Это на ToDo на завтра.

Уху, замечания принимаются :) 
Это на ToDo на завтра.

Вот эту хорошую тему можно уже перенести в "Интеграция со сторонними сервисами", иначе затеряется.

завтра буду браться за тестирование.

Вот эту хорошую тему можно уже перенести в "Интеграция со сторонними сервисами", иначе затеряется.

завтра буду  браться за тестирование.

Пожалуйста :)

Пожалуйста :)

Вот только что написал тест класс в котором ничего абсолютно нет кроме создание какой-то пустой записи и вызов футур-ауткола с этой записью. Думал тест упадет.

Но он внезапно не упал. В Эклипсе покрытие футур класса 1% и самого вебсервис класса 0%

А в Орге и консоли - футур класса 78% и самого вебсервис класса 89% (ЕСЛИ проверять построчное покрытие или открыть страницу с самим классом и посмотреть покрытие). Не покрылся только код, который работает с результатом колаута.

В Логе орг консоли написано:
Methods defined as testMethod do not support web service callouts, test skipped.

вот и не знаю где здесь подвох: эти чудо-результаты покрытия будут действительны при попытке переноса кода в прод или все это иллюзия успеха...

PS: сделал подмену колаут-ответа для тестовой ситуации , как в самом начале темы описал Дмитрий - и тест passed.
сейчас заполню тестовый ответ какой-нибудь датой - чтобы дальше покрыть код.

А после попробую все это тестировать через тестовый вызов тригера - который и вызывает в реальности этот колаут.
надеюсь он не добавит новых проблем.

Вот только что написал тест класс в котором ничего абсолютно нет кроме создание какой-то пустой записи и вызов футур-ауткола с этой записью. Думал тест упадет.

Но он внезапно не упал. В Эклипсе покрытие футур класса 1% и самого вебсервис класса 0%

А в Орге и консоли - футур класса 78% и самого вебсервис класса 89% (ЕСЛИ проверять построчное покрытие или открыть страницу с самим классом и посмотреть покрытие). Не покрылся только код, который работает с результатом колаута.

В Логе орг консоли написано:
Methods defined as testMethod do not support web service callouts, test skipped.

вот и не знаю где здесь подвох: эти чудо-результаты покрытия будут действительны при попытке переноса кода в прод или все это  иллюзия успеха...

PS: сделал подмену колаут-ответа для тестовой ситуации , как в самом начале темы описал Дмитрий - и тест passed.
сейчас заполню тестовый ответ какой-нибудь датой - чтобы дальше покрыть код.

А после попробую все это тестировать через тестовый вызов тригера - который и вызывает в реальности этот колаут. 
надеюсь он не добавит новых проблем.

Это иллюзия успеха
Если покрытие кода показывает неадекватные результаты, то нужно очистить историю результатов выполнения тестов и запустить тесты повторно.

А в Орге и консоли - футур класса 78% и самого вебсервис класса 89% (ЕСЛИ проверять построчное покрытие или открыть страницу с самим классом и посмотреть покрытие). Не покрылся только код, который работает с результатом колаута.

Это правильный вариант. Будет ошибка и код залить на прод не получится.

Это иллюзия успеха :)

Если покрытие кода показывает неадекватные результаты, то нужно очистить историю результатов выполнения тестов и запустить тесты повторно.

[quote]А в Орге и консоли - футур класса 78% и самого вебсервис класса 89% (ЕСЛИ проверять построчное покрытие или открыть страницу с самим классом и посмотреть покрытие). Не покрылся только код, который работает с результатом колаута.[/quote]
Это правильный вариант. Будет ошибка и код залить на прод не получится.

Вроде все получилось.

Сделал вот это:
сделал подмену колаут-ответа для тестовой ситуации , как в самом начале темы описал Дмитрий - и тест passed.
сейчас заполню тестовый ответ какой-нибудь датой - чтобы дальше покрыть код.

Далее тестом вызвал этот колаут через тригер - который его в норме и вызывает.
И в Эклипс вернулся результат покрытий 2 тех классов и этого тригера от 84 до 100%.

А я уже приготовился к витеиватейшим головоломкам.

Вроде все получилось.

Сделал вот это:
сделал подмену колаут-ответа для тестовой ситуации , как в самом начале темы описал Дмитрий - и тест passed.
сейчас заполню тестовый ответ какой-нибудь датой - чтобы дальше покрыть код.

Далее тестом вызвал этот колаут через тригер - который его в норме и вызывает.
И в Эклипс вернулся результат покрытий 2 тех классов и этого тригера от 84 до 100%.

А я уже приготовился к витеиватейшим головоломкам.

А я уже приготовился к витеиватейшим головоломкам.

Да ну Программирование это легко. Не так как кажется на первый взгляд.

[quote]А я уже приготовился к витеиватейшим головоломкам.[/quote]
Да ну :) Программирование это легко. Не так как кажется на первый взгляд.