1.Visualforce는 Lightning Platform에서 호스팅할 수 있는 모바일 및 데스크탑 앱을 위한

정교한 사용자 정의 사용자 인터페이스를 구축할 수 있게 해주는 웹 개발 프레임워크다.

 

또한 Salesforce의 기본 제공 기능을 확장하고 새로운 기능으로 교체하면서완전히 새로운 앱을

구축할 수 있다고는 하는데 기존 어플을 만든 경험은 없기 때문에 그냥 개발하는 툴이라는 생각밖에 들지 않는다.

 

apex라는 자체 언어를 여기에도 끼얹어서 그런지

이유는 모르겠지만 코드 앞부분에 apex:를 붙이는 것을 좋아한다.

//정보 수정 페이지에 어울리는 기능
<apex:page standardController="Contact" >
    <apex:form >
        <apex:pageBlock title="Edit Contact">
            <apex:pageBlockSection columns="1">
                <apex:inputField value="{!Contact.FirstName}"/>
	                <apex:inputField value="{!Contact.LastName}"/>
                <apex:inputField value="{!Contact.Email}"/>
                <apex:inputField value="{!Contact.Birthdate}"/>
            </apex:pageBlockSection>
            <apex:pageBlockButtons >
                <apex:commandButton action="{!save}" value="Save"/>
            </apex:pageBlockButtons>
        </apex:pageBlock>
    </apex:form>
</apex:page>

 

 

2.아래에는 부분적으로 사용되는 사용 예시 정리다.

//이미지 첨부
<apex:image url="https://developer.salesforce.com/files/salesforce-developer-network-logo.png"/>

//글로벌 값 조회
//Visualforce 표현식은 대소문자를 구분하지 않으며, {! ... } 내 공백은 무시
{! $GlobalName.fieldName }

//내부 계산 가능
<p>The year today is {! YEAR(TODAY()) }</p>
<p>Tomorrow will be day number  {! DAY(TODAY() + 1) }</p>
<p>Let s find a maximum: {! MAX(1,2,3,4,5,6,5,4,3,2,1) } </p>
<p>The square root of 49 is {! SQRT(49) }</p>
<p>Is it true?  {! CONTAINS('salesforce.com', 'force.com') }</p>
<p>{! IF( CONTAINS('salesforce.com','force.com'),'Yep', 'Nope') }</p>

//글로벌 값 수식 표현
({! IF($User.isActive, $User.Username, 'inactive') })

//점 표기법으로 레코드간의 관계 탐색 가능
Account owner: {! Account.Owner.Name }


//standardController 설정으로 인한 접근 가능
<apex:page standardController="Contact">
    {! Contact.lastName}
    {! Contact.FirstName}
    {! Contact.Owner.Email}
</apex:page>


//standardController 내부 value, var 사용
<apex:page standardController="Account">
    <apex:pageBlock title="Account Details">
        <apex:pageBlockSection >
            <apex:outputField value="{! Account.Name }"/>
            <apex:outputField value="{! Account.Phone }"/>
            <apex:outputField value="{! Account.Industry }"/>
            <apex:outputField value="{! Account.AnnualRevenue }"/>
        </apex:pageBlockSection>
    </apex:pageBlock>
    <apex:pageBlock title="Contacts">
       <apex:pageBlockTable value="{!Account.contacts}" var="contact">
          <apex:column value="{!contact.Name}"/>
          <apex:column value="{!contact.Title}"/>
          <apex:column value="{!contact.Phone}"/>
       </apex:pageBlockTable>
    </apex:pageBlock>
</apex:page>


//페이지네이션 및 필터
<apex:page standardController="Contact" recordSetVar="contacts">
    <apex:form >
        <apex:pageBlock title="Contacts List" id="contacts_list">
            Filter:
            <apex:selectList value="{! filterId }" size="1">
                <apex:selectOptions value="{! listViewOptions }"/>
                <apex:actionSupport event="onchange" reRender="contacts_list"/>
            </apex:selectList>
            <!-- Contacts List -->
            <apex:pageBlockTable value="{! contacts }" var="ct">
                <apex:column value="{! ct.FirstName }"/>
                <apex:column value="{! ct.LastName }"/>
                <apex:column value="{! ct.Email }"/>
                <apex:column value="{! ct.Account.Name }"/>
            </apex:pageBlockTable>
            <!-- Pagination -->
            <table style="width: 100%"><tr>
                <td>
                    Page: <apex:outputText value=" {!PageNumber} of {! CEILING(ResultSize / PageSize) }"/>
                </td>
                <td align="center">
                    <!-- Previous page -->
                    <!-- active -->
                    <apex:commandLink action="{! Previous }" value="« Previous"
                         rendered="{! HasPrevious }"/>
                    <!-- inactive (no earlier pages) -->
                    <apex:outputText style="color: #ccc;" value="« Previous"
                         rendered="{! NOT(HasPrevious) }"/>
                    &nbsp;&nbsp;
                    <!-- Next page -->
                    <!-- active -->
                    <apex:commandLink action="{! Next }" value="Next »"
                         rendered="{! HasNext }"/>
                    <!-- inactive (no more pages) -->
                    <apex:outputText style="color: #ccc;" value="Next »"
                         rendered="{! NOT(HasNext) }"/>
                </td>
                <td align="right">
                    Records per page:
                    <apex:selectList value="{! PageSize }" size="1">
                        <apex:selectOption itemValue="1" itemLabel="1"/>
                        <apex:selectOption itemValue="5" itemLabel="5"/>
                        <apex:selectOption itemValue="10" itemLabel="10"/>
                        <apex:selectOption itemValue="20" itemLabel="20"/>
                        <apex:actionSupport event="onchange" reRender="contacts_list"/>
                    </apex:selectList>
                </td>
            </tr></table>
        </apex:pageBlock>
    </apex:form>
</apex:page>


//링크걸기
<apex:page standardController="Account" recordSetVar="accounts">
        <apex:repeat value="{! Accounts }" var="a">
            <li>
                <apex:outputLink value="/{! a.ID }">
                    {! a.name}    
                </apex:outputLink>  
            </li>
        </apex:repeat>
</apex:page>


//정적 주소에서 이미지 가져오기 vfimagetest = 압축파일명 
<apex:page >
    <apex:image url="{!URLFOR($Resource.vfimagetest, 'cats/kitten1.jpg')}" />
</apex:page>


//이름 필터링 controller
public class ContactsListWithController {
    private String sortOrder = 'LastName';
    public List<Contact> getContacts() {
        List<Contact> results = Database.query(
            'SELECT Id, FirstName, LastName, Title, Email ' +
            'FROM Contact ' +
            'ORDER BY ' + sortOrder + ' ASC ' +
            'LIMIT 10'
        );
        return results;
    }
    public void sortByLastName() {
        this.sortOrder = 'LastName';
    }
    public void sortByFirstName() {
        this.sortOrder = 'FirstName';
    }
}


//custom controller 사용
<apex:page controller="ContactsListWithController">
    <apex:form>
        <apex:pageBlock title="Contacts List" id="contacts_list">
            <apex:pageBlockTable value="{! contacts }" var="ct">
                <apex:column value="{! ct.FirstName }">
                    <apex:facet name="header">
                        <apex:commandLink action="{! sortByFirstName }"
                            reRender="contacts_list">First Name
                        </apex:commandLink>
                    </apex:facet>
                </apex:column>
                <apex:column value="{! ct.LastName }">
                    <apex:facet name="header">
                        <apex:commandLink action="{! sortByLastName }"
                            reRender="contacts_list">Last Name
                        </apex:commandLink>
                    </apex:facet>
                </apex:column>
                <apex:column value="{! ct.Title }"/>
                <apex:column value="{! ct.Email }"/>
            </apex:pageBlockTable>
        </apex:pageBlock>
    </apex:form>
</apex:page>


//직접 작성한 콘트롤러 실패 후 정답
public class NewCaseListController {
//    public Static List<Case> getNewCases () {
  //      List<Case> returnData = new List<Case>();
    //    List<Case> results = Database.query(
     //       'SELECT Id, CaseNumber, status '+
      //      'FROM Case' 
 //       );
   //     for(Case a : results){
     //       if(a.Status == 'New'){
       //         returnData.add(a);
         //   }
   //     }
     //   return returnData;
   // }

    list<case> newcase = new list<case>();
        public list<case> GetNewCases() 
        {
        newcase = [Select Id,CaseNumber from case where status='New'];
    
            return newcase;
        }
}

// 콘트롤러 사용
<apex:page controller="NewCaseListController">
        <apex:repeat value="{! newCases }" var="case">
            <li>
                <apex:outputLink value="/{! case.ID }">
                    {! case.ID}    
                </apex:outputLink>  
                    {! case.CaseNumber}    
            </li>
        </apex:repeat>
</apex:page>

 

 

3.위에서 언급한 전역변수는 26가지가 있다.

$Action

$Api

$Asset

$Cache.Org

$Cache.Session

$Component

$ComponentLabel

$CurrentPage

$FieldSet

$Label

$Label.Site

$MessageChannel

$Network

$ObjectType

$Organization

$Page

$Permission

$Profile

$Resource

$SControl

$Setup

$Site

$System.OriginDateTime

$User

$User.UITheme and

$User.UIThemeDisplayed

$UserRole

 

 

4.Visualforce 표현식은 대소문자를 구분하지 않으며, {! ... } 내 공백은 무시되고 &로 문자열 수식을 합칠 수 있다.

({! $User.FirstName & ' ' & $User.LastName })

 

 

5.SaaS, PaaS, IaaS등 여러가지 종류가 존재하는데

뒤의 aaS 부분은 as a Service의 약자로 동일하며

앞부분만 Software, Platform, Infrastructure이라는 차이가 있다.

 

이름에서도 알 수 있듯 SaaS는 소프트웨어까지 적용되 있기 때문에

사용만 하면 되는 서비스를 말하고

PaaS는 온프레미스나 클라우드의 최신 애플리케이션 등을 구축하고 관리하는 데 사용되는

클라우드 서비스 세트라고 볼 수 있다.

 

여기서 온프레미스는 직접 컴퓨터를 하나하나 연결해서 서버를 만드는 작업을 통해

모든 과정을 사용자가 통제할 수 있는 환경을 말하는데

이런 환경을 구축했기 때문에 AWS가 IaaS를 제공할 수 있기도 하다.

 

 

6.Heroku에서는 RESTful API 구축, 고객과 소통할 수 있는 웹 사이트 구축,

API 서비스를 통해 모바일 및 IoT 강화, 플랫폼의 기능을 완성하는 보완 도구를 제공,

빠르게 새로운 아이디어를 시도해 보고 나면 응용 프로그램을 삭제 가능 등

여러가지 장점들을 가지고 있다.

 

 

7.Heroku는 Git, GitHub, Heroku 검토 앱, 배포 버튼, Docker, Hashicorp Terraform 등으로

배포를 할 수 있으며 각각의 장점들은 아래의 표에서 확인할 수 있다.

Deployment Method Requirements Best Suited For Pros Cons
Git - Full access to both the Git repository and Heroku app to manually push code to production. - Projects with small, trusted teams. - Simple to add to any Git-based workflow - Supports Git submodules  - Can track your source code in Subversion or another revision control system - Requires manually deploying code with git push
GitHub Integration - Admin access to a GitHub repo - Automated deployments - Automatically deploys apps and keeps them up to date - Integrates with Heroku Pipelines, Review Apps, and Heroku CI for a continuous workflow - No support for Git submodules
Heroku Review Apps - The GitHub integration  - Heroku Pipelines - Projects in GitHub with apps deployed to multiple environments. - Option to automatically create and update Review Apps for each PR - Supports Docker images - Supports Heroku Private Spaces for testing changes in an isolated environment - Additional costs from resources used in Review Apps. See tips on optimizing costs on the Dev Center.
'Deploy to Heroku' Button - A GitHub repo - A valid app.json file in the project's root directory - Apps provided to your users or customers, such as open-source projects - Onboarding new hires - Deploy with clicks - Easy to add to a project's README file or web page - Provides a template with pre-configured default values, environment variables, and parameters - No support for Git submodules - No auto-updates when the repo changes. You must use another deployment method for subsequent deploys to the same app.
Docker - A Docker image - Apps with custom stacks. - More control over your app's stack - Automatically generate images, or push an existing image to the container registry - Consistency between environments - Compatible with Heroku Review Apps - You must maintain your own stack - No support for pipeline promotions
Hashicorp Terraform - Terraform - Apps with complex infrastructure components - Automates Heroku app deployments - Allows you to deploy Heroku apps as code - Simplifies the management of large, complex deployments - Can configure apps, Private Spaces, or resources from other providers into a repeatable multi-provider architecture Heroku Support can't provide help with these more complex deployments

 

8.Heroku의 코드는 Dynos라는 내부 플랫폼에서 실행되며

공룡과 관련이 있을 것 같은 이름이지만 단지 Linux 운영 체제 기반 런타임 컨테이너일 뿐이다.

 

런타임 컨테이너는 둘 이상의 Dynos를 구별해 서로 충돌없이 격리된 환경에서 동작할 수 있게 한다.

 

슬러그는 배포하기 위해 최적화된 응용 프로그램의 압축 및 사전 패키징된 복사본이며

Heroku에 푸시할 경우 슬러그 컴파일러가 코드를 수신하고

빌드팩을 이용해 구현 및 컴파일하게 된다.

 

 

 

 

 

(1).백준 1212번 8진수 2진수는 입력된 어마어마어마하게 큰(숫자일 수 있는) 8진수 숫자를 받아

2진수 숫자로 변경해야 하는 문제였다.

 

toString으로 2진수 변경을 쉽게 할 수 있었지만

BigInt로 처리해야하는 규모의 8진수 숫자를 8진수로 인식하게 만드는 것이 문제였는데

예전에 배웠던 0O, 0X등의 8, 16진수 규칙을 떠올리며 간단하게 해결할 수 있었다.

const input = '0O' + `314`
console.log(BigInt(input).toString(2))

'회고' 카테고리의 다른 글

[수습일지] - 20(주말)  (0) 2023.04.15
[수습일지] - 19  (0) 2023.04.14
[수습일지] - 17  (0) 2023.04.12
[수습일지] - 16  (0) 2023.04.11
[수습일지] - 15  (0) 2023.04.10

1.유닛 테스트를 통해 다음과 같은 이점을 얻을 수 있다.

  • 클래스 및 트리거가 예상대로 작동하는지 확인 가능
  • 회귀 테스트 도구 모음을 통해 향후 업데이트에도 사용 가능
  • 코드 범위 요구 사항 충족 확인 가능
  • 고품질 앱으로 생산성을 높이고 신뢰를 높일 수 있음

 

2.sfdc에서는 75%이상의 테스트 적용 범위를 요구하지만

최대한 100%를 목적으로 테스트를 작성해야 하며

모든 테스트 메서드를 작성한다고 하더라도 100%가 되지 않을 수 있다.

아래에는 추가적인 주의사항이다.

  • 조직 데이터에 접근하기 위해서는 @isTest가 아닌 @isTest(SeeAllData=true)를 사용해야 한다.
  • 조직에는 최대 6MB의 코드를 저장할 수 있지만 @isTest 주석이 달린 코드는 이 제한에서 제외한다.
  • 고유한 제약 조건이 있는 필드가 있는 일부 sObject의 경우 중복 sObject 레코드를 삽입하면 오류가 발생한다.
  • 테스트 메서드에서는 이메일을 보낼 수 없다.
  • 외부 서비스에 대한 콜아웃을 만들 수 없고 모의 콜아웃만 사용할 수 있다.
  • 테스트에서 수행된 SOSL 검색은 빈 결과를 반환하기 떄문에 Test.setFixedSearchResults()로 값을 정의해줘야 한다.

 

3.Apex 유닛 테스트 통과 코드

@isTest
private class TestVerifyDate {
    
    @isTest static void testTaskPriority() {
        Date a = Date.newInstance(2023, 4, 12);
        Date b = a.addDays(29);
        Date pri = VerifyDate.CheckDates(a, b);
        System.assertEquals(b, pri);
    }
    
    @isTest static void testTaskHighPriority() {
        Date a = Date.newInstance(2023, 4, 12);
        Date b = a.addDays(30);
        Date c = Date.newInstance(2023, 4, 30);
        Date pri = VerifyDate.CheckDates(a, b);
        System.assertEquals(c, pri);
    }
}
//진행이 안되서 문제점을 찾아보니 private에 접근을 하지 못하기 때문이었다.
//테스트를 진행할 때는 private을 작동시키는 public 메서드의 분기 조건을 파악하고
//해당 분기 조건이 모든 private을 작동시킬 수 있게 다양한 접근으로 테스트를 진행해야 한다.
//다른 문제 형식으로 Test를 붙여서 작성했는데 통과되지 않아 보니 이번 과제에서는 Test를 앞에 붙여야 했다.

 

 

4.테스트 메서드에서는 Test.startTest() 및 Test.stopTest()를 사용해 테스트를 진행할 수 있고

해당 결과를 Database.SaveResult 등의 타입으로 설정된 결과 값에 저장한 다음

해당 결과값(result)의 isSuccess, getErrors 등의 메서드로 작업 수행 결과를 체크할 수 있다.

 

 

5.Apex 트리거 테스트 통과 코드

@isTest
public class TestRestrictContactByName {
    @isTest static void Test() {
        Contact acct = new Contact(LastName = 'INVALIDNAME');       
        
        Test.startTest();
        insert acct;       
        Database.SaveResult result = Database.insert(acct, false);
        Test.stopTest();
        
        System.assert(!result.isSuccess());
        System.assert(result.getErrors().size() > 0);
        System.assertEquals('The Last Name \"INVALIDNAME\" is not allowed for DML',
                             result.getErrors()[0].getMessage());
    }
}

//1.name 부분부터 막혀서 당황했는데 자세히 보니 수정 불가능한 내용이라고 할당을 할 수 없었다.
//Object Manager에 들어갔지만 따로 설정하는 부분을 찾을 수 없었기 때문에 테스트 할 트리거를 자세히 보니
//LastName을 수정해야 한다고 했지만 해당 필드는 존재하지 않았다.
//결론적으로 언급도 하지 않은 LastName field를 직접 생성하니 오류가 사라졌다.
//2.Database.DeleteResult를 Insert로 수정하고 진행해도 되지 않아 검색해도 제대로 나오지 않았다.
//결국 찾은 것은 SaveResult인데 한번 알면 별것 아니지만 메서드명에서 많이 막히는 것 같다.
//3.테스트는 통과할 수 있었지만 마지막 에러 처리가 되지 않았는데 The Last Name 어쩌고로 끝나는 것이 아니라
//추가적으로 이상한 글자가 더 포함된 것 같았다.
//4.글 작성 시 확인하니 INVALIDNAME 부분이 이상하게 보이는데 여러가지 방법으로 시도하다가 
//최종적으로 저 부분에서 포기한 내용이다.

 

 

6.TestDataFactory는 @isTest 환경에서만 작동하는 유형의 클래스로

테스트 데이터 설정과 같은 유용한 작업을 수행하기 위해

테스트 메서드에서 호출할 수 있는 메서드가 포함되어있다.

 

 

7.Apex 테스트용 데이터 생성 통과 코드

public class RandomContactFactory {
    public Static List<Contact> generateRandomContacts(Integer contactAmount, String lastNameStr){
        Contact[] contacts = new List<Contact>(); 
        for(Integer i=0;i<contactAmount;i++) {
            Contact a = new Contact(FirstName = lastNameStr + i);
            contacts.add(a);
        }
        system.debug(contacts);
        return contacts;
    }
}
//1.공개 클래스 이름 복붙(test 안붙임)
//2.공개 정적 List<Contact> 이름 복붙(test 안붙임)
//3.매개변수 (정수형 갯수, 문자형 성)
//4.반환 List <Contact>
//
//에러 이유 
//1.method does not exist(아님)
//2.is not static(아님)
//3.did not return the correct set of Contact records (모름)
//
//해결 => FirstName이라는 필드가 존재했지만 객체 관리자에는 표기되지 않아
//직접 생성해서 시도하다가 에러가 발생했고
//해당 필드의 존재 여부를 정확히 알기 위해서는 공식문서를 봐야한다.

 

 

8.Lightning 앱 빌더에서 Visualforce 페이지를 사용할 수 있도록 하려면

"Lightning Experience, Lightning 커뮤니티 및 모바일 앱에 사용 가능"을 활성화해야한다.

 

 

9.개발을 진행하기 전 환경 세팅을 해야 하는데

기본 개발 환경, 검토 환경으로 분리될 수 있고

검토 환경은 개발 환경과 다른 브라우저 및 모바일/태블릿 기기 등의 환경이 포함된다.

 

일반적으로 주 개발 환경은 크롬으로 개발을 진행하게 되며

인터페이스 확인은 Salesforce Classic으로 진행하고

Lightning Exprience 검토 환경에서는 타 브라우저들을 테스트 사용자로 접속해

Lightning Experience 작동을 테스트해볼 수 있다.

 

모바일 환경은 일반적으로 Salesforce 앱의 디자인 및 동작을 확인하기 위해 세팅하며

앱을 사용할 수 있는 Android, iOS, 태블릿 등으로 진행한다.

 

초기 세팅이 번거로울 수 있지만 실제로 사용되기 전에는 반드시 점검을 진행해야 하고

특정 환경에서만 되는 것과 특정 환경에서만 발생하는 에러 등을 독립적으로 테스트하기 위해서

각각 다른 지원 장치, 운영체제, 앱 및 브라우저 등을 테스트해야하며

추가적으로 서로 다른 사용자 인터페이스도 확인해야 한다.

(버전마다 확인해야 할 수 있지만 대부분 최신 버전만 확인해도 통과한다)

 

 

10.Visualforce를 Lightning Experience에서 시작할 경우 몇가지 특징이있다.

  • Visualforce 페이지가 Lightning Experience에서 실행되면 HTML iframe 내부에 생성
  • Lightning Experience는 상위 컨텍스트이고 Visualforce 페이지는 하위 컨텍스트가 된다.
  • Visualforce 페이지를 Lightning Experience 앱 내의 iframe에 포함시켜 보안에 영향을 준다.
    • 세션 유지 및 갱신
    • 인증
    • 교차 도메인 요청
    • 임베딩 제한
  • Visualforce 페이지를 Lightning Experience 앱 내의 iframe에 포함시켜 범위에 영향을 준다.
    • DOM 액세스 및 수정
    • JavaScript 범위, 가시성 및 액세스
    • window.location과 같은 JavaScript 전역 변수
  • Lightning Experience의 Visualforce 기본값 및 환경 변경 사항
    • Salesforce Classic 헤더 및 탐색 메뉴를 숨길 수 없음
    • 머리글 및 사이드바가 항상 표시되지 않음
  • apex:relatedList 및 차단된 관련 목록 확인하기
  • iframe 피하기
  • window.location 사용하지 않기
  • sforce.one은 Salesforce 모바일 전용이 아님

 

 

 

 

(1).백준 5585번 거스름돈은 1000원짜리 지폐를 가지고 있을 때

특정 금액의 물건을 살 경우 500, 100, 50, 10, 5, 1원짜리 지폐를 최소 몇장 거슬러 받을 수 있는지 묻는 문제였다.

 

최소 1원 이상의 물건을 구매해야 하기 때문에 첫 번째 500이상일 경우 500을 감소시켰고

그 외에는 몇개가 될지 모르기 때문에 Math.floor와 %를 사용해 처리했다.

 

조금 더 이쁘게 배열안에 금액권을 담아서 for문으로 돌린다던가 방법이 있을 것 같지만

정말로 요즘 시간이 없어서 한 문제씩 겨우 풀고 있다.

const input = 1

let money = 1000 - input
let count = 0

if(money >= 500){
    money -= 500
    count++
}
if(money >= 100){
    count += Math.floor(money/100)
    money = money % 100
}
if(money >= 50){
    count += Math.floor(money/50)
    money = money % 50
}
if(money >= 10){
    count += Math.floor(money/10)
    money = money % 10
}
if(money >= 5){
    count += Math.floor(money/5)
    money = money % 5
}
count += money

console.log(count)

'회고' 카테고리의 다른 글

[수습일지] - 19  (0) 2023.04.14
[수습일지] - 18  (0) 2023.04.13
[수습일지] - 16  (0) 2023.04.11
[수습일지] - 15  (0) 2023.04.10
[수습일지] - 14(주말)  (0) 2023.04.09

+ Recent posts